From a4c71ce26bb10bd822fefcaa3a7c414271677ee6 Mon Sep 17 00:00:00 2001 From: Ammar Qammaz Date: Mon, 17 Jul 2023 18:14:06 +0300 Subject: [PATCH 001/154] adding a stub for MocapNET4 --- src/MocapNET4/MocapNET4Test/CMakeLists.txt | 24 + src/MocapNET4/MocapNET4Test/test.cpp | 122 + src/MocapNET4/MocapNETLib4/CMakeLists.txt | 48 + src/MocapNET4/MocapNETLib4/IO/inputRouting.h | 259 ++ src/MocapNET4/MocapNETLib4/JSON/nxjson.c | 389 ++ src/MocapNET4/MocapNETLib4/JSON/nxjson.h | 65 + .../MocapNETLib4/JSON/readListFile.h | 173 + .../JSON/readModelConfiguration.h | 753 ++++ src/MocapNET4/MocapNETLib4/NSxM/EDM.h | 103 + src/MocapNET4/MocapNETLib4/NSxM/NSDM.h | 45 + src/MocapNET4/MocapNETLib4/NSxM/NSRM.h | 343 ++ src/MocapNET4/MocapNETLib4/NSxM/NSxM.h | 1481 +++++++ .../MocapNETLib4/NSxM/calculations.c | 137 + .../MocapNETLib4/NSxM/calculations.h | 27 + src/MocapNET4/MocapNETLib4/PCA/PCA.h | 446 +++ .../PCA/principleComponentAnalysis.py | 327 ++ src/MocapNET4/MocapNETLib4/config.h | 46 + src/MocapNET4/MocapNETLib4/mocapnet4.cpp | 61 + src/MocapNET4/MocapNETLib4/mocapnet4.h | 3464 +++++++++++++++++ src/MocapNET4/MocapNETLib4/tools.h | 86 + 20 files changed, 8399 insertions(+) create mode 100644 src/MocapNET4/MocapNET4Test/CMakeLists.txt create mode 100644 src/MocapNET4/MocapNET4Test/test.cpp create mode 100644 src/MocapNET4/MocapNETLib4/CMakeLists.txt create mode 100644 src/MocapNET4/MocapNETLib4/IO/inputRouting.h create mode 100644 src/MocapNET4/MocapNETLib4/JSON/nxjson.c create mode 100644 src/MocapNET4/MocapNETLib4/JSON/nxjson.h create mode 100644 src/MocapNET4/MocapNETLib4/JSON/readListFile.h create mode 100644 src/MocapNET4/MocapNETLib4/JSON/readModelConfiguration.h create mode 100644 src/MocapNET4/MocapNETLib4/NSxM/EDM.h create mode 100644 src/MocapNET4/MocapNETLib4/NSxM/NSDM.h create mode 100644 src/MocapNET4/MocapNETLib4/NSxM/NSRM.h create mode 100644 src/MocapNET4/MocapNETLib4/NSxM/NSxM.h create mode 100644 src/MocapNET4/MocapNETLib4/NSxM/calculations.c create mode 100644 src/MocapNET4/MocapNETLib4/NSxM/calculations.h create mode 100644 src/MocapNET4/MocapNETLib4/PCA/PCA.h create mode 100644 src/MocapNET4/MocapNETLib4/PCA/principleComponentAnalysis.py create mode 100644 src/MocapNET4/MocapNETLib4/config.h create mode 100644 src/MocapNET4/MocapNETLib4/mocapnet4.cpp create mode 100644 src/MocapNET4/MocapNETLib4/mocapnet4.h create mode 100644 src/MocapNET4/MocapNETLib4/tools.h diff --git a/src/MocapNET4/MocapNET4Test/CMakeLists.txt b/src/MocapNET4/MocapNET4Test/CMakeLists.txt new file mode 100644 index 0000000..211cfab --- /dev/null +++ b/src/MocapNET4/MocapNET4Test/CMakeLists.txt @@ -0,0 +1,24 @@ +project( MocapNET4Test ) +cmake_minimum_required( VERSION 2.8.7 ) +#cmake_minimum_required(VERSION 3.5) +find_package(OpenCV REQUIRED) +INCLUDE_DIRECTORIES(${OpenCV_INCLUDE_DIRS}) + +#set_property(GLOBAL PROPERTY USE_FOLDERS ON) +set(CMAKE_CXX_STANDARD 11) +include_directories(${TENSORFLOW_INCLUDE_ROOT}) +include_directories(../MocapNETLib4) #<- includes in test.cpp + + +add_executable(MocapNET4Test test.cpp) + +target_link_libraries(MocapNET4Test rt dl m ${OpenCV_LIBRARIES} ${OPENGL_LIBS} JointEstimator2D Tensorflow TensorflowFramework MocapNETLib4 ${NETWORK_CLIENT_LIBRARIES} ${PNG_Libs} ${JPG_Libs} ) +set_target_properties(MocapNET4Test PROPERTIES DEBUG_POSTFIX "D") + + +set_target_properties(MocapNET4Test PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}" + ) + diff --git a/src/MocapNET4/MocapNET4Test/test.cpp b/src/MocapNET4/MocapNET4Test/test.cpp new file mode 100644 index 0000000..dc2ffd3 --- /dev/null +++ b/src/MocapNET4/MocapNET4Test/test.cpp @@ -0,0 +1,122 @@ + +/** @file livedemo.cpp + * @brief This is the main "demo" offered in this repository, it will take a stream from a webcam or video file using OpenCV and run +* 2D pose estimation + MocapNET giving you a nice 3D visualization as well as an output .bvh file + * @author Ammar Qammaz (AmmarkoV) + */ +#include +#include +//----------------------------------------------------------------- + +#include "PCA/PCA.h" +#include "JSON/readModelConfiguration.h" +#include "JSON/readListFile.h" + +#include "NSxM/NSxM.h" + +#define NORMAL "\033[0m" +#define BLACK "\033[30m" /* Black */ +#define RED "\033[31m" /* Red */ +#define GREEN "\033[32m" /* Green */ +#define YELLOW "\033[33m" /* Yellow */ + + + +void testPCA(struct PCAData * pca) +{ + float output[210]={0}; + int outputSize = 210; + + float input[458]={0}; + int inputSize = 458; + for (int i=0; i + + +/** + * @brief This is an array of names for all the new BODY25 body parts. + * This tries to mirror body_configuration.json and everything used is lowercase exactly for this reason.. + * it also has to be the same with the bvh file headerWithHeadAndOneMotion.bvh + */ +static const char * Body25Labels[] = +{ + "head", //0 + "neck", //1 + "rshoulder", //2 + "relbow", //3 + "rhand", //4 + "lshoulder", //5 + "lelbow", //6 + "lhand", //7 + "hip", //8 + "rhip", //9 + "rknee", //10 + "rfoot", //11 + "lhip", //12 + "lknee", //13 + "lfoot", //14 + "endsite_eye.r", //15 + "endsite_eye.l", //16 + "rear", //17 ========= No correspondance + "lear", //18 ========= No correspondance + "endsite_toe1-2.l",//19 + "endsite_toe5-3.l",//20 + "lheel", //21 ========= No correspondance + "endsite_toe1-2.r",//22 + "endsite_toe5-3.r",//23 + "rheel", //24 ========= No correspondance + "bkg", //25 ========= No correspondance + //================== + "End of Joint Names", + 0 +}; + + + + +#include //toupper +static int strcasecmp_route(const char * input1,const char * input2) +{ + if ( (input1==0) || (input2==0) ) + { + fprintf(stderr,"Error , calling strcasecmp_route with null parameters \n"); + return 1; + } + + #if CASE_SENSITIVE_OBJECT_NAMES + return strcmp(input1,input2); + #endif + + unsigned int len1 = strlen(input1); + unsigned int len2 = strlen(input2); + if (len1!=len2) + { + //mismatched lengths of strings , they can't be equal..! + return 1; + } + + char A; //<- character buffer for input1 + char B; //<- character buffer for input2 + unsigned int i=0; + while (iroutedValues!=0) { free(route->routedValues); route->routedValues=0; } + if (route->routingRules!=0) { free(route->routingRules); route->routingRules=0; } + route->numberOfRoutingRules=0; + route->resolved=0; + return 1; +} + + + +/** + * @brief generate Route from Labels + * @param Pointer to a model configuration + * @param Pointer to the route structure + * @param Labels to route + * @param Number of Labels to route + * @retval 1=Success/0=Failure + */ +static int generateRouteFromLabels( + struct ModelConfigurationData * config, + struct inputRouting * route, + const char * * incomingLabels, + unsigned int incomingLabelsLength + ) +{ + fprintf(stderr,GREEN "\ngenerateRouteFromLabels for %u labels and %u hierarchy elements\n" NORMAL,incomingLabelsLength,config->numberOfHierarchyElements); + + if (config==0) { return 0; } + if (incomingLabels==0) { return 0; } + if (route==0) { return 0; } + + + destroyRoute(route); + + route->numberOfRoutingRules = config->numberOfHierarchyElements; + + route->routedValues = (float*) malloc(sizeof(float) * 3 * route->numberOfRoutingRules); + route->routingRules = (int*) malloc(sizeof(int) * route->numberOfRoutingRules); + + if ( + (route->routedValues==0) || + (route->routingRules==0) + ) //Failed allocating memory.. + { + destroyRoute(route); + return 0; + } + + + memset(route->routedValues,0,sizeof(float) * 3 * route->numberOfRoutingRules); + memset(route->routingRules,0,sizeof(int) * route->numberOfRoutingRules); + + + int routingFailures = 0; + for (int trg=0; trgnumberOfHierarchyElements; trg++) + { + //fprintf(stderr,"trg %u/%u\n",trg,config->numberOfHierarchyElements); + int trgJointResolved = 0; + + for (int src=0; srchierarchyElements[trg].joint)==0) + { + fprintf(stderr,GREEN "MATCH %s (%u) to %s (%u) \n" NORMAL,incomingLabels[src],src,config->hierarchyElements[trg].joint,trg); + trgJointResolved = 1; + route->routedValues[trg*3+0] = 0.0;//Set everything to zero initially.. + route->routedValues[trg*3+1] = 0.0;//Set everything to zero initially.. + route->routedValues[trg*3+2] = 0.0;//Set everything to zero initially.. + route->routingRules[trg] = src; + } + } + + + if (!trgJointResolved) + { + fprintf(stderr,YELLOW "Could not match %s \n" NORMAL,config->hierarchyElements[trg].joint); + routingFailures+=1; + } + } + + if (routingFailures==0) + { + fprintf(stderr,GREEN "Successfully routed all %u input rules\n" NORMAL,route->numberOfRoutingRules); + } else + { + fprintf(stderr,RED "Failed routing %u out of %u input rules\n" NORMAL, routingFailures,route->numberOfRoutingRules); + } + + route->resolved = (routingFailures==0); + + return (routingFailures==0); +} + + + +static int routeInput( + float * preexistingOutput2DJoints, + int * output2DJointsLength, + struct ModelConfigurationData * config, + struct inputRouting * route, + float * raw2DPoints, + int raw2DPointsLength + ) +{ + if (route->resolved) + { + //if (raw2DPointsLength==route->numberOfRoutingRules) + { + float * output = preexistingOutput2DJoints; + + int val = 0; + for (int i=0; inumberOfRoutingRules; i++) + { + fprintf(stderr,"%s ",config->hierarchyElements[i].joint); + //Each rule has 3 values 2DX, 2DY, 2DVisibility + //-------------------------------------------------------------------------- + output[val]=raw2DPoints[(route->routingRules[i]*3) + 0]; + fprintf(stderr,"2DX=%0.2f ",output[val]); + val+=1; + //-------------------------------------------------------------------------- + output[val]=raw2DPoints[(route->routingRules[i]*3) + 1]; + fprintf(stderr,"2DY=%0.2f ",output[val]); + val+=1; + //-------------------------------------------------------------------------- + output[val]=raw2DPoints[(route->routingRules[i]*3) + 2]; + fprintf(stderr,"2DVisibility=%0.2f \n",output[val]); + val+=1; + //-------------------------------------------------------------------------- + } + return 1; + } + } + return 0; +} + + + +#ifdef __cplusplus +} +#endif + + + + +#endif diff --git a/src/MocapNET4/MocapNETLib4/JSON/nxjson.c b/src/MocapNET4/MocapNETLib4/JSON/nxjson.c new file mode 100644 index 0000000..84a0c3c --- /dev/null +++ b/src/MocapNET4/MocapNETLib4/JSON/nxjson.c @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2013 Yaroslav Stavnichiy + * + * This file is part of NXJSON. + * + * NXJSON is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * NXJSON is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with NXJSON. If not, see . + */ + +// this file can be #included in your code +#ifndef NXJSON_C +#define NXJSON_C + +#ifdef __cplusplus +extern "C" { +#endif + + +#include +#include +#include +#include +#include + +#include "nxjson.h" + +// redefine NX_JSON_CALLOC & NX_JSON_FREE to use custom allocator +#ifndef NX_JSON_CALLOC +#define NX_JSON_CALLOC() calloc(1, sizeof(nx_json)) +#define NX_JSON_FREE(json) free((void*)(json)) +#endif + +// redefine NX_JSON_REPORT_ERROR to use custom error reporting +#ifndef NX_JSON_REPORT_ERROR +#define NX_JSON_REPORT_ERROR(msg, p) fprintf(stderr, "NXJSON PARSE ERROR (%d): " msg " at %s\n", __LINE__, p) +#endif + +#define IS_WHITESPACE(c) ((unsigned char)(c)<=(unsigned char)' ') + +static const nx_json dummy={ NX_JSON_NULL }; + +static nx_json* create_json(nx_json_type type, const char* key, nx_json* parent) { + nx_json* js=NX_JSON_CALLOC(); + assert(js); + js->type=type; + js->key=key; + if (!parent->last_child) { + parent->child=parent->last_child=js; + } + else { + parent->last_child->next=js; + parent->last_child=js; + } + parent->length++; + return js; +} + +void nx_json_free(const nx_json* js) { + nx_json* p=js->child; + nx_json* p1; + while (p) { + p1=p->next; + nx_json_free(p); + p=p1; + } + NX_JSON_FREE(js); +} + +static int unicode_to_utf8(unsigned int codepoint, char* p, char** endp) { + // code from http://stackoverflow.com/a/4609989/697313 + if (codepoint<0x80) *p++=codepoint; + else if (codepoint<0x800) *p++=192+codepoint/64, *p++=128+codepoint%64; + else if (codepoint-0xd800u<0x800) return 0; // surrogate must have been treated earlier + else if (codepoint<0x10000) *p++=224+codepoint/4096, *p++=128+codepoint/64%64, *p++=128+codepoint%64; + else if (codepoint<0x110000) *p++=240+codepoint/262144, *p++=128+codepoint/4096%64, *p++=128+codepoint/64%64, *p++=128+codepoint%64; + else return 0; // error + *endp=p; + return 1; +} + +nx_json_unicode_encoder nx_json_unicode_to_utf8=unicode_to_utf8; + +static inline int hex_val(char c) { + if (c>='0' && c<='9') return c-'0'; + if (c>='a' && c<='f') return c-'a'+10; + if (c>='A' && c<='F') return c-'A'+10; + return -1; +} + +static char* unescape_string(char* s, char** end, nx_json_unicode_encoder encoder) { + char* p=s; + char* d=s; + char c; + while ((c=*p++)) { + if (c=='"') { + *d='\0'; + *end=p; + return s; + } + else if (c=='\\') { + switch (*p) { + case '\\': + case '/': + case '"': + *d++=*p++; + break; + case 'b': + *d++='\b'; p++; + break; + case 'f': + *d++='\f'; p++; + break; + case 'n': + *d++='\n'; p++; + break; + case 'r': + *d++='\r'; p++; + break; + case 't': + *d++='\t'; p++; + break; + case 'u': // unicode + if (!encoder) { + // leave untouched + *d++=c; + break; + } + char* ps=p-1; + int h1, h2, h3, h4; + if ((h1=hex_val(p[1]))<0 || (h2=hex_val(p[2]))<0 || (h3=hex_val(p[3]))<0 || (h4=hex_val(p[4]))<0) { + NX_JSON_REPORT_ERROR("invalid unicode escape", p-1); + return 0; + } + unsigned int codepoint=h1<<12|h2<<8|h3<<4|h4; + if ((codepoint & 0xfc00)==0xd800) { // high surrogate; need one more unicode to succeed + p+=6; + if (p[-1]!='\\' || *p!='u' || (h1=hex_val(p[1]))<0 || (h2=hex_val(p[2]))<0 || (h3=hex_val(p[3]))<0 || (h4=hex_val(p[4]))<0) { + NX_JSON_REPORT_ERROR("invalid unicode surrogate", ps); + return 0; + } + unsigned int codepoint2=h1<<12|h2<<8|h3<<4|h4; + if ((codepoint2 & 0xfc00)!=0xdc00) { + NX_JSON_REPORT_ERROR("invalid unicode surrogate", ps); + return 0; + } + codepoint=0x10000+((codepoint-0xd800)<<10)+(codepoint2-0xdc00); + } + if (!encoder(codepoint, d, &d)) { + NX_JSON_REPORT_ERROR("invalid codepoint", ps); + return 0; + } + p+=5; + break; + default: + // leave untouched + *d++=c; + break; + } + } + else { + *d++=c; + } + } + NX_JSON_REPORT_ERROR("no closing quote for string", s); + return 0; +} + +static char* skip_block_comment(char* p) { + // assume p[-2]=='/' && p[-1]=='*' + char* ps=p-2; + if (!*p) { + NX_JSON_REPORT_ERROR("endless comment", ps); + return 0; + } + REPEAT: + p=strchr(p+1, '/'); + if (!p) { + NX_JSON_REPORT_ERROR("endless comment", ps); + return 0; + } + if (p[-1]!='*') goto REPEAT; + return p+1; +} + +static char* parse_key(const char** key, char* p, nx_json_unicode_encoder encoder) { + // on '}' return with *p=='}' + char c; + while ((c=*p++)) { + if (c=='"') { + *key=unescape_string(p, &p, encoder); + if (!*key) return 0; // propagate error + while (*p && IS_WHITESPACE(*p)) p++; + if (*p==':') return p+1; + NX_JSON_REPORT_ERROR("unexpected chars", p); + return 0; + } + else if (IS_WHITESPACE(c) || c==',') { + // continue + } + else if (c=='}') { + return p-1; + } + else if (c=='/') { + if (*p=='/') { // line comment + char* ps=p-1; + p=strchr(p+1, '\n'); + if (!p) { + NX_JSON_REPORT_ERROR("endless comment", ps); + return 0; // error + } + p++; + } + else if (*p=='*') { // block comment + p=skip_block_comment(p+1); + if (!p) return 0; + } + else { + NX_JSON_REPORT_ERROR("unexpected chars", p-1); + return 0; // error + } + } + else { + NX_JSON_REPORT_ERROR("unexpected chars", p-1); + return 0; // error + } + } + NX_JSON_REPORT_ERROR("unexpected chars", p-1); + return 0; // error +} + +static char* parse_value(nx_json* parent, const char* key, char* p, nx_json_unicode_encoder encoder) { + nx_json* js; + while (1) { + switch (*p) { + case '\0': + NX_JSON_REPORT_ERROR("unexpected end of text", p); + return 0; // error + case ' ': case '\t': case '\n': case '\r': + case ',': + // skip + p++; + break; + case '{': + js=create_json(NX_JSON_OBJECT, key, parent); + p++; + while (1) { + const char* new_key; + p=parse_key(&new_key, p, encoder); + if (!p) return 0; // error + if (*p=='}') return p+1; // end of object + p=parse_value(js, new_key, p, encoder); + if (!p) return 0; // error + } + case '[': + js=create_json(NX_JSON_ARRAY, key, parent); + p++; + while (1) { + p=parse_value(js, 0, p, encoder); + if (!p) return 0; // error + if (*p==']') return p+1; // end of array + } + case ']': + return p; + case '"': + p++; + js=create_json(NX_JSON_STRING, key, parent); + js->text_value=unescape_string(p, &p, encoder); + if (!js->text_value) return 0; // propagate error + return p; + case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': + { + js=create_json(NX_JSON_INTEGER, key, parent); + char* pe; + errno = 0; + js->int_value=strtoll(p, &pe, 0); + if (pe==p || errno==ERANGE) { + NX_JSON_REPORT_ERROR("invalid number", p); + return 0; // error + } + if (*pe=='.' || *pe=='e' || *pe=='E') { // double value + js->type=NX_JSON_DOUBLE; + errno = 0; + js->dbl_value=strtod(p, &pe); + if (pe==p || errno==ERANGE) { + NX_JSON_REPORT_ERROR("invalid number", p); + return 0; // error + } + } + else { + js->dbl_value=js->int_value; + } + return pe; + } + case 't': + if (!strncmp(p, "true", 4)) { + js=create_json(NX_JSON_BOOL, key, parent); + js->int_value=1; + return p+4; + } + NX_JSON_REPORT_ERROR("unexpected chars", p); + return 0; // error + case 'f': + if (!strncmp(p, "false", 5)) { + js=create_json(NX_JSON_BOOL, key, parent); + js->int_value=0; + return p+5; + } + NX_JSON_REPORT_ERROR("unexpected chars", p); + return 0; // error + case 'n': + if (!strncmp(p, "null", 4)) { + create_json(NX_JSON_NULL, key, parent); + return p+4; + } + NX_JSON_REPORT_ERROR("unexpected chars", p); + return 0; // error + case '/': // comment + if (p[1]=='/') { // line comment + char* ps=p; + p=strchr(p+2, '\n'); + if (!p) { + NX_JSON_REPORT_ERROR("endless comment", ps); + return 0; // error + } + p++; + } + else if (p[1]=='*') { // block comment + p=skip_block_comment(p+2); + if (!p) return 0; + } + else { + NX_JSON_REPORT_ERROR("unexpected chars", p); + return 0; // error + } + break; + default: + NX_JSON_REPORT_ERROR("unexpected chars", p); + return 0; // error + } + } +} + +const nx_json* nx_json_parse_utf8(char* text) { + return nx_json_parse(text, unicode_to_utf8); +} + +const nx_json* nx_json_parse(char* text, nx_json_unicode_encoder encoder) { + nx_json js={0}; + if (!parse_value(&js, 0, text, encoder)) { + if (js.child) nx_json_free(js.child); + return 0; + } + return js.child; +} + +const nx_json* nx_json_get(const nx_json* json, const char* key) { + if (!json || !key) return &dummy; // never return null + nx_json* js; + for (js=json->child; js; js=js->next) { + if (js->key && !strcmp(js->key, key)) return js; + } + return &dummy; // never return null +} + +const nx_json* nx_json_item(const nx_json* json, int idx) { + if (!json) return &dummy; // never return null + nx_json* js; + for (js=json->child; js; js=js->next) { + if (!idx--) return js; + } + return &dummy; // never return null +} + + +#ifdef __cplusplus +} +#endif + +#endif /* NXJSON_C */ diff --git a/src/MocapNET4/MocapNETLib4/JSON/nxjson.h b/src/MocapNET4/MocapNETLib4/JSON/nxjson.h new file mode 100644 index 0000000..f85bba2 --- /dev/null +++ b/src/MocapNET4/MocapNETLib4/JSON/nxjson.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2013 Yaroslav Stavnichiy + * + * This file is part of NXJSON. + * + * NXJSON is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * NXJSON is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with NXJSON. If not, see . + */ + +#ifndef NXJSON_H +#define NXJSON_H + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef enum nx_json_type { + NX_JSON_NULL, // this is null value + NX_JSON_OBJECT, // this is an object; properties can be found in child nodes + NX_JSON_ARRAY, // this is an array; items can be found in child nodes + NX_JSON_STRING, // this is a string; value can be found in text_value field + NX_JSON_INTEGER, // this is an integer; value can be found in int_value field + NX_JSON_DOUBLE, // this is a double; value can be found in dbl_value field + NX_JSON_BOOL // this is a boolean; value can be found in int_value field +} nx_json_type; + +typedef struct nx_json { + nx_json_type type; // type of json node, see above + const char* key; // key of the property; for object's children only + const char* text_value; // text value of STRING node + long long int_value; // the value of INTEGER or BOOL node + double dbl_value; // the value of DOUBLE node + int length; // number of children of OBJECT or ARRAY + struct nx_json* child; // points to first child + struct nx_json* next; // points to next child + struct nx_json* last_child; +} nx_json; + +typedef int (*nx_json_unicode_encoder)(unsigned int codepoint, char* p, char** endp); + +extern nx_json_unicode_encoder nx_json_unicode_to_utf8; + +const nx_json* nx_json_parse(char* text, nx_json_unicode_encoder encoder); +const nx_json* nx_json_parse_utf8(char* text); +void nx_json_free(const nx_json* js); +const nx_json* nx_json_get(const nx_json* json, const char* key); // get object's property by key +const nx_json* nx_json_item(const nx_json* json, int idx); // get array element by index + + +#ifdef __cplusplus +} +#endif + +#endif /* NXJSON_H */ diff --git a/src/MocapNET4/MocapNETLib4/JSON/readListFile.h b/src/MocapNET4/MocapNETLib4/JSON/readListFile.h new file mode 100644 index 0000000..e90c3d1 --- /dev/null +++ b/src/MocapNET4/MocapNETLib4/JSON/readListFile.h @@ -0,0 +1,173 @@ +/** @file readListFile.h + * @brief An implementation of reading a list from a text file + * @author Ammar Qammaz (AmmarkoV) + */ + +#ifndef READ_LIST_FILE_H_INCLUDED +#define READ_LIST_FILE_H_INCLUDED + + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include + +struct listFileEntry +{ + int strLength; + char * str; +}; + +struct listFileData +{ + unsigned int numberOfEntries; + struct listFileEntry * entry; +}; + + +static int slowLineCounter(const char * filename) +{ + char ch; + int linesCount=0; + //open file in read more + FILE * fp=fopen(filename,"r"); + if(fp==NULL) { + printf("File \"%s\" does not exist!!!\n",filename); + return -1; + } + //read character by character and check for new line + while((ch=fgetc(fp))!=EOF) + { + if(ch=='\n') + linesCount++; + } + //close the file + fclose(fp); + + return linesCount; +} + +static int destroyListFile(struct listFileData * listOutput) +{ + fprintf(stderr,"destroying List File..! \n"); + if (listOutput!=0) + { + if (listOutput->entry!=0) + { + for (int i=0; inumberOfEntries; i++) + { + if (listOutput->entry[i].str!=0) + { + free(listOutput->entry[i].str); + listOutput->entry[i].strLength = 0; + } + } + //------------------------------------------------ + free(listOutput->entry); + listOutput->entry=0; + } + } + fprintf(stderr,"destroyed List File..! \n"); + return 1; +} + + + +static int printListFile(struct listFileData * listOutput,const char * label) +{ + if (listOutput==0) { return 0; } + if (listOutput->entry!=0) + { + printf("Listing %s\n",label); + printf("_______________________\n"); + for (int i=0; inumberOfEntries; i++) + { + printf("Line %u === `%s`\n",i,listOutput->entry[i].str); + } + printf("\n\n\n"); + return 1; + } + + + return 0; +} + +static int readListFile(struct listFileData * listOutput,const char * filename) +{ + if (listOutput==0) { return 0; } + + //We now know the number of entries + listOutput->numberOfEntries = slowLineCounter(filename); + + if (listOutput!=0) + { + //Clean up everything! + destroyListFile(listOutput); + } + + + listOutput->entry = (struct listFileEntry *) malloc(sizeof(struct listFileEntry) * listOutput->numberOfEntries ); + if (listOutput->entry==0) { return 0; } + + + char * line = NULL; + size_t len = 0; + ssize_t read = 0; + + FILE * fp = fopen(filename, "r"); + if (fp == NULL) + { return 0; } + + unsigned int entryNumber = 0; + int i=0; + while ((read=getline(&line, &len, fp)) != -1) + { + int stringLength = strlen(line); + + int stringLengthWithoutNull = stringLength-1; + while ( (stringLengthWithoutNull>0) && ( (line[stringLengthWithoutNull]==10) || (line[stringLengthWithoutNull]==13) ) ) + { + line[stringLengthWithoutNull]=0; + stringLengthWithoutNull-=1; + stringLength-=1; + } + + + //fprintf(stderr,"reading line %s (%u)..! \n",line,stringLength); + if (entryNumbernumberOfEntries) + { + listOutput->entry[i].str = (char *) malloc(sizeof(char) * (stringLength+2)); + if (listOutput->entry[i].str!=0) + { + listOutput->entry[i].strLength = stringLength; + strncpy(listOutput->entry[i].str,line,stringLength); + listOutput->entry[i].str[stringLength]=0; //Null termination + //snprintf(listOutput->entry[i].str,stringLength+1,"%s",line); + } + + ++entryNumber; + } + + + i+=1; + } + + fclose(fp); + if (line) + { free(line); } + return 1; +} + + +#ifdef __cplusplus +} +#endif + + + + +#endif + diff --git a/src/MocapNET4/MocapNETLib4/JSON/readModelConfiguration.h b/src/MocapNET4/MocapNETLib4/JSON/readModelConfiguration.h new file mode 100644 index 0000000..84b40b3 --- /dev/null +++ b/src/MocapNET4/MocapNETLib4/JSON/readModelConfiguration.h @@ -0,0 +1,753 @@ +/** @file readModelConfiguration.h + * @brief Parsing the JSON files accompanying models..! + * @author Ammar Qammaz (AmmarkoV) + */ + +#ifndef READ_JSON_CONFIGURATION_H_INCLUDED +#define READ_JSON_CONFIGURATION_H_INCLUDED + + +#ifdef __cplusplus +extern "C" +{ +#endif + + +#include +#include +#include "nxjson.h" +#include "../tools.h" + +#define SMALL_STR 32 +#define BIG_STR 128 +#define MAX_JOINT_NAME 32 +#define MAX_DESCRIPTOR_ELEMENTS 64 +#define MAX_HIERARCHY_ELEMENTS 64 +#define MAX_BANNED_ELEMENTS 16 + +//-------------------------------------- +struct jointName +{ + char joint[MAX_JOINT_NAME]; +}; +//-------------------------------------- +struct jointPair +{ + char jointStart[MAX_JOINT_NAME]; + unsigned int jointStartID; + char jointEnd[MAX_JOINT_NAME]; + unsigned int jointEndID; +}; +//-------------------------------------- +struct jointDescriptorItem +{ + char joint[MAX_JOINT_NAME]; + unsigned int jointID; + char isVirtual; + float xOffset; + float yOffset; + char halfwayFromJointAndThis[MAX_JOINT_NAME]; + unsigned int secondTargetJointID; +}; +//-------------------------------------- +struct jointHierarchyItem +{ + char joint[MAX_JOINT_NAME]; + char inheritNetwork[MAX_JOINT_NAME]; + char parent[MAX_JOINT_NAME]; + unsigned int parentID; + unsigned int importance; + char immuneToSelfOcclusions; +}; +//-------------------------------------- + +struct ModelConfigurationData +{ + float version; + //-------------------------------------- + char backend[SMALL_STR]; + char precision[SMALL_STR]; + char BVH[BIG_STR]; + char outputDirectory[BIG_STR]; + unsigned int veryHighNumberOfEpochs; + unsigned int highNumberOfEpochs; + unsigned int defaultNumberOfEpochs; + unsigned int defaultBatchSize; + float learningRate; + float minEarlyStoppingDelta; + char activationFunction[SMALL_STR]; + float dropoutRate; + float lamda; + char useQuadLoss; + char useSquaredLoss; + unsigned int earlyStoppingPatience; + char rememberWeights; + char rememberConsecutiveWeights; + char useOnlineHardExampleMining; + unsigned int hardMiningEpochs; + unsigned int normalMiningEpochs; + unsigned int groupOutputs; + char ignoreOcclusions; + char NSDMNormalizationMasterSwitch; + char NSDMAlsoUseAlignmentAngles; + unsigned int neuralNetworkDepth; + char EDM; + char eNSRM; + char doPCA[BIG_STR]; + unsigned int PCADimensionsKept; + unsigned int padEnsembleInput; + //-------------------------------------- + struct jointPair normalizedBasedOn[3]; + unsigned int numberOfNormalizedBasedOnRules; + //-------------------------------------- + struct jointPair alignment[3]; + unsigned int numberOfAlignmentRules; + //-------------------------------------- + struct jointDescriptorItem descriptorElements[MAX_DESCRIPTOR_ELEMENTS]; + unsigned int numberOfDescriptorElements; + //-------------------------------------- + struct jointHierarchyItem hierarchyElements[MAX_HIERARCHY_ELEMENTS]; + unsigned int numberOfHierarchyElements; + //-------------------------------------- + struct jointName bannedJoints[MAX_BANNED_ELEMENTS]; + unsigned int numberOfBannedJoints; +}; + + +static void printModelConfigurationData(struct ModelConfigurationData* out) +{ + fprintf(stderr,"Version : %0.2f\n", out->version); + fprintf(stderr,"Backend : %s\n", out->backend); + fprintf(stderr,"Precision : %s\n", out->precision); + fprintf(stderr,"BVH : %s\n", out->BVH); + fprintf(stderr,"outputDirectory : %s\n", out->outputDirectory); + fprintf(stderr,"veryHighNumberOfEpochs : %u\n", out->veryHighNumberOfEpochs); + fprintf(stderr,"highNumberOfEpochs : %u\n", out->highNumberOfEpochs); + fprintf(stderr,"defaultNumberOfEpochs : %u\n", out->defaultNumberOfEpochs); + fprintf(stderr,"defaultBatchSize : %u\n", out->defaultBatchSize); + fprintf(stderr,"learningRate : %f\n", out->learningRate); + fprintf(stderr,"minEarlyStoppingDelta : %f\n", out->minEarlyStoppingDelta); + fprintf(stderr,"activationFunction : %s\n", out->activationFunction); + fprintf(stderr,"dropoutRate : %f\n", out->dropoutRate); + fprintf(stderr,"lamda : %f\n", out->lamda); + fprintf(stderr,"useQuadLoss : %u\n", out->useQuadLoss); + fprintf(stderr,"useSquaredLoss : %u\n", out->useSquaredLoss); + fprintf(stderr,"earlyStoppingPatience : %u\n", out->earlyStoppingPatience); + fprintf(stderr,"rememberWeights : %u\n", out->rememberWeights); + fprintf(stderr,"rememberConsecutiveWeights : %u\n", out->rememberConsecutiveWeights); + fprintf(stderr,"useOnlineHardExampleMining : %u\n", out->useOnlineHardExampleMining); + fprintf(stderr,"hardMiningEpochs : %u\n", out->hardMiningEpochs); + fprintf(stderr,"normalMiningEpochs : %u\n", out->normalMiningEpochs); + fprintf(stderr,"groupOutputs : %u\n", out->groupOutputs); + fprintf(stderr,"ignoreOcclusions : %u\n", out->ignoreOcclusions); + fprintf(stderr,"NSDMNormalizationMasterSwitch : %u\n",out->NSDMNormalizationMasterSwitch); + fprintf(stderr,"NSDMAlsoUseAlignmentAngles : %u\n", out->NSDMAlsoUseAlignmentAngles); + fprintf(stderr,"neuralNetworkDepth : %u\n", out->neuralNetworkDepth); + fprintf(stderr,"EDM : %u\n", out->EDM); + fprintf(stderr,"eNSRM : %u\n", out->eNSRM); + fprintf(stderr,"BVH : %s\n", out->doPCA); + fprintf(stderr,"PCADimensionsKept : %u\n", out->PCADimensionsKept); + fprintf(stderr,"padEnsembleInput : %u\n", out->padEnsembleInput); + + fprintf(stderr,"Normalization Rules : %u\n",out->numberOfNormalizedBasedOnRules); + for (int i=0; inumberOfNormalizedBasedOnRules; i++) + { + fprintf(stderr,"Rule %u : \n",i); + fprintf(stderr," jointStart:%s\n" ,out->normalizedBasedOn[i].jointStart); + fprintf(stderr," jointStartID:%u\n",out->normalizedBasedOn[i].jointStartID); + fprintf(stderr," jointEnd:%s\n" ,out->normalizedBasedOn[i].jointEnd); + fprintf(stderr," jointEndID:%u\n" ,out->normalizedBasedOn[i].jointEndID); + } + + fprintf(stderr,"Alignment Rules : %u\n",out->numberOfAlignmentRules); + for (int i=0; inumberOfAlignmentRules; i++) + { + fprintf(stderr,"Rule %u : \n",i); + fprintf(stderr," jointStart:%s\n" ,out->alignment[i].jointStart); + fprintf(stderr," jointStartID:%u\n",out->alignment[i].jointStartID); + fprintf(stderr," jointEnd:%s\n" ,out->alignment[i].jointEnd); + fprintf(stderr," jointEndID:%u\n" ,out->alignment[i].jointEndID); + } + + fprintf(stderr,"Descriptor Rules : %u\n",out->numberOfDescriptorElements); + for (int i=0; inumberOfDescriptorElements; i++) + { + fprintf(stderr,"Rule %u : \n",i); + fprintf(stderr," joint:%s\n" ,out->descriptorElements[i].joint); + fprintf(stderr," jointID:%u\n" ,out->descriptorElements[i].jointID); + fprintf(stderr," isVirtual:%u\n" ,out->descriptorElements[i].isVirtual); + fprintf(stderr," xOffset:%f\n" ,out->descriptorElements[i].xOffset); + fprintf(stderr," yOffset:%f\n" ,out->descriptorElements[i].yOffset); + fprintf(stderr," halfwayFromJointAndThis:%s\n",out->descriptorElements[i].halfwayFromJointAndThis); + fprintf(stderr," secondTargetJointID:%u\n" ,out->descriptorElements[i].secondTargetJointID); + } + + //TODO POPULATE JOINT ID! + + fprintf(stderr,"Hierarchy Rules : %u\n",out->numberOfHierarchyElements); + for (int i=0; inumberOfHierarchyElements; i++) + { + fprintf(stderr,"Rule %u : \n",i); + fprintf(stderr," joint:%s\n" ,out->hierarchyElements[i].joint); + fprintf(stderr," inheritNetwork:%s\n" ,out->hierarchyElements[i].inheritNetwork); + fprintf(stderr," parent:%s\n" ,out->hierarchyElements[i].parent); + fprintf(stderr," parentID:%u\n" ,out->hierarchyElements[i].parentID); + fprintf(stderr," importance:%u\n" ,out->hierarchyElements[i].importance); + fprintf(stderr," immuneToSelfOcclusions:%u\n" ,out->hierarchyElements[i].immuneToSelfOcclusions); + } + + + fprintf(stderr,"Banned Encoders Rules : %u\n",out->numberOfBannedJoints); + for (int i=0; inumberOfBannedJoints; i++) + { + fprintf(stderr,"Rule %u : \n",i); + fprintf(stderr," joint:%s\n" ,out->bannedJoints[i].joint); + } +} + + + + +static int resolveConfigurationData(struct ModelConfigurationData* config) +{ + if (config!=0) + { //Found a configuration to resolve.. + + printModelConfigurationData(config); + + fprintf(stderr,"Resolving %u NSxM matrix elements..\n",config->numberOfDescriptorElements); + fprintf(stderr,"Using %u hierarchy elements..\n",config->numberOfHierarchyElements); + + //--------------------------------------------------------------------------------------------------------------------------- + //--------------------------------------------------------------------------------------------------------------------------- + //--------------------------------------------------------------------------------------------------------------------------- + //--------------------------------------------------------------------------------------------------------------------------- + //--------------------------------------------------------------------------------------------------------------------------- + + for (unsigned int descID=0; descIDnumberOfDescriptorElements; descID++) + { + //Resolve NSxM to Joint ID + for (unsigned int jointID=0; jointIDnumberOfHierarchyElements; jointID++) + { + fprintf(stderr,"Trying to match %s(%u/%u) with %s(%u/%u)\n",config->descriptorElements[descID].joint,descID,config->numberOfDescriptorElements,config->hierarchyElements[jointID].joint,jointID,config->numberOfHierarchyElements); + + if (strcmp(config->descriptorElements[descID].joint,config->hierarchyElements[jointID].joint)==0) + { + fprintf(stderr,"Found\n"); + config->descriptorElements[descID].jointID = jointID; + break; + } + } + + //Resolve NSxM to Joint ID for halfway + for (int jointID=0; jointIDnumberOfHierarchyElements; jointID++) + { + if (config->descriptorElements[descID].isVirtual==2) + { + fprintf(stderr,"Trying to match %s(%u) with %s(%u)\n",config->descriptorElements[descID].halfwayFromJointAndThis,descID,config->hierarchyElements[jointID].joint,jointID); + + if (strcmp(config->descriptorElements[descID].halfwayFromJointAndThis,config->hierarchyElements[jointID].joint)==0) + { + fprintf(stderr,"Found\n"); + config->descriptorElements[descID].secondTargetJointID = jointID; + break; + } + } + } + } + + //--------------------------------------------------------------------------------------------------------------------------- + //--------------------------------------------------------------------------------------------------------------------------- + //--------------------------------------------------------------------------------------------------------------------------- + //--------------------------------------------------------------------------------------------------------------------------- + //--------------------------------------------------------------------------------------------------------------------------- + + + + + const int INVALID_VALUE=666; + + //Resolve normalized based on order.. + //--------------------------------------------------------------------------------------------------------------------------- + for (unsigned int descID=0; descIDnumberOfNormalizedBasedOnRules; descID++) + { + config->normalizedBasedOn[descID].jointStartID=INVALID_VALUE; + config->normalizedBasedOn[descID].jointEndID =INVALID_VALUE; + } + //--------------------------------------------------------------------------------------------------------------------------- + for (unsigned int descID=0; descIDnumberOfDescriptorElements; descID++) + { + for (int jointID=0; jointIDnumberOfHierarchyElements; jointID++) + { + fprintf(stderr,"Trying to match norm rule %s(%u/%u) with %s(%u/%u)\n",config->descriptorElements[descID].joint,descID,config->numberOfNormalizedBasedOnRules,config->hierarchyElements[jointID].joint,jointID,config->numberOfHierarchyElements); + + if (strcmp(config->normalizedBasedOn[descID].jointStart,config->hierarchyElements[jointID].joint)==0) + { + fprintf(stderr,"Found\n"); + config->normalizedBasedOn[descID].jointStartID = jointID; + } + if (strcmp(config->normalizedBasedOn[descID].jointEnd,config->hierarchyElements[jointID].joint)==0) + { + fprintf(stderr,"Found\n"); + config->normalizedBasedOn[descID].jointEndID = jointID; + } + } + } + //--------------------------------------------------------------------------------------------------------------------------- + for (unsigned int descID=0; descIDnumberOfNormalizedBasedOnRules; descID++) + { + if ( config->normalizedBasedOn[descID].jointStartID == INVALID_VALUE ) + { + fprintf(stderr,"Normalization Rule %u/%u for start of joint is unresolved, stopping..\n",descID,config->numberOfNormalizedBasedOnRules); + return 0; + } + if ( config->normalizedBasedOn[descID].jointEndID == INVALID_VALUE ) + { + fprintf(stderr,"Normalization Rule %u/%u for end of joint is unresolved, stopping..\n",descID,config->numberOfNormalizedBasedOnRules); + return 0; + } + } + //--------------------------------------------------------------------------------------------------------------------------- + + + // Resolve joint alignment.. + //--------------------------------------------------------------------------------------------------------------------------- + for (unsigned int descID=0; descIDnumberOfAlignmentRules; descID++) + { + config->alignment[descID].jointStartID=INVALID_VALUE; + config->alignment[descID].jointEndID =INVALID_VALUE; + } + //--------------------------------------------------------------------------------------------------------------------------- + for (unsigned int descID=0; descIDnumberOfAlignmentRules; descID++) + { + for (int jointID=0; jointIDnumberOfHierarchyElements; jointID++) + { + fprintf(stderr,"Trying to match norm rule %s(%u/%u) with %s(%u/%u)\n",config->descriptorElements[descID].joint,descID,config->numberOfNormalizedBasedOnRules,config->hierarchyElements[jointID].joint,jointID,config->numberOfHierarchyElements); + + if (strcmp(config->alignment[descID].jointStart,config->hierarchyElements[jointID].joint)==0) + { + fprintf(stderr,"Found\n"); + config->alignment[descID].jointStartID = jointID; + } + if (strcmp(config->alignment[descID].jointEnd,config->hierarchyElements[jointID].joint)==0) + { + fprintf(stderr,"Found\n"); + config->alignment[descID].jointEndID = jointID; + } + } + } + //--------------------------------------------------------------------------------------------------------------------------- + for (unsigned int descID=0; descIDnumberOfAlignmentRules; descID++) + { + if ( config->alignment[descID].jointStartID == INVALID_VALUE ) + { + fprintf(stderr,"Normalization Rule %u/%u for start of joint is unresolved, stopping..\n",descID,config->numberOfNormalizedBasedOnRules); + return 0; + } + if ( config->alignment[descID].jointEndID == INVALID_VALUE ) + { + fprintf(stderr,"Normalization Rule %u/%u for end of joint is unresolved, stopping..\n",descID,config->numberOfNormalizedBasedOnRules); + return 0; + } + } + //--------------------------------------------------------------------------------------------------------------------------- + + + + //Resolve parent order for OpenCV drawing.. + for (unsigned int jointID=0; jointIDnumberOfHierarchyElements; jointID++) + { + //Resolve NSxM to Joint ID + for (unsigned int parentID=0; parentIDnumberOfHierarchyElements; parentID++) + { + if (strcmp(config->hierarchyElements[parentID].joint,config->hierarchyElements[jointID].joint)==0) + { + fprintf(stderr,"Found\n"); + config->hierarchyElements[jointID].parentID = jointID; + break; + } + } + } + + + return 1; + } + return 0; +} + + + + + +static int loadModelConfigurationData(struct ModelConfigurationData* out,const char * jsonFilename) +{ + fprintf(stderr,"Loading Configuration file %s ...\n",jsonFilename); + unsigned int inputLength=0; + char* input = readFileToMemory(jsonFilename,&inputLength); + if (input!=0) + { + fprintf(stderr,"Parsing %s ...\n",jsonFilename); + const nx_json* json=nx_json_parse_utf8(input); + const nx_json* rule=0; + const nx_json* item=0; + + //------------------------------------------------------------------- + const nx_json* j = nx_json_get(json,"version"); + if ((j!=0) && (j->type==NX_JSON_STRING)) { out->version = atof(j->text_value); } + //------------------------------------------------------------------- + j = nx_json_get(json,"backend"); + if ((j!=0) && (j->type==NX_JSON_STRING)) { snprintf(out->backend,SMALL_STR,"%s",j->text_value); } + //------------------------------------------------------------------- + j = nx_json_get(json,"precision"); + if ((j!=0) && (j->type==NX_JSON_STRING)) { snprintf(out->precision,SMALL_STR,"%s",j->text_value); } + //------------------------------------------------------------------- + j = nx_json_get(json,"BVH"); + if ((j!=0) && (j->type==NX_JSON_STRING)) { snprintf(out->BVH,BIG_STR,"%s",j->text_value); } + //------------------------------------------------------------------- + j = nx_json_get(json,"OutputDirectory"); + if ((j!=0) && (j->type==NX_JSON_STRING)) { snprintf(out->outputDirectory,BIG_STR,"%s",j->text_value); } + //------------------------------------------------------------------- + j = nx_json_get(json,"veryHighNumberOfEpochs"); + if ((j!=0) && (j->type==NX_JSON_INTEGER)) { out->veryHighNumberOfEpochs = j->int_value; } + //------------------------------------------------------------------- + j = nx_json_get(json,"highNumberOfEpochs"); + if ((j!=0) && (j->type==NX_JSON_INTEGER)) { out->highNumberOfEpochs = j->int_value; } + //------------------------------------------------------------------- + j = nx_json_get(json,"defaultNumberOfEpochs"); + if ((j!=0) && (j->type==NX_JSON_INTEGER)) { out->defaultNumberOfEpochs = j->int_value; } + //------------------------------------------------------------------- + j = nx_json_get(json,"defaultBatchSize"); + if ((j!=0) && (j->type==NX_JSON_INTEGER)) { out->defaultBatchSize = j->int_value; } + //------------------------------------------------------------------- + j = nx_json_get(json,"learningRate"); + if ((j!=0) && (j->type==NX_JSON_DOUBLE)) { out->learningRate = (float) j->dbl_value; } + //------------------------------------------------------------------- + j = nx_json_get(json,"minEarlyStoppingDelta"); + if ((j!=0) && (j->type==NX_JSON_DOUBLE)) { out->minEarlyStoppingDelta = (float) j->dbl_value; } + //------------------------------------------------------------------- + j = nx_json_get(json,"activationFunction"); + if ((j!=0) && (j->type==NX_JSON_STRING)) { snprintf(out->activationFunction,SMALL_STR,"%s",j->text_value); } + //------------------------------------------------------------------- + j = nx_json_get(json,"dropoutRate"); + if ((j!=0) && (j->type==NX_JSON_DOUBLE)) { out->dropoutRate = (float) j->dbl_value; } + //------------------------------------------------------------------- + j = nx_json_get(json,"lamda"); + if ((j!=0) && (j->type==NX_JSON_DOUBLE)) { out->lamda = (float) j->dbl_value; } + //------------------------------------------------------------------- + j = nx_json_get(json,"useQuadLoss"); + if ((j!=0) && (j->type==NX_JSON_INTEGER)) { out->useQuadLoss = j->int_value; } + //------------------------------------------------------------------- + j = nx_json_get(json,"useSquaredLoss"); + if ((j!=0) && (j->type==NX_JSON_INTEGER)) { out->useSquaredLoss = j->int_value; } + //------------------------------------------------------------------- + j = nx_json_get(json,"earlyStoppingPatience"); + if ((j!=0) && (j->type==NX_JSON_INTEGER)) { out->earlyStoppingPatience = j->int_value; } + //------------------------------------------------------------------- + j = nx_json_get(json,"rememberWeights"); + if ((j!=0) && (j->type==NX_JSON_INTEGER)) { out->rememberWeights = j->int_value; } + //------------------------------------------------------------------- + j = nx_json_get(json,"rememberConsecutiveWeights"); + if ((j!=0) && (j->type==NX_JSON_INTEGER)) { out->rememberConsecutiveWeights = j->int_value; } + //------------------------------------------------------------------- + j = nx_json_get(json,"useOnlineHardExampleMining"); + if ((j!=0) && (j->type==NX_JSON_INTEGER)) { out->useOnlineHardExampleMining = j->int_value; } + //------------------------------------------------------------------- + j = nx_json_get(json,"hardMiningEpochs"); + if ((j!=0) && (j->type==NX_JSON_INTEGER)) { out->hardMiningEpochs = j->int_value; } + //------------------------------------------------------------------- + j = nx_json_get(json,"normalMiningEpochs"); + if ((j!=0) && (j->type==NX_JSON_INTEGER)) { out->normalMiningEpochs = j->int_value; } + //------------------------------------------------------------------- + j = nx_json_get(json,"groupOutputs"); + if ((j!=0) && (j->type==NX_JSON_INTEGER)) { out->groupOutputs = j->int_value; } + //------------------------------------------------------------------- + j = nx_json_get(json,"ignoreOcclusions"); + if ((j!=0) && (j->type==NX_JSON_INTEGER)) { out->ignoreOcclusions = j->int_value; } + //------------------------------------------------------------------- + j = nx_json_get(json,"NSDMNormalizationMasterSwitch"); + if ((j!=0) && (j->type==NX_JSON_INTEGER)) { out->NSDMNormalizationMasterSwitch = j->int_value; } + //------------------------------------------------------------------- + j = nx_json_get(json,"NSDMAlsoUseAlignmentAngles"); + if ((j!=0) && (j->type==NX_JSON_INTEGER)) { out->NSDMAlsoUseAlignmentAngles = j->int_value; } + //------------------------------------------------------------------- + j = nx_json_get(json,"neuralNetworkDepth"); + if ((j!=0) && (j->type==NX_JSON_INTEGER)) { out->neuralNetworkDepth = j->int_value; } + //------------------------------------------------------------------- + j = nx_json_get(json,"EDM"); + if ((j!=0) && (j->type==NX_JSON_INTEGER)) { out->EDM = j->int_value; } + //------------------------------------------------------------------- + j = nx_json_get(json,"eNSRM"); + if ((j!=0) && (j->type==NX_JSON_INTEGER)) { out->eNSRM = j->int_value; } + //------------------------------------------------------------------- + j = nx_json_get(json,"doPCA"); + if ((j!=0) && (j->type==NX_JSON_STRING)) { snprintf(out->doPCA,BIG_STR,"%s",j->text_value); } + //------------------------------------------------------------------- + j = nx_json_get(json,"PCADimensionsKept"); + if ((j!=0) && (j->type==NX_JSON_INTEGER)) { out->PCADimensionsKept = j->int_value; } + //------------------------------------------------------------------- + j = nx_json_get(json,"padEnsembleInput"); + if ((j!=0) && (j->type==NX_JSON_INTEGER)) { out->padEnsembleInput = j->int_value; } + //------------------------------------------------------------------- + //------------------------------------------------------------------- + //------------------------------------------------------------------- + fprintf(stderr,"Parsed Initial Variables...\n"); + + //Now to parse Normalization elements.. + //------------------------------------------------------------------- + j = nx_json_get(json,"NormalizeNSDMBasedOn"); + if (j!=0) { + out->numberOfNormalizedBasedOnRules = j->length; + if (out->numberOfNormalizedBasedOnRules>3) + { + fprintf(stderr,"Maximum Accepted Normalization rules are 3!"); + out->numberOfNormalizedBasedOnRules = 3; + } + + for (int i=0; inumberOfNormalizedBasedOnRules; i++) + { + rule = nx_json_item(j,i); + //--------------------------------------------------------------------------------------------------- + item = nx_json_get(rule,"jointStart"); + if ((item!=0) && (item->type==NX_JSON_STRING)) { snprintf(out->normalizedBasedOn[i].jointStart,MAX_JOINT_NAME,"%s",item->text_value); } + item = nx_json_get(rule,"jointStartID"); + if ((item!=0) && (item->type==NX_JSON_INTEGER)) { out->normalizedBasedOn[i].jointStartID = item->int_value; } + item = nx_json_get(rule,"jointEnd"); + if ((item!=0) && (item->type==NX_JSON_STRING)) { snprintf(out->normalizedBasedOn[i].jointEnd,MAX_JOINT_NAME,"%s",item->text_value); } + item = nx_json_get(rule,"jointEndID"); + if ((item!=0) && (item->type==NX_JSON_INTEGER)) { out->normalizedBasedOn[i].jointEndID = item->int_value; } + } + } + //------------------------------------------------------------------- + + + //Now to parse Alignment elements.. + //------------------------------------------------------------------- + j = nx_json_get(json,"Alignment"); + if (j!=0) { + out->numberOfAlignmentRules = j->length; + if (out->numberOfAlignmentRules>3) + { + fprintf(stderr,"Maximum Accepted Alignment rules are 3!"); + out->numberOfAlignmentRules = 3; + } + + for (int i=0; inumberOfAlignmentRules; i++) + { + rule = nx_json_item(j,i); + //--------------------------------------------------------------------------------------------------- + item = nx_json_get(rule,"jointStart"); + if ((item!=0) && (item->type==NX_JSON_STRING)) { snprintf(out->alignment[i].jointStart,MAX_JOINT_NAME,"%s",item->text_value); } + item = nx_json_get(rule,"jointStartID"); + if ((item!=0) && (item->type==NX_JSON_INTEGER)) { out->alignment[i].jointStartID = item->int_value; } + item = nx_json_get(rule,"jointEnd"); + if ((item!=0) && (item->type==NX_JSON_STRING)) { snprintf(out->alignment[i].jointEnd,MAX_JOINT_NAME,"%s",item->text_value); } + item = nx_json_get(rule,"jointEndID"); + if ((item!=0) && (item->type==NX_JSON_INTEGER)) { out->alignment[i].jointEndID = item->int_value; } + } + } + //------------------------------------------------------------------- + + + //Now to parse NSxM elements.. + //------------------------------------------------------------------- + j = nx_json_get(json,"NSDM"); + if (j!=0) { + out->numberOfDescriptorElements = j->length; + if (out->numberOfDescriptorElements>MAX_DESCRIPTOR_ELEMENTS) + { + fprintf(stderr,"Maximum Accepted NSxM rules are %u!",MAX_DESCRIPTOR_ELEMENTS); + out->numberOfDescriptorElements = MAX_DESCRIPTOR_ELEMENTS; + } + + for (int i=0; inumberOfDescriptorElements; i++) + { + rule = nx_json_item(j,i); + //--------------------------------------------------------------------------------------------------- + item = nx_json_get(rule,"joint"); + if ((item!=0) && (item->type==NX_JSON_STRING)) { snprintf(out->descriptorElements[i].joint,MAX_JOINT_NAME,"%s",item->text_value); } + item = nx_json_get(rule,"jointID"); + if ((item!=0) && (item->type==NX_JSON_INTEGER)) { out->descriptorElements[i].jointID = item->int_value; } + item = nx_json_get(rule,"isVirtual"); + if ((item!=0) && (item->type==NX_JSON_INTEGER)) { out->descriptorElements[i].isVirtual = item->int_value; } + item = nx_json_get(rule,"xOffset"); + if ((item!=0) && (item->type==NX_JSON_DOUBLE)) { out->descriptorElements[i].xOffset = (float) item->dbl_value; } + item = nx_json_get(rule,"yOffset"); + if ((item!=0) && (item->type==NX_JSON_DOUBLE)) { out->descriptorElements[i].yOffset = (float) item->dbl_value; } + item = nx_json_get(rule,"halfWayFromThisAnd"); + if ((item!=0) && (item->type==NX_JSON_STRING)) { snprintf(out->descriptorElements[i].halfwayFromJointAndThis,MAX_JOINT_NAME,"%s",item->text_value); } + item = nx_json_get(rule,"secondTargetJointID"); + if ((item!=0) && (item->type==NX_JSON_INTEGER)) { out->descriptorElements[i].secondTargetJointID = item->int_value; } + } + } + //------------------------------------------------------------------- + + + + + //Now to parse Hierarchy elements.. + //------------------------------------------------------------------- + j = nx_json_get(json,"hierarchy"); + if (j!=0) { + out->numberOfHierarchyElements = j->length; + if (out->numberOfHierarchyElements>MAX_HIERARCHY_ELEMENTS) + { + fprintf(stderr,"Maximum Accepted Hierarchy rules are %u!",MAX_HIERARCHY_ELEMENTS); + out->numberOfHierarchyElements = MAX_HIERARCHY_ELEMENTS; + } + + for (int i=0; inumberOfHierarchyElements; i++) + { + rule = nx_json_item(j,i); + //--------------------------------------------------------------------------------------------------- + item = nx_json_get(rule,"joint"); + if ((item!=0) && (item->type==NX_JSON_STRING)) { snprintf(out->hierarchyElements[i].joint,MAX_JOINT_NAME,"%s",item->text_value); } + item = nx_json_get(rule,"inheritNetwork"); + if ((item!=0) && (item->type==NX_JSON_STRING)) { snprintf(out->hierarchyElements[i].inheritNetwork,MAX_JOINT_NAME,"%s",item->text_value); } + item = nx_json_get(rule,"parent"); + if ((item!=0) && (item->type==NX_JSON_STRING)) { snprintf(out->hierarchyElements[i].parent,MAX_JOINT_NAME,"%s",item->text_value); } + item = nx_json_get(rule,"parentID"); + if ((item!=0) && (item->type==NX_JSON_INTEGER)) { out->hierarchyElements[i].parentID = item->int_value; } + item = nx_json_get(rule,"importance"); + if ((item!=0) && (item->type==NX_JSON_INTEGER)) { out->hierarchyElements[i].importance = item->int_value; } + item = nx_json_get(rule,"immuneToSelfOcclusions"); + if ((item!=0) && (item->type==NX_JSON_INTEGER)) { out->hierarchyElements[i].immuneToSelfOcclusions = item->int_value; } + } + } + //------------------------------------------------------------------- + + + + + //Now to parse Hierarchy elements.. + //------------------------------------------------------------------- + j = nx_json_get(json,"banned"); + if (j!=0) { + out->numberOfBannedJoints = j->length; + if (out->numberOfBannedJoints>MAX_BANNED_ELEMENTS) + { + fprintf(stderr,"Maximum Accepted Banned rules are %u!",MAX_BANNED_ELEMENTS); + out->numberOfBannedJoints = MAX_BANNED_ELEMENTS; + } + + for (int i=0; inumberOfBannedJoints; i++) + { + rule = nx_json_item(j,i); + //--------------------------------------------------------------------------------------------------- + item = nx_json_get(rule,"output"); + if ((item!=0) && (item->type==NX_JSON_STRING)) { snprintf(out->bannedJoints[i].joint,MAX_JOINT_NAME,"%s",item->text_value); } + } + } + //------------------------------------------------------------------- + + return resolveConfigurationData(out); + } + return 0; +} + + + + + + + +static int getCompositePoint( + float * iXOut, + float * iYOut, + float * iVisibilityOut, + int * invalidPointOut, + struct ModelConfigurationData* rules, + int i, + float * points2D, + unsigned int points2DLength + ) +{ + if (i > rules->numberOfDescriptorElements) + { + fprintf(stderr,"Point %u out of bounds for descriptor elements\n",i); + return 0; + } + + int invalidPoint = 0; + int numberOfNSDMRules = rules->numberOfDescriptorElements; + int iJointID = rules->descriptorElements[i].jointID; + + if (iJointID*3 > points2DLength) + { + fprintf(stderr,"Unable to get composite point %u\n",i); + return 0; + } + + float iX = points2D[iJointID*3+0]; + float iY = points2D[iJointID*3+1]; + float iVisibility = points2D[iJointID*3+2]; + //--------------------------------------------------------------------------- + //In case we fall through.. + *iXOut = iX; + *iYOut = iY; + *iVisibilityOut = iVisibility; + *invalidPointOut = invalidPoint; + //--------------------------------------------------------------------------- + + + if ((iX!=0) || (iY!=0)) + { + //--------------------------------------------------------------------------- + // Synthetic Points + //--------------------------------------------------------------------------- + + if (rules->descriptorElements[i].isVirtual==1) + { + + iX=iX+rules->descriptorElements[i].xOffset; + iY=iY+rules->descriptorElements[i].yOffset; + } + else + if (rules->descriptorElements[i].isVirtual==2) + { + int secondTargetJointID = rules->descriptorElements[i].secondTargetJointID;// rules['NSDM'][i]['secondTargetJointID'] + float secondTargetX = points2D[secondTargetJointID*3+0]; + float secondTargetY = points2D[secondTargetJointID*3+1]; + if ((secondTargetX==0) && (secondTargetY==0)) + { + invalidPoint=1; + iX=0; + iY=0; + } + else + { + iX=(float) (iX+secondTargetX)/2; + iY=(float) (iY+secondTargetY)/2; + } + } + //--------------------------------------------------------------------------- + + //Return values.. + *iXOut = iX; + *iYOut = iY; + *iVisibilityOut = iVisibility; + *invalidPointOut = invalidPoint; + return 1; + } + return 0; +} + + + + + + + + + + + + + +#ifdef __cplusplus +} +#endif + + + + +#endif diff --git a/src/MocapNET4/MocapNETLib4/NSxM/EDM.h b/src/MocapNET4/MocapNETLib4/NSxM/EDM.h new file mode 100644 index 0000000..65e8e84 --- /dev/null +++ b/src/MocapNET4/MocapNETLib4/NSxM/EDM.h @@ -0,0 +1,103 @@ +/** @file EDM.h + * @brief An implementation of an EDM descriptor + * @author Ammar Qammaz (AmmarkoV) + */ + +#ifndef EDM_H_INCLUDED +#define EDM_H_INCLUDED + + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "../JSON/readModelConfiguration.h" +#include "NSxM.h" +#include + + +static int countEDMElements(int numberOfJointRules) +{ + int count = 0; + for (int i=0; ij) + { + count+=1; + } + } + } + return count; +} + +/** @brief This function returns the euclidean distance between two input 2D joints and zero if either of them is invalid*/ +static float getJoint2DDistanceEDM(float aX,float aY,float bX,float bY) +{ + if ( ((aX==0) && (aY==0)) || ((bX==0) && (bY==0)) ) + { + return 0.0; + } + //------------------------- + float xDistance=(float) bX-aX; + float yDistance=(float) bY-aY; + float result = sqrt( (xDistance*xDistance) + (yDistance*yDistance) ); + + if (result!=result) + { + fprintf(stderr,"getJoint2DDistanceEDM yielded NaN..\n"); + return 0.0; + } + + return result; +} + +static int appendEDMElements( + float * input2DJoints, + unsigned int input2DJointsLength, + float * output, + struct ModelConfigurationData* rules + ) +{ + int numberOfJointRules = rules->numberOfDescriptorElements; + //---------------------- + float iX=0.0,iY=0.0,iVisibility=0.0; + int iInvalidPoint=0; + //---------------------- + float jX=0.0,jY=0.0,jVisibility=0.0; + int jInvalidPoint=0; + //---------------------- + int count = 0; + for (int i=0; ij) + { + getCompositePoint(&jX,&jY,&jVisibility,&jInvalidPoint,rules,j,input2DJoints,input2DJointsLength); + if ( (iInvalidPoint) && (jInvalidPoint) ) //<- Why is this AND and not OR ? also in EDM.py code + { + output[count] = 0.0; + } else + { + output[count] = getJoint2DDistanceEDM(iX,iY,jX,jY); + } + count+=1; + } + } + } + + return count; +} + +#ifdef __cplusplus +} +#endif + + + + +#endif diff --git a/src/MocapNET4/MocapNETLib4/NSxM/NSDM.h b/src/MocapNET4/MocapNETLib4/NSxM/NSDM.h new file mode 100644 index 0000000..7d044b7 --- /dev/null +++ b/src/MocapNET4/MocapNETLib4/NSxM/NSDM.h @@ -0,0 +1,45 @@ +/** @file NSDM.h + * @brief An implementation of an NSRM descriptor + * @author Ammar Qammaz (AmmarkoV) + */ + +#ifndef NSDM_H_INCLUDED +#define NSDM_H_INCLUDED + + +#ifdef __cplusplus +extern "C" +{ +#endif + + +#include "../JSON/readModelConfiguration.h" +#include "NSxM.h" +#include + +static int countNSDMElements(int numberOfJointRules) +{ + return 2*numberOfJointRules*numberOfJointRules; +} + + + +static int appendNSDMElements( + float * input2DJoints, + struct descriptor * output, + int numberOfJointRules + ) +{ + return 0; +} + + + +#ifdef __cplusplus +} +#endif + + + + +#endif diff --git a/src/MocapNET4/MocapNETLib4/NSxM/NSRM.h b/src/MocapNET4/MocapNETLib4/NSxM/NSRM.h new file mode 100644 index 0000000..5f74405 --- /dev/null +++ b/src/MocapNET4/MocapNETLib4/NSxM/NSRM.h @@ -0,0 +1,343 @@ +/** @file NSRM.h + * @brief An implementation of an NSRM descriptor + * @author Ammar Qammaz (AmmarkoV) + */ + +#ifndef NSRM_H_INCLUDED +#define NSRM_H_INCLUDED + + +#ifdef __cplusplus +extern "C" +{ +#endif + + +#include + + +const float goFromRadToDegrees=(float) 180.0 / M_PI; +const float goFromDegreesToRad=(float) M_PI / 180.0; + +/** @brief This function returns the euclidean distance between two input 2D joints and zero if either of them is invalid*/ +static float getJoint2DDistance_NSRM(float aX,float aY,float bX,float bY) +{ + float xDistance=(float) bX-aX; + float yDistance=(float) bY-aY; + return (float) sqrt( (xDistance*xDistance) + (yDistance*yDistance) ); +} + + +static float getAngleToAlignToZero_NSRM(float aX,float aY,float bX,float bY) +{ + if ( (aX==bX) && (aY==bY) ) { return 0; } + + + //Bigger magnitudes.. + aX=100*aX; + aY=100*aY; + bX=100*bX; + bY=100*bY; + + //We have points a, b and c and we want to calculate angle b + float lengthBetweenAAndB = getJoint2DDistance_NSRM(aX,aY,bX,bY); + + + //We align vertically.. , Point C is B offset in Y direction + float cX = bX; + float cY = bY - lengthBetweenAAndB; + + //fprintf(stderr,"We want to align A(%0.2f,%0.2f) to C(%0.2f,%0.2f) with pivot B(%0.2f,%0.2f)\n",aX,aY,cX,cY,bX,bY); + //fprintf(stderr,"length AB = %0.2f\n",lengthBetweenAAndB); + //fprintf(stderr,"bY = %0.2f\n",bY); + //fprintf(stderr,"cY = %0.2f = %0.2f - %0.2f\n",cY,bY,lengthBetweenAAndB); + + + //Calulate vector a->b + float abX = bX - aX; + float abY = bY - aY; + + //calculate vector c->b + float cbX = bX - cX; + float cbY = bY - cY; + + + float dot = ((abX * cbX) + (abY * cbY)); // dot product + float cross = ((abX * cbY) - (abY * cbX)); // cross product + + float alpha = atan2(cross,dot); + + //fprintf(stderr,"Angle is %0.2f rad or %0.2f degrees \n",alpha,alpha*goFromRadToDegrees); + return (float) alpha;// * goFromRadToDegrees ; +} + + + +static float getAngleToAlignToZeroJoints_NSRM(float * positions,unsigned int centerJoint,unsigned int referenceJoint) +{ + //We have points a, b and c and we want to calculate angle b + float aX= positions[referenceJoint*3+0]; + float aY= positions[referenceJoint*3+1]; + + float bX= positions[centerJoint*3+0]; + float bY= positions[centerJoint*3+1]; + + return getAngleToAlignToZero_NSRM(aX,aY,bX,bY); +} + + +/* +static int rotate2DPointsBasedOnJointAsCenter_NSRM(float * positions,int positionsSize,float angle,unsigned int centerJoint) +{ + if (positionsSize%3!=0) + { + fprintf(stderr,RED "rotate2DPointsBasedOnJointAsCenter: incorrect positions.. \n" NORMAL); + return 0; + } + + if (positionsSize<=centerJoint*3) + { + fprintf(stderr,RED "rotate2DPointsBasedOnJointAsCenter: centerJoint out of bounds.. \n" NORMAL); + return 0; + } + + float s = sin((float) angle * goFromDegreesToRad); + float c = cos((float) angle * goFromDegreesToRad); + //================================================= + float cx = positions[centerJoint*3+0]; + float cy = positions[centerJoint*3+1]; + float cVisibility = positions[centerJoint*3+2]; + //================================================= + + if (cVisibility==0.0) + { + fprintf(stderr,RED "rotate2DPointsBasedOnJointAsCenter: cannot work without pivot joint.. \n" NORMAL); + return 0; + } + + for (unsigned int jID=0; jID ",jX,jY,cx,cy,angle); + + //Translate point back to origin: + jX -= cx; + jY -= cy; + + //Rotate point + float xnew = jX * c - jY * s; + float ynew = jX * s + jY * c; + + //Translate point back: + positions[jID*3+0] = xnew + cx; + positions[jID*3+1] = ynew + cy; + + //fprintf(stderr,"%0.2f,%0.2f\n",positions[jID*3+0],positions[jID*3+1]); + } + + + return 1; +}*/ + + + +static int countNSRMElements(int numberOfJointRules) +{ + return numberOfJointRules*numberOfJointRules; +} + + + + +static int rotate2DPointsBasedOnJointAsCenter_NSRM(float * positions,unsigned int positionsLength,float angle,unsigned int centerJoint) +{ + if (positionsLength%3!=0) + { + fprintf(stderr,RED "rotate2DPointsBasedOnJointAsCenter: incorrect positions.. \n" NORMAL); + return 0; + } + + if (positionsLength<=centerJoint*3) + { + fprintf(stderr,RED "rotate2DPointsBasedOnJointAsCenter: centerJoint out of bounds.. \n" NORMAL); + return 0; + } + + float s = sin((float) angle * goFromDegreesToRad ); + float c = cos((float) angle * goFromDegreesToRad ); + + float cx=positions[centerJoint*3+0]; + float cy=positions[centerJoint*3+1]; + float cVisibility=positions[centerJoint*3+2]; + + if (cVisibility==0.0) + { + fprintf(stderr,RED "rotate2DPointsBasedOnJointAsCenter: cannot work with invisible pivot joint.. \n" NORMAL); + return 0; + } + + for (unsigned int jID=0; jID ",jX,jY,cx,cy,angle); + + //Translate point back to origin: + jX -= cx; + jY -= cy; + + //Rotate point + float xnew = jX * c - jY * s; + float ynew = jX * s + jY * c; + + //Translate point back: + positions[jID*3+0] = xnew + cx; + positions[jID*3+1] = ynew + cy; + + //fprintf(stderr,"%0.2f,%0.2f\n",positions[jID*3+0],positions[jID*3+1]); + } + + + return 1; +} + + + +static float performNSRMAlignment(float * input2DJoints, + unsigned int input2DJointsLength, + struct ModelConfigurationData* rules) +{ + //Enforce alignment rule.. + //------------------------------------------------------------ + int pivotPoint = rules->alignment[0].jointStartID;// rules['Alignment'][0]['jointStartID'] + int referencePoint = rules->alignment[0].jointEndID; // rules['Alignment'][0]['jointEndID'] + //------------------------------------------------------------ + float pivotX = input2DJoints[pivotPoint*3+0]; + float pivotY = input2DJoints[pivotPoint*3+1]; + float pivotVisibility = input2DJoints[pivotPoint*3+2]; + + float referenceX = input2DJoints[referencePoint*3+0]; + float referenceY = input2DJoints[referencePoint*3+1]; + float referenceVisibility = input2DJoints[referencePoint*3+2]; + //------------------------------------------------------------ + + float angleToRotate = 0.0; + if ((pivotVisibility!=0) && (referenceVisibility!=0)) + { + angleToRotate = getAngleToAlignToZero_NSRM(pivotX,pivotY,referenceX,referenceY); + rotate2DPointsBasedOnJointAsCenter_NSRM(input2DJoints,input2DJointsLength,angleToRotate,pivotPoint); + } + + return angleToRotate; +} + + +static int appendNSRMElements( + float * input2DJoints, + unsigned int input2DJointsLength, + float * output, + struct ModelConfigurationData* rules, + float angleUsedToRotateInput + ) +{ + int numberOfJointRules = rules->numberOfDescriptorElements; + //---------------------- + float iX=0.0,iY=0.0,iVisibility=0.0; + int iInvalidPoint=0; + //---------------------- + float jX=0.0,jY=0.0,jVisibility=0.0; + int jInvalidPoint=0; + //---------------------- + + //----------------------------------------------------------------------------------------------------- + // ..Main NSRM parameters .. + //----------------------------------------------------------------------------------------------------- + int count = 0; + for (int i=0; ieNSRM) + { + count=0; + int iJointID = rules->descriptorElements[0].jointID; + iX = input2DJoints[iJointID*3+0]; + iY = input2DJoints[iJointID*3+1]; + //getCompositePoint(&iX,&iY,&iVisibility,&iInvalidPoint,rules,i,input2DJoints,input2DJointsLength); + for (int i=0; i0) + { + int jJointID = rules->descriptorElements[j].jointID; + jX = input2DJoints[jJointID*3+0]; + jY = input2DJoints[jJointID*3+1]; + //getCompositePoint(&jX,&jY,&jVisibility,&jInvalidPoint,rules,j,input2DJoints,input2DJointsLength); + output[count] = getJoint2DDistance_NSRM(iX,iY,jX,jY); + } + } + count+=1; + } + } + } + + + //NSDM Normalization rule does not apply on NSRM + //SKIPPED + //----------------------------------------------------------------------------------------------------- + + //Enforce alignment rule.. + //------------------------------------------------------------ + int iJointID = rules->alignment[0].jointStartID;// rules['Alignment'][0]['jointStartID'] + int jJointID = rules->alignment[0].jointEndID; // rules['Alignment'][0]['jointEndID'] + //------------------------------------------------------------ + float aX = input2DJoints[iJointID*3+0]; + float aY = input2DJoints[iJointID*3+1]; + float bX = input2DJoints[jJointID*3+0]; + float bY = input2DJoints[jJointID*3+1]; + //------------------------------------------------------------ + float alignmentAngle = getAngleToAlignToZero_NSRM(aX,aY,bX,bY); + //fprintf(stderr,"ALIGNMENT %u %u %f",iJointID,jJointID,alignmentAngle); + for (int i=0; i +#include +#include +#include + +#include "../JSON/readListFile.h" +#include "../JSON/readModelConfiguration.h" + +#include "../PCA/PCA.h" + +#include "../IO/inputRouting.h" + +#include "EDM.h" +#include "NSDM.h" +#include "NSRM.h" + +#define NORMAL "\033[0m" +#define BLACK "\033[30m" /* Black */ +#define RED "\033[31m" /* Red */ +#define GREEN "\033[32m" /* Green */ +#define YELLOW "\033[33m" /* Yellow */ + +struct label +{ + char * str; + unsigned int length; +}; + +struct descriptor +{ + float * routedInput; + int routedInputLength; + unsigned int numberOfElements; + unsigned int maxNumberOfElements; + struct label * labels; + float * values; +}; + + +static int destroyDescriptor( + struct descriptor * output + ) +{ + //----------------------------------------------- + if (output->values!=0) + { free(output->values); } + //----------------------------------------------- + for (int i=0; imaxNumberOfElements; i++) + { + if (output->labels[i].str!=0) + { + free(output->labels[i].str); + output->labels[i].str = 0; + output->labels[i].length = 0; + } + } + //----------------------------------------------- + return 1; +} + + + + +static int createDescriptor( + struct descriptor * output, + float * data2DRaw, + unsigned int data2DRawLength, + struct inputRouting * route, + struct ModelConfigurationData * config, + struct PCAData * pca, + struct listFileData * listInputJoints, + struct listFileData * listOutput + ) +{ + fprintf(stderr,GREEN "createDescriptor..\n" NORMAL); + if (output==0) { return 0; } + //--------------------------------------------------------- + int useEDM = config->EDM; + int useNSRM = config->eNSRM; + int alignPoints = config->NSDMAlsoUseAlignmentAngles; + int usePCADimensions = config->PCADimensionsKept; + int numberOfJointRules = config->numberOfDescriptorElements; + //--------------------------------------------------------- + + + if (output->routedInput==0) + { + output->routedInput = (float *) malloc(sizeof(float) * route->numberOfRoutingRules * 3); + output->routedInputLength = route->numberOfRoutingRules * 3; + } + + //-------------------------------------- + if ( + !routeInput( + output->routedInput, + &output->routedInputLength, + config, + route, + data2DRaw, + data2DRawLength + ) + ) + { + fprintf(stderr,RED "Unable to route 2D input..\n" NORMAL); + return 0; + } + //-------------------------------------- + float * data2D = output->routedInput; + unsigned int data2DLength = output->routedInputLength; + //-------------------------------------- + + if (listInputJoints->numberOfEntries!=data2DLength) + { + fprintf(stderr,RED "Mismatch of Input 2D Points Vs Neural Network 2D Joints..\n" NORMAL); + } + + for (int i=0; ioutput->maxNumberOfElements) + { + //Space already allocated but not enough, + //Destroy anything previously allocated + destroyDescriptor(output); + } + //--------------------------------------------------------- + if (output->maxNumberOfElements==0) + { //If operating on a newly allocated descriptor + output->maxNumberOfElements = neededSpace; + //--------------------------------------------------------- + output->values = (float*) malloc( sizeof(float) * output->maxNumberOfElements ); + if (output->values!=0) + { memset(output->values,0,sizeof(float) * output->maxNumberOfElements); } + //--------------------------------------------------------- + output->labels = (struct label *) malloc( sizeof(struct label) * output->maxNumberOfElements ); + if (output->labels!=0) + { memset(output->labels,0,sizeof(struct label) * output->maxNumberOfElements); } + //We have a clean output descriptor + } + //--------------------------------------------------------- + + + float * outputInPosition = output->values; + + //Copy all 2D Data this must happen before the alignment!.. + fprintf(stderr,"Copying %u 2D coordinates \n",data2DLength); + for (int i=0; ieNSRM)//alignPoints) + { + fprintf(stderr,"Aligning Points\n"); + //Do point alignment here.. + angleToRotate = performNSRMAlignment(data2D,data2DLength,config); + fprintf(stderr,YELLOW "Correcting skeleton by rotating it %0.2f degrees\n" NORMAL,angleToRotate); + } + + /* + fprintf(stderr," 2d = list()\n"); + for (int i=0; ivalues[i],i); + }*/ + + + if (useEDM) + { + fprintf(stderr,"Using EDM\n"); + //--------------------------------------------------------- + int EDMElements = appendEDMElements( + data2D, + data2DLength, + outputInPosition, + config + ); + outputInPosition += EDMElements; + //--------------------------------------------------------- + /* + fprintf(stderr," EDM = list()\n"); + for (int i=0; ivalues[i],i); + }*/ + } + + + if (useNSRM) + { + fprintf(stderr,"Using NSRM\n"); + //--------------------------------------------------------- + int NSRMElements = appendNSRMElements( + data2D, + data2DLength, + outputInPosition, + config, + angleToRotate + ); + outputInPosition += NSRMElements; + //--------------------------------------------------------- + /* + fprintf(stderr," NSRM = list()\n"); + for (int i=0; ivalues[i],i); + }*/ + } + output->values[169] += angleToRotate; + + fprintf(stderr,"Descriptor yielded %u elements : ",neededSpace); + output->numberOfElements = neededSpace; + for (int i=0; ivalues[i]>10.0) { fprintf(stderr,RED); } + fprintf(stderr,"%0.2f(#%u) ",output->values[i],i); + if (output->values[i]>10.0) { fprintf(stderr,NORMAL); } + } + fprintf(stderr,"\n"); + + if (config->doPCA) + { + /* + fprintf(stderr," PCA = list()\n"); + for (int i=0; ivalues[i],i); + }*/ + int finalOutputSize = config->PCADimensionsKept; + doPCATransform( + output->values, + &finalOutputSize, + pca, + output->values, + neededSpace, + config->PCADimensionsKept + ); + + fprintf(stderr,"PCA (mean %0.2f/std %0.2f) packed descriptor yielded %u elements : \n",pca->mean,pca->std,finalOutputSize); + for (int i=0; ivalues[i]); + } + output->numberOfElements = finalOutputSize; + } + + return 1; +} + +/** @brief This function returns the euclidean distance between two input 2D joints and zero if either of them is invalid*/ +static float getJoint2DDistanceNSxM(float* in,int jointA,int jointB) +{ + float aX=in[jointA*3+0]; + float aY=in[jointA*3+1]; + float bX=in[jointB*3+0]; + float bY=in[jointB*3+1]; + if ( ((aX==0) && (aY==0)) || ((bX==0) && (bY==0)) ) { + return 0.0; + } + + + float xDistance=(float) bX-aX; + float yDistance=(float) bY-aY; + return sqrt( (xDistance*xDistance) + (yDistance*yDistance) ); +} + + + + +/** @brief This is an array of names for all uncompressed 2D inputs expected. */ +static const unsigned int mocapNET_InputLength_WithoutNSDM_upperbody = 33; + +/** @brief Use rich diagonal, part of networks after 31-01-2021 */ +static const unsigned int richDiagonal_upperbody = 1; + +/** @brief An array of strings that contains the label for each expected input. */ +static const char * mocapNET_upperbody[] = +{ + "2DX_hip", //0 + "2DY_hip", //1 + "visible_hip", //2 + "2DX_neck", //3 + "2DY_neck", //4 + "visible_neck", //5 + "2DX_head", //6 + "2DY_head", //7 + "visible_head", //8 + "2DX_EndSite_eye.l", //9 + "2DY_EndSite_eye.l", //10 + "visible_EndSite_eye.l", //11 + "2DX_EndSite_eye.r", //12 + "2DY_EndSite_eye.r", //13 + "visible_EndSite_eye.r", //14 + "2DX_rshoulder", //15 + "2DY_rshoulder", //16 + "visible_rshoulder", //17 + "2DX_relbow", //18 + "2DY_relbow", //19 + "visible_relbow", //20 + "2DX_rhand", //21 + "2DY_rhand", //22 + "visible_rhand", //23 + "2DX_lshoulder", //24 + "2DY_lshoulder", //25 + "visible_lshoulder", //26 + "2DX_lelbow", //27 + "2DY_lelbow", //28 + "visible_lelbow", //29 + "2DX_lhand", //30 + "2DY_lhand", //31 + "visible_lhand", //32 +//This is where regular input ends and the NSDM data kicks in.. + "angleUsedFor2DRotation_0", //33 + "hipY-EndSite_eye.rY-Angle", //34 + "hipY-EndSite_eye.lY-Angle", //35 + "hipY-neckY-Angle", //36 + "hipY-rshoulderY-Angle", //37 + "hipY-halfway_rshoulder_and_relbowY-Angle", //38 + "hipY-relbowY-Angle", //39 + "hipY-halfway_relbow_and_rhandY-Angle", //40 + "hipY-rhandY-Angle", //41 + "hipY-virtual_hip_x_minus_0_15_y_minus_0_15Y-Angle", //42 + "hipY-virtual_hip_x_plus0_15_y_minus_0_15Y-Angle", //43 + "hipY-lshoulderY-Angle", //44 + "hipY-halfway_lshoulder_and_lelbowY-Angle", //45 + "hipY-lelbowY-Angle", //46 + "hipY-halfway_lelbow_and_lhandY-Angle", //47 + "hipY-lhandY-Angle", //48 + "hipY-halfway_neck_and_hipY-Angle", //49 + "EndSite_eye.rY-hipY-Angle", //50 + "angleUsedFor2DRotation_1", //51 + "EndSite_eye.rY-EndSite_eye.lY-Angle", //52 + "EndSite_eye.rY-neckY-Angle", //53 + "EndSite_eye.rY-rshoulderY-Angle", //54 + "EndSite_eye.rY-halfway_rshoulder_and_relbowY-Angle", //55 + "EndSite_eye.rY-relbowY-Angle", //56 + "EndSite_eye.rY-halfway_relbow_and_rhandY-Angle", //57 + "EndSite_eye.rY-rhandY-Angle", //58 + "EndSite_eye.rY-virtual_hip_x_minus_0_15_y_minus_0_15Y-Angle", //59 + "EndSite_eye.rY-virtual_hip_x_plus0_15_y_minus_0_15Y-Angle", //60 + "EndSite_eye.rY-lshoulderY-Angle", //61 + "EndSite_eye.rY-halfway_lshoulder_and_lelbowY-Angle", //62 + "EndSite_eye.rY-lelbowY-Angle", //63 + "EndSite_eye.rY-halfway_lelbow_and_lhandY-Angle", //64 + "EndSite_eye.rY-lhandY-Angle", //65 + "EndSite_eye.rY-halfway_neck_and_hipY-Angle", //66 + "EndSite_eye.lY-hipY-Angle", //67 + "EndSite_eye.lY-EndSite_eye.rY-Angle", //68 + "angleUsedFor2DRotation_2", //69 + "EndSite_eye.lY-neckY-Angle", //70 + "EndSite_eye.lY-rshoulderY-Angle", //71 + "EndSite_eye.lY-halfway_rshoulder_and_relbowY-Angle", //72 + "EndSite_eye.lY-relbowY-Angle", //73 + "EndSite_eye.lY-halfway_relbow_and_rhandY-Angle", //74 + "EndSite_eye.lY-rhandY-Angle", //75 + "EndSite_eye.lY-virtual_hip_x_minus_0_15_y_minus_0_15Y-Angle", //76 + "EndSite_eye.lY-virtual_hip_x_plus0_15_y_minus_0_15Y-Angle", //77 + "EndSite_eye.lY-lshoulderY-Angle", //78 + "EndSite_eye.lY-halfway_lshoulder_and_lelbowY-Angle", //79 + "EndSite_eye.lY-lelbowY-Angle", //80 + "EndSite_eye.lY-halfway_lelbow_and_lhandY-Angle", //81 + "EndSite_eye.lY-lhandY-Angle", //82 + "EndSite_eye.lY-halfway_neck_and_hipY-Angle", //83 + "neckY-hipY-Angle", //84 + "neckY-EndSite_eye.rY-Angle", //85 + "neckY-EndSite_eye.lY-Angle", //86 + "angleUsedFor2DRotation_3", //87 + "neckY-rshoulderY-Angle", //88 + "neckY-halfway_rshoulder_and_relbowY-Angle", //89 + "neckY-relbowY-Angle", //90 + "neckY-halfway_relbow_and_rhandY-Angle", //91 + "neckY-rhandY-Angle", //92 + "neckY-virtual_hip_x_minus_0_15_y_minus_0_15Y-Angle", //93 + "neckY-virtual_hip_x_plus0_15_y_minus_0_15Y-Angle", //94 + "neckY-lshoulderY-Angle", //95 + "neckY-halfway_lshoulder_and_lelbowY-Angle", //96 + "neckY-lelbowY-Angle", //97 + "neckY-halfway_lelbow_and_lhandY-Angle", //98 + "neckY-lhandY-Angle", //99 + "neckY-halfway_neck_and_hipY-Angle", //100 + "rshoulderY-hipY-Angle", //101 + "rshoulderY-EndSite_eye.rY-Angle", //102 + "rshoulderY-EndSite_eye.lY-Angle", //103 + "rshoulderY-neckY-Angle", //104 + "angleUsedFor2DRotation_4", //105 + "rshoulderY-halfway_rshoulder_and_relbowY-Angle", //106 + "rshoulderY-relbowY-Angle", //107 + "rshoulderY-halfway_relbow_and_rhandY-Angle", //108 + "rshoulderY-rhandY-Angle", //109 + "rshoulderY-virtual_hip_x_minus_0_15_y_minus_0_15Y-Angle", //110 + "rshoulderY-virtual_hip_x_plus0_15_y_minus_0_15Y-Angle", //111 + "rshoulderY-lshoulderY-Angle", //112 + "rshoulderY-halfway_lshoulder_and_lelbowY-Angle", //113 + "rshoulderY-lelbowY-Angle", //114 + "rshoulderY-halfway_lelbow_and_lhandY-Angle", //115 + "rshoulderY-lhandY-Angle", //116 + "rshoulderY-halfway_neck_and_hipY-Angle", //117 + "halfway_rshoulder_and_relbowY-hipY-Angle", //118 + "halfway_rshoulder_and_relbowY-EndSite_eye.rY-Angle", //119 + "halfway_rshoulder_and_relbowY-EndSite_eye.lY-Angle", //120 + "halfway_rshoulder_and_relbowY-neckY-Angle", //121 + "halfway_rshoulder_and_relbowY-rshoulderY-Angle", //122 + "angleUsedFor2DRotation_5", //123 + "halfway_rshoulder_and_relbowY-relbowY-Angle", //124 + "halfway_rshoulder_and_relbowY-halfway_relbow_and_rhandY-Angle", //125 + "halfway_rshoulder_and_relbowY-rhandY-Angle", //126 + "halfway_rshoulder_and_relbowY-virtual_hip_x_minus_0_15_y_minus_0_15Y-Angle", //127 + "halfway_rshoulder_and_relbowY-virtual_hip_x_plus0_15_y_minus_0_15Y-Angle", //128 + "halfway_rshoulder_and_relbowY-lshoulderY-Angle", //129 + "halfway_rshoulder_and_relbowY-halfway_lshoulder_and_lelbowY-Angle", //130 + "halfway_rshoulder_and_relbowY-lelbowY-Angle", //131 + "halfway_rshoulder_and_relbowY-halfway_lelbow_and_lhandY-Angle", //132 + "halfway_rshoulder_and_relbowY-lhandY-Angle", //133 + "halfway_rshoulder_and_relbowY-halfway_neck_and_hipY-Angle", //134 + "relbowY-hipY-Angle", //135 + "relbowY-EndSite_eye.rY-Angle", //136 + "relbowY-EndSite_eye.lY-Angle", //137 + "relbowY-neckY-Angle", //138 + "relbowY-rshoulderY-Angle", //139 + "relbowY-halfway_rshoulder_and_relbowY-Angle", //140 + "angleUsedFor2DRotation_6", //141 + "relbowY-halfway_relbow_and_rhandY-Angle", //142 + "relbowY-rhandY-Angle", //143 + "relbowY-virtual_hip_x_minus_0_15_y_minus_0_15Y-Angle", //144 + "relbowY-virtual_hip_x_plus0_15_y_minus_0_15Y-Angle", //145 + "relbowY-lshoulderY-Angle", //146 + "relbowY-halfway_lshoulder_and_lelbowY-Angle", //147 + "relbowY-lelbowY-Angle", //148 + "relbowY-halfway_lelbow_and_lhandY-Angle", //149 + "relbowY-lhandY-Angle", //150 + "relbowY-halfway_neck_and_hipY-Angle", //151 + "halfway_relbow_and_rhandY-hipY-Angle", //152 + "halfway_relbow_and_rhandY-EndSite_eye.rY-Angle", //153 + "halfway_relbow_and_rhandY-EndSite_eye.lY-Angle", //154 + "halfway_relbow_and_rhandY-neckY-Angle", //155 + "halfway_relbow_and_rhandY-rshoulderY-Angle", //156 + "halfway_relbow_and_rhandY-halfway_rshoulder_and_relbowY-Angle", //157 + "halfway_relbow_and_rhandY-relbowY-Angle", //158 + "angleUsedFor2DRotation_7", //159 + "halfway_relbow_and_rhandY-rhandY-Angle", //160 + "halfway_relbow_and_rhandY-virtual_hip_x_minus_0_15_y_minus_0_15Y-Angle", //161 + "halfway_relbow_and_rhandY-virtual_hip_x_plus0_15_y_minus_0_15Y-Angle", //162 + "halfway_relbow_and_rhandY-lshoulderY-Angle", //163 + "halfway_relbow_and_rhandY-halfway_lshoulder_and_lelbowY-Angle", //164 + "halfway_relbow_and_rhandY-lelbowY-Angle", //165 + "halfway_relbow_and_rhandY-halfway_lelbow_and_lhandY-Angle", //166 + "halfway_relbow_and_rhandY-lhandY-Angle", //167 + "halfway_relbow_and_rhandY-halfway_neck_and_hipY-Angle", //168 + "rhandY-hipY-Angle", //169 + "rhandY-EndSite_eye.rY-Angle", //170 + "rhandY-EndSite_eye.lY-Angle", //171 + "rhandY-neckY-Angle", //172 + "rhandY-rshoulderY-Angle", //173 + "rhandY-halfway_rshoulder_and_relbowY-Angle", //174 + "rhandY-relbowY-Angle", //175 + "rhandY-halfway_relbow_and_rhandY-Angle", //176 + "angleUsedFor2DRotation_8", //177 + "rhandY-virtual_hip_x_minus_0_15_y_minus_0_15Y-Angle", //178 + "rhandY-virtual_hip_x_plus0_15_y_minus_0_15Y-Angle", //179 + "rhandY-lshoulderY-Angle", //180 + "rhandY-halfway_lshoulder_and_lelbowY-Angle", //181 + "rhandY-lelbowY-Angle", //182 + "rhandY-halfway_lelbow_and_lhandY-Angle", //183 + "rhandY-lhandY-Angle", //184 + "rhandY-halfway_neck_and_hipY-Angle", //185 + "virtual_hip_x_minus_0_15_y_minus_0_15Y-hipY-Angle", //186 + "virtual_hip_x_minus_0_15_y_minus_0_15Y-EndSite_eye.rY-Angle", //187 + "virtual_hip_x_minus_0_15_y_minus_0_15Y-EndSite_eye.lY-Angle", //188 + "virtual_hip_x_minus_0_15_y_minus_0_15Y-neckY-Angle", //189 + "virtual_hip_x_minus_0_15_y_minus_0_15Y-rshoulderY-Angle", //190 + "virtual_hip_x_minus_0_15_y_minus_0_15Y-halfway_rshoulder_and_relbowY-Angle", //191 + "virtual_hip_x_minus_0_15_y_minus_0_15Y-relbowY-Angle", //192 + "virtual_hip_x_minus_0_15_y_minus_0_15Y-halfway_relbow_and_rhandY-Angle", //193 + "virtual_hip_x_minus_0_15_y_minus_0_15Y-rhandY-Angle", //194 + "angleUsedFor2DRotation_9", //195 + "virtual_hip_x_minus_0_15_y_minus_0_15Y-virtual_hip_x_plus0_15_y_minus_0_15Y-Angle", //196 + "virtual_hip_x_minus_0_15_y_minus_0_15Y-lshoulderY-Angle", //197 + "virtual_hip_x_minus_0_15_y_minus_0_15Y-halfway_lshoulder_and_lelbowY-Angle", //198 + "virtual_hip_x_minus_0_15_y_minus_0_15Y-lelbowY-Angle", //199 + "virtual_hip_x_minus_0_15_y_minus_0_15Y-halfway_lelbow_and_lhandY-Angle", //200 + "virtual_hip_x_minus_0_15_y_minus_0_15Y-lhandY-Angle", //201 + "virtual_hip_x_minus_0_15_y_minus_0_15Y-halfway_neck_and_hipY-Angle", //202 + "virtual_hip_x_plus0_15_y_minus_0_15Y-hipY-Angle", //203 + "virtual_hip_x_plus0_15_y_minus_0_15Y-EndSite_eye.rY-Angle", //204 + "virtual_hip_x_plus0_15_y_minus_0_15Y-EndSite_eye.lY-Angle", //205 + "virtual_hip_x_plus0_15_y_minus_0_15Y-neckY-Angle", //206 + "virtual_hip_x_plus0_15_y_minus_0_15Y-rshoulderY-Angle", //207 + "virtual_hip_x_plus0_15_y_minus_0_15Y-halfway_rshoulder_and_relbowY-Angle", //208 + "virtual_hip_x_plus0_15_y_minus_0_15Y-relbowY-Angle", //209 + "virtual_hip_x_plus0_15_y_minus_0_15Y-halfway_relbow_and_rhandY-Angle", //210 + "virtual_hip_x_plus0_15_y_minus_0_15Y-rhandY-Angle", //211 + "virtual_hip_x_plus0_15_y_minus_0_15Y-virtual_hip_x_minus_0_15_y_minus_0_15Y-Angle", //212 + "angleUsedFor2DRotation_10", //213 + "virtual_hip_x_plus0_15_y_minus_0_15Y-lshoulderY-Angle", //214 + "virtual_hip_x_plus0_15_y_minus_0_15Y-halfway_lshoulder_and_lelbowY-Angle", //215 + "virtual_hip_x_plus0_15_y_minus_0_15Y-lelbowY-Angle", //216 + "virtual_hip_x_plus0_15_y_minus_0_15Y-halfway_lelbow_and_lhandY-Angle", //217 + "virtual_hip_x_plus0_15_y_minus_0_15Y-lhandY-Angle", //218 + "virtual_hip_x_plus0_15_y_minus_0_15Y-halfway_neck_and_hipY-Angle", //219 + "lshoulderY-hipY-Angle", //220 + "lshoulderY-EndSite_eye.rY-Angle", //221 + "lshoulderY-EndSite_eye.lY-Angle", //222 + "lshoulderY-neckY-Angle", //223 + "lshoulderY-rshoulderY-Angle", //224 + "lshoulderY-halfway_rshoulder_and_relbowY-Angle", //225 + "lshoulderY-relbowY-Angle", //226 + "lshoulderY-halfway_relbow_and_rhandY-Angle", //227 + "lshoulderY-rhandY-Angle", //228 + "lshoulderY-virtual_hip_x_minus_0_15_y_minus_0_15Y-Angle", //229 + "lshoulderY-virtual_hip_x_plus0_15_y_minus_0_15Y-Angle", //230 + "angleUsedFor2DRotation_11", //231 + "lshoulderY-halfway_lshoulder_and_lelbowY-Angle", //232 + "lshoulderY-lelbowY-Angle", //233 + "lshoulderY-halfway_lelbow_and_lhandY-Angle", //234 + "lshoulderY-lhandY-Angle", //235 + "lshoulderY-halfway_neck_and_hipY-Angle", //236 + "halfway_lshoulder_and_lelbowY-hipY-Angle", //237 + "halfway_lshoulder_and_lelbowY-EndSite_eye.rY-Angle", //238 + "halfway_lshoulder_and_lelbowY-EndSite_eye.lY-Angle", //239 + "halfway_lshoulder_and_lelbowY-neckY-Angle", //240 + "halfway_lshoulder_and_lelbowY-rshoulderY-Angle", //241 + "halfway_lshoulder_and_lelbowY-halfway_rshoulder_and_relbowY-Angle", //242 + "halfway_lshoulder_and_lelbowY-relbowY-Angle", //243 + "halfway_lshoulder_and_lelbowY-halfway_relbow_and_rhandY-Angle", //244 + "halfway_lshoulder_and_lelbowY-rhandY-Angle", //245 + "halfway_lshoulder_and_lelbowY-virtual_hip_x_minus_0_15_y_minus_0_15Y-Angle", //246 + "halfway_lshoulder_and_lelbowY-virtual_hip_x_plus0_15_y_minus_0_15Y-Angle", //247 + "halfway_lshoulder_and_lelbowY-lshoulderY-Angle", //248 + "angleUsedFor2DRotation_12", //249 + "halfway_lshoulder_and_lelbowY-lelbowY-Angle", //250 + "halfway_lshoulder_and_lelbowY-halfway_lelbow_and_lhandY-Angle", //251 + "halfway_lshoulder_and_lelbowY-lhandY-Angle", //252 + "halfway_lshoulder_and_lelbowY-halfway_neck_and_hipY-Angle", //253 + "lelbowY-hipY-Angle", //254 + "lelbowY-EndSite_eye.rY-Angle", //255 + "lelbowY-EndSite_eye.lY-Angle", //256 + "lelbowY-neckY-Angle", //257 + "lelbowY-rshoulderY-Angle", //258 + "lelbowY-halfway_rshoulder_and_relbowY-Angle", //259 + "lelbowY-relbowY-Angle", //260 + "lelbowY-halfway_relbow_and_rhandY-Angle", //261 + "lelbowY-rhandY-Angle", //262 + "lelbowY-virtual_hip_x_minus_0_15_y_minus_0_15Y-Angle", //263 + "lelbowY-virtual_hip_x_plus0_15_y_minus_0_15Y-Angle", //264 + "lelbowY-lshoulderY-Angle", //265 + "lelbowY-halfway_lshoulder_and_lelbowY-Angle", //266 + "angleUsedFor2DRotation_13", //267 + "lelbowY-halfway_lelbow_and_lhandY-Angle", //268 + "lelbowY-lhandY-Angle", //269 + "lelbowY-halfway_neck_and_hipY-Angle", //270 + "halfway_lelbow_and_lhandY-hipY-Angle", //271 + "halfway_lelbow_and_lhandY-EndSite_eye.rY-Angle", //272 + "halfway_lelbow_and_lhandY-EndSite_eye.lY-Angle", //273 + "halfway_lelbow_and_lhandY-neckY-Angle", //274 + "halfway_lelbow_and_lhandY-rshoulderY-Angle", //275 + "halfway_lelbow_and_lhandY-halfway_rshoulder_and_relbowY-Angle", //276 + "halfway_lelbow_and_lhandY-relbowY-Angle", //277 + "halfway_lelbow_and_lhandY-halfway_relbow_and_rhandY-Angle", //278 + "halfway_lelbow_and_lhandY-rhandY-Angle", //279 + "halfway_lelbow_and_lhandY-virtual_hip_x_minus_0_15_y_minus_0_15Y-Angle", //280 + "halfway_lelbow_and_lhandY-virtual_hip_x_plus0_15_y_minus_0_15Y-Angle", //281 + "halfway_lelbow_and_lhandY-lshoulderY-Angle", //282 + "halfway_lelbow_and_lhandY-halfway_lshoulder_and_lelbowY-Angle", //283 + "halfway_lelbow_and_lhandY-lelbowY-Angle", //284 + "angleUsedFor2DRotation_14", //285 + "halfway_lelbow_and_lhandY-lhandY-Angle", //286 + "halfway_lelbow_and_lhandY-halfway_neck_and_hipY-Angle", //287 + "lhandY-hipY-Angle", //288 + "lhandY-EndSite_eye.rY-Angle", //289 + "lhandY-EndSite_eye.lY-Angle", //290 + "lhandY-neckY-Angle", //291 + "lhandY-rshoulderY-Angle", //292 + "lhandY-halfway_rshoulder_and_relbowY-Angle", //293 + "lhandY-relbowY-Angle", //294 + "lhandY-halfway_relbow_and_rhandY-Angle", //295 + "lhandY-rhandY-Angle", //296 + "lhandY-virtual_hip_x_minus_0_15_y_minus_0_15Y-Angle", //297 + "lhandY-virtual_hip_x_plus0_15_y_minus_0_15Y-Angle", //298 + "lhandY-lshoulderY-Angle", //299 + "lhandY-halfway_lshoulder_and_lelbowY-Angle", //300 + "lhandY-lelbowY-Angle", //301 + "lhandY-halfway_lelbow_and_lhandY-Angle", //302 + "angleUsedFor2DRotation_15", //303 + "lhandY-halfway_neck_and_hipY-Angle", //304 + "halfway_neck_and_hipY-hipY-Angle", //305 + "halfway_neck_and_hipY-EndSite_eye.rY-Angle", //306 + "halfway_neck_and_hipY-EndSite_eye.lY-Angle", //307 + "halfway_neck_and_hipY-neckY-Angle", //308 + "halfway_neck_and_hipY-rshoulderY-Angle", //309 + "halfway_neck_and_hipY-halfway_rshoulder_and_relbowY-Angle", //310 + "halfway_neck_and_hipY-relbowY-Angle", //311 + "halfway_neck_and_hipY-halfway_relbow_and_rhandY-Angle", //312 + "halfway_neck_and_hipY-rhandY-Angle", //313 + "halfway_neck_and_hipY-virtual_hip_x_minus_0_15_y_minus_0_15Y-Angle", //314 + "halfway_neck_and_hipY-virtual_hip_x_plus0_15_y_minus_0_15Y-Angle", //315 + "halfway_neck_and_hipY-lshoulderY-Angle", //316 + "halfway_neck_and_hipY-halfway_lshoulder_and_lelbowY-Angle", //317 + "halfway_neck_and_hipY-lelbowY-Angle", //318 + "halfway_neck_and_hipY-halfway_lelbow_and_lhandY-Angle", //319 + "halfway_neck_and_hipY-lhandY-Angle", //320 + "angleUsedFor2DRotation_16", //321 + "end" +}; +/** @brief Programmer friendly enumerator of expected inputs*/ +enum mocapNET_upperbody_enum +{ + MNET_UPPERBODY_IN_2DX_HIP = 0, //0 + MNET_UPPERBODY_IN_2DY_HIP, //1 + MNET_UPPERBODY_IN_VISIBLE_HIP, //2 + MNET_UPPERBODY_IN_2DX_NECK, //3 + MNET_UPPERBODY_IN_2DY_NECK, //4 + MNET_UPPERBODY_IN_VISIBLE_NECK, //5 + MNET_UPPERBODY_IN_2DX_HEAD, //6 + MNET_UPPERBODY_IN_2DY_HEAD, //7 + MNET_UPPERBODY_IN_VISIBLE_HEAD, //8 + MNET_UPPERBODY_IN_2DX_ENDSITE_EYE_L, //9 + MNET_UPPERBODY_IN_2DY_ENDSITE_EYE_L, //10 + MNET_UPPERBODY_IN_VISIBLE_ENDSITE_EYE_L, //11 + MNET_UPPERBODY_IN_2DX_ENDSITE_EYE_R, //12 + MNET_UPPERBODY_IN_2DY_ENDSITE_EYE_R, //13 + MNET_UPPERBODY_IN_VISIBLE_ENDSITE_EYE_R, //14 + MNET_UPPERBODY_IN_2DX_RSHOULDER, //15 + MNET_UPPERBODY_IN_2DY_RSHOULDER, //16 + MNET_UPPERBODY_IN_VISIBLE_RSHOULDER, //17 + MNET_UPPERBODY_IN_2DX_RELBOW, //18 + MNET_UPPERBODY_IN_2DY_RELBOW, //19 + MNET_UPPERBODY_IN_VISIBLE_RELBOW, //20 + MNET_UPPERBODY_IN_2DX_RHAND, //21 + MNET_UPPERBODY_IN_2DY_RHAND, //22 + MNET_UPPERBODY_IN_VISIBLE_RHAND, //23 + MNET_UPPERBODY_IN_2DX_LSHOULDER, //24 + MNET_UPPERBODY_IN_2DY_LSHOULDER, //25 + MNET_UPPERBODY_IN_VISIBLE_LSHOULDER, //26 + MNET_UPPERBODY_IN_2DX_LELBOW, //27 + MNET_UPPERBODY_IN_2DY_LELBOW, //28 + MNET_UPPERBODY_IN_VISIBLE_LELBOW, //29 + MNET_UPPERBODY_IN_2DX_LHAND, //30 + MNET_UPPERBODY_IN_2DY_LHAND, //31 + MNET_UPPERBODY_IN_VISIBLE_LHAND, //32 + MNET_UPPERBODY_IN_ANGLEUSEDFOR2DROTATION_0, //33 + MNET_UPPERBODY_IN_HIPY_ENDSITE_EYE_RY_ANGLE, //34 + MNET_UPPERBODY_IN_HIPY_ENDSITE_EYE_LY_ANGLE, //35 + MNET_UPPERBODY_IN_HIPY_NECKY_ANGLE, //36 + MNET_UPPERBODY_IN_HIPY_RSHOULDERY_ANGLE, //37 + MNET_UPPERBODY_IN_HIPY_HALFWAY_RSHOULDER_AND_RELBOWY_ANGLE, //38 + MNET_UPPERBODY_IN_HIPY_RELBOWY_ANGLE, //39 + MNET_UPPERBODY_IN_HIPY_HALFWAY_RELBOW_AND_RHANDY_ANGLE, //40 + MNET_UPPERBODY_IN_HIPY_RHANDY_ANGLE, //41 + MNET_UPPERBODY_IN_HIPY_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_ANGLE, //42 + MNET_UPPERBODY_IN_HIPY_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_ANGLE, //43 + MNET_UPPERBODY_IN_HIPY_LSHOULDERY_ANGLE, //44 + MNET_UPPERBODY_IN_HIPY_HALFWAY_LSHOULDER_AND_LELBOWY_ANGLE, //45 + MNET_UPPERBODY_IN_HIPY_LELBOWY_ANGLE, //46 + MNET_UPPERBODY_IN_HIPY_HALFWAY_LELBOW_AND_LHANDY_ANGLE, //47 + MNET_UPPERBODY_IN_HIPY_LHANDY_ANGLE, //48 + MNET_UPPERBODY_IN_HIPY_HALFWAY_NECK_AND_HIPY_ANGLE, //49 + MNET_UPPERBODY_IN_ENDSITE_EYE_RY_HIPY_ANGLE, //50 + MNET_UPPERBODY_IN_ANGLEUSEDFOR2DROTATION_1, //51 + MNET_UPPERBODY_IN_ENDSITE_EYE_RY_ENDSITE_EYE_LY_ANGLE, //52 + MNET_UPPERBODY_IN_ENDSITE_EYE_RY_NECKY_ANGLE, //53 + MNET_UPPERBODY_IN_ENDSITE_EYE_RY_RSHOULDERY_ANGLE, //54 + MNET_UPPERBODY_IN_ENDSITE_EYE_RY_HALFWAY_RSHOULDER_AND_RELBOWY_ANGLE, //55 + MNET_UPPERBODY_IN_ENDSITE_EYE_RY_RELBOWY_ANGLE, //56 + MNET_UPPERBODY_IN_ENDSITE_EYE_RY_HALFWAY_RELBOW_AND_RHANDY_ANGLE, //57 + MNET_UPPERBODY_IN_ENDSITE_EYE_RY_RHANDY_ANGLE, //58 + MNET_UPPERBODY_IN_ENDSITE_EYE_RY_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_ANGLE, //59 + MNET_UPPERBODY_IN_ENDSITE_EYE_RY_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_ANGLE, //60 + MNET_UPPERBODY_IN_ENDSITE_EYE_RY_LSHOULDERY_ANGLE, //61 + MNET_UPPERBODY_IN_ENDSITE_EYE_RY_HALFWAY_LSHOULDER_AND_LELBOWY_ANGLE, //62 + MNET_UPPERBODY_IN_ENDSITE_EYE_RY_LELBOWY_ANGLE, //63 + MNET_UPPERBODY_IN_ENDSITE_EYE_RY_HALFWAY_LELBOW_AND_LHANDY_ANGLE, //64 + MNET_UPPERBODY_IN_ENDSITE_EYE_RY_LHANDY_ANGLE, //65 + MNET_UPPERBODY_IN_ENDSITE_EYE_RY_HALFWAY_NECK_AND_HIPY_ANGLE, //66 + MNET_UPPERBODY_IN_ENDSITE_EYE_LY_HIPY_ANGLE, //67 + MNET_UPPERBODY_IN_ENDSITE_EYE_LY_ENDSITE_EYE_RY_ANGLE, //68 + MNET_UPPERBODY_IN_ANGLEUSEDFOR2DROTATION_2, //69 + MNET_UPPERBODY_IN_ENDSITE_EYE_LY_NECKY_ANGLE, //70 + MNET_UPPERBODY_IN_ENDSITE_EYE_LY_RSHOULDERY_ANGLE, //71 + MNET_UPPERBODY_IN_ENDSITE_EYE_LY_HALFWAY_RSHOULDER_AND_RELBOWY_ANGLE, //72 + MNET_UPPERBODY_IN_ENDSITE_EYE_LY_RELBOWY_ANGLE, //73 + MNET_UPPERBODY_IN_ENDSITE_EYE_LY_HALFWAY_RELBOW_AND_RHANDY_ANGLE, //74 + MNET_UPPERBODY_IN_ENDSITE_EYE_LY_RHANDY_ANGLE, //75 + MNET_UPPERBODY_IN_ENDSITE_EYE_LY_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_ANGLE, //76 + MNET_UPPERBODY_IN_ENDSITE_EYE_LY_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_ANGLE, //77 + MNET_UPPERBODY_IN_ENDSITE_EYE_LY_LSHOULDERY_ANGLE, //78 + MNET_UPPERBODY_IN_ENDSITE_EYE_LY_HALFWAY_LSHOULDER_AND_LELBOWY_ANGLE, //79 + MNET_UPPERBODY_IN_ENDSITE_EYE_LY_LELBOWY_ANGLE, //80 + MNET_UPPERBODY_IN_ENDSITE_EYE_LY_HALFWAY_LELBOW_AND_LHANDY_ANGLE, //81 + MNET_UPPERBODY_IN_ENDSITE_EYE_LY_LHANDY_ANGLE, //82 + MNET_UPPERBODY_IN_ENDSITE_EYE_LY_HALFWAY_NECK_AND_HIPY_ANGLE, //83 + MNET_UPPERBODY_IN_NECKY_HIPY_ANGLE, //84 + MNET_UPPERBODY_IN_NECKY_ENDSITE_EYE_RY_ANGLE, //85 + MNET_UPPERBODY_IN_NECKY_ENDSITE_EYE_LY_ANGLE, //86 + MNET_UPPERBODY_IN_ANGLEUSEDFOR2DROTATION_3, //87 + MNET_UPPERBODY_IN_NECKY_RSHOULDERY_ANGLE, //88 + MNET_UPPERBODY_IN_NECKY_HALFWAY_RSHOULDER_AND_RELBOWY_ANGLE, //89 + MNET_UPPERBODY_IN_NECKY_RELBOWY_ANGLE, //90 + MNET_UPPERBODY_IN_NECKY_HALFWAY_RELBOW_AND_RHANDY_ANGLE, //91 + MNET_UPPERBODY_IN_NECKY_RHANDY_ANGLE, //92 + MNET_UPPERBODY_IN_NECKY_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_ANGLE, //93 + MNET_UPPERBODY_IN_NECKY_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_ANGLE, //94 + MNET_UPPERBODY_IN_NECKY_LSHOULDERY_ANGLE, //95 + MNET_UPPERBODY_IN_NECKY_HALFWAY_LSHOULDER_AND_LELBOWY_ANGLE, //96 + MNET_UPPERBODY_IN_NECKY_LELBOWY_ANGLE, //97 + MNET_UPPERBODY_IN_NECKY_HALFWAY_LELBOW_AND_LHANDY_ANGLE, //98 + MNET_UPPERBODY_IN_NECKY_LHANDY_ANGLE, //99 + MNET_UPPERBODY_IN_NECKY_HALFWAY_NECK_AND_HIPY_ANGLE, //100 + MNET_UPPERBODY_IN_RSHOULDERY_HIPY_ANGLE, //101 + MNET_UPPERBODY_IN_RSHOULDERY_ENDSITE_EYE_RY_ANGLE, //102 + MNET_UPPERBODY_IN_RSHOULDERY_ENDSITE_EYE_LY_ANGLE, //103 + MNET_UPPERBODY_IN_RSHOULDERY_NECKY_ANGLE, //104 + MNET_UPPERBODY_IN_ANGLEUSEDFOR2DROTATION_4, //105 + MNET_UPPERBODY_IN_RSHOULDERY_HALFWAY_RSHOULDER_AND_RELBOWY_ANGLE, //106 + MNET_UPPERBODY_IN_RSHOULDERY_RELBOWY_ANGLE, //107 + MNET_UPPERBODY_IN_RSHOULDERY_HALFWAY_RELBOW_AND_RHANDY_ANGLE, //108 + MNET_UPPERBODY_IN_RSHOULDERY_RHANDY_ANGLE, //109 + MNET_UPPERBODY_IN_RSHOULDERY_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_ANGLE, //110 + MNET_UPPERBODY_IN_RSHOULDERY_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_ANGLE, //111 + MNET_UPPERBODY_IN_RSHOULDERY_LSHOULDERY_ANGLE, //112 + MNET_UPPERBODY_IN_RSHOULDERY_HALFWAY_LSHOULDER_AND_LELBOWY_ANGLE, //113 + MNET_UPPERBODY_IN_RSHOULDERY_LELBOWY_ANGLE, //114 + MNET_UPPERBODY_IN_RSHOULDERY_HALFWAY_LELBOW_AND_LHANDY_ANGLE, //115 + MNET_UPPERBODY_IN_RSHOULDERY_LHANDY_ANGLE, //116 + MNET_UPPERBODY_IN_RSHOULDERY_HALFWAY_NECK_AND_HIPY_ANGLE, //117 + MNET_UPPERBODY_IN_HALFWAY_RSHOULDER_AND_RELBOWY_HIPY_ANGLE, //118 + MNET_UPPERBODY_IN_HALFWAY_RSHOULDER_AND_RELBOWY_ENDSITE_EYE_RY_ANGLE, //119 + MNET_UPPERBODY_IN_HALFWAY_RSHOULDER_AND_RELBOWY_ENDSITE_EYE_LY_ANGLE, //120 + MNET_UPPERBODY_IN_HALFWAY_RSHOULDER_AND_RELBOWY_NECKY_ANGLE, //121 + MNET_UPPERBODY_IN_HALFWAY_RSHOULDER_AND_RELBOWY_RSHOULDERY_ANGLE, //122 + MNET_UPPERBODY_IN_ANGLEUSEDFOR2DROTATION_5, //123 + MNET_UPPERBODY_IN_HALFWAY_RSHOULDER_AND_RELBOWY_RELBOWY_ANGLE, //124 + MNET_UPPERBODY_IN_HALFWAY_RSHOULDER_AND_RELBOWY_HALFWAY_RELBOW_AND_RHANDY_ANGLE, //125 + MNET_UPPERBODY_IN_HALFWAY_RSHOULDER_AND_RELBOWY_RHANDY_ANGLE, //126 + MNET_UPPERBODY_IN_HALFWAY_RSHOULDER_AND_RELBOWY_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_ANGLE, //127 + MNET_UPPERBODY_IN_HALFWAY_RSHOULDER_AND_RELBOWY_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_ANGLE, //128 + MNET_UPPERBODY_IN_HALFWAY_RSHOULDER_AND_RELBOWY_LSHOULDERY_ANGLE, //129 + MNET_UPPERBODY_IN_HALFWAY_RSHOULDER_AND_RELBOWY_HALFWAY_LSHOULDER_AND_LELBOWY_ANGLE, //130 + MNET_UPPERBODY_IN_HALFWAY_RSHOULDER_AND_RELBOWY_LELBOWY_ANGLE, //131 + MNET_UPPERBODY_IN_HALFWAY_RSHOULDER_AND_RELBOWY_HALFWAY_LELBOW_AND_LHANDY_ANGLE, //132 + MNET_UPPERBODY_IN_HALFWAY_RSHOULDER_AND_RELBOWY_LHANDY_ANGLE, //133 + MNET_UPPERBODY_IN_HALFWAY_RSHOULDER_AND_RELBOWY_HALFWAY_NECK_AND_HIPY_ANGLE, //134 + MNET_UPPERBODY_IN_RELBOWY_HIPY_ANGLE, //135 + MNET_UPPERBODY_IN_RELBOWY_ENDSITE_EYE_RY_ANGLE, //136 + MNET_UPPERBODY_IN_RELBOWY_ENDSITE_EYE_LY_ANGLE, //137 + MNET_UPPERBODY_IN_RELBOWY_NECKY_ANGLE, //138 + MNET_UPPERBODY_IN_RELBOWY_RSHOULDERY_ANGLE, //139 + MNET_UPPERBODY_IN_RELBOWY_HALFWAY_RSHOULDER_AND_RELBOWY_ANGLE, //140 + MNET_UPPERBODY_IN_ANGLEUSEDFOR2DROTATION_6, //141 + MNET_UPPERBODY_IN_RELBOWY_HALFWAY_RELBOW_AND_RHANDY_ANGLE, //142 + MNET_UPPERBODY_IN_RELBOWY_RHANDY_ANGLE, //143 + MNET_UPPERBODY_IN_RELBOWY_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_ANGLE, //144 + MNET_UPPERBODY_IN_RELBOWY_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_ANGLE, //145 + MNET_UPPERBODY_IN_RELBOWY_LSHOULDERY_ANGLE, //146 + MNET_UPPERBODY_IN_RELBOWY_HALFWAY_LSHOULDER_AND_LELBOWY_ANGLE, //147 + MNET_UPPERBODY_IN_RELBOWY_LELBOWY_ANGLE, //148 + MNET_UPPERBODY_IN_RELBOWY_HALFWAY_LELBOW_AND_LHANDY_ANGLE, //149 + MNET_UPPERBODY_IN_RELBOWY_LHANDY_ANGLE, //150 + MNET_UPPERBODY_IN_RELBOWY_HALFWAY_NECK_AND_HIPY_ANGLE, //151 + MNET_UPPERBODY_IN_HALFWAY_RELBOW_AND_RHANDY_HIPY_ANGLE, //152 + MNET_UPPERBODY_IN_HALFWAY_RELBOW_AND_RHANDY_ENDSITE_EYE_RY_ANGLE, //153 + MNET_UPPERBODY_IN_HALFWAY_RELBOW_AND_RHANDY_ENDSITE_EYE_LY_ANGLE, //154 + MNET_UPPERBODY_IN_HALFWAY_RELBOW_AND_RHANDY_NECKY_ANGLE, //155 + MNET_UPPERBODY_IN_HALFWAY_RELBOW_AND_RHANDY_RSHOULDERY_ANGLE, //156 + MNET_UPPERBODY_IN_HALFWAY_RELBOW_AND_RHANDY_HALFWAY_RSHOULDER_AND_RELBOWY_ANGLE, //157 + MNET_UPPERBODY_IN_HALFWAY_RELBOW_AND_RHANDY_RELBOWY_ANGLE, //158 + MNET_UPPERBODY_IN_ANGLEUSEDFOR2DROTATION_7, //159 + MNET_UPPERBODY_IN_HALFWAY_RELBOW_AND_RHANDY_RHANDY_ANGLE, //160 + MNET_UPPERBODY_IN_HALFWAY_RELBOW_AND_RHANDY_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_ANGLE, //161 + MNET_UPPERBODY_IN_HALFWAY_RELBOW_AND_RHANDY_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_ANGLE, //162 + MNET_UPPERBODY_IN_HALFWAY_RELBOW_AND_RHANDY_LSHOULDERY_ANGLE, //163 + MNET_UPPERBODY_IN_HALFWAY_RELBOW_AND_RHANDY_HALFWAY_LSHOULDER_AND_LELBOWY_ANGLE, //164 + MNET_UPPERBODY_IN_HALFWAY_RELBOW_AND_RHANDY_LELBOWY_ANGLE, //165 + MNET_UPPERBODY_IN_HALFWAY_RELBOW_AND_RHANDY_HALFWAY_LELBOW_AND_LHANDY_ANGLE, //166 + MNET_UPPERBODY_IN_HALFWAY_RELBOW_AND_RHANDY_LHANDY_ANGLE, //167 + MNET_UPPERBODY_IN_HALFWAY_RELBOW_AND_RHANDY_HALFWAY_NECK_AND_HIPY_ANGLE, //168 + MNET_UPPERBODY_IN_RHANDY_HIPY_ANGLE, //169 + MNET_UPPERBODY_IN_RHANDY_ENDSITE_EYE_RY_ANGLE, //170 + MNET_UPPERBODY_IN_RHANDY_ENDSITE_EYE_LY_ANGLE, //171 + MNET_UPPERBODY_IN_RHANDY_NECKY_ANGLE, //172 + MNET_UPPERBODY_IN_RHANDY_RSHOULDERY_ANGLE, //173 + MNET_UPPERBODY_IN_RHANDY_HALFWAY_RSHOULDER_AND_RELBOWY_ANGLE, //174 + MNET_UPPERBODY_IN_RHANDY_RELBOWY_ANGLE, //175 + MNET_UPPERBODY_IN_RHANDY_HALFWAY_RELBOW_AND_RHANDY_ANGLE, //176 + MNET_UPPERBODY_IN_ANGLEUSEDFOR2DROTATION_8, //177 + MNET_UPPERBODY_IN_RHANDY_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_ANGLE, //178 + MNET_UPPERBODY_IN_RHANDY_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_ANGLE, //179 + MNET_UPPERBODY_IN_RHANDY_LSHOULDERY_ANGLE, //180 + MNET_UPPERBODY_IN_RHANDY_HALFWAY_LSHOULDER_AND_LELBOWY_ANGLE, //181 + MNET_UPPERBODY_IN_RHANDY_LELBOWY_ANGLE, //182 + MNET_UPPERBODY_IN_RHANDY_HALFWAY_LELBOW_AND_LHANDY_ANGLE, //183 + MNET_UPPERBODY_IN_RHANDY_LHANDY_ANGLE, //184 + MNET_UPPERBODY_IN_RHANDY_HALFWAY_NECK_AND_HIPY_ANGLE, //185 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_HIPY_ANGLE, //186 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_ENDSITE_EYE_RY_ANGLE, //187 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_ENDSITE_EYE_LY_ANGLE, //188 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_NECKY_ANGLE, //189 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_RSHOULDERY_ANGLE, //190 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_HALFWAY_RSHOULDER_AND_RELBOWY_ANGLE, //191 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_RELBOWY_ANGLE, //192 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_HALFWAY_RELBOW_AND_RHANDY_ANGLE, //193 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_RHANDY_ANGLE, //194 + MNET_UPPERBODY_IN_ANGLEUSEDFOR2DROTATION_9, //195 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_ANGLE, //196 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_LSHOULDERY_ANGLE, //197 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_HALFWAY_LSHOULDER_AND_LELBOWY_ANGLE, //198 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_LELBOWY_ANGLE, //199 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_HALFWAY_LELBOW_AND_LHANDY_ANGLE, //200 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_LHANDY_ANGLE, //201 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_HALFWAY_NECK_AND_HIPY_ANGLE, //202 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_HIPY_ANGLE, //203 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_ENDSITE_EYE_RY_ANGLE, //204 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_ENDSITE_EYE_LY_ANGLE, //205 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_NECKY_ANGLE, //206 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_RSHOULDERY_ANGLE, //207 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_HALFWAY_RSHOULDER_AND_RELBOWY_ANGLE, //208 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_RELBOWY_ANGLE, //209 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_HALFWAY_RELBOW_AND_RHANDY_ANGLE, //210 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_RHANDY_ANGLE, //211 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_ANGLE, //212 + MNET_UPPERBODY_IN_ANGLEUSEDFOR2DROTATION_10, //213 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_LSHOULDERY_ANGLE, //214 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_HALFWAY_LSHOULDER_AND_LELBOWY_ANGLE, //215 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_LELBOWY_ANGLE, //216 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_HALFWAY_LELBOW_AND_LHANDY_ANGLE, //217 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_LHANDY_ANGLE, //218 + MNET_UPPERBODY_IN_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_HALFWAY_NECK_AND_HIPY_ANGLE, //219 + MNET_UPPERBODY_IN_LSHOULDERY_HIPY_ANGLE, //220 + MNET_UPPERBODY_IN_LSHOULDERY_ENDSITE_EYE_RY_ANGLE, //221 + MNET_UPPERBODY_IN_LSHOULDERY_ENDSITE_EYE_LY_ANGLE, //222 + MNET_UPPERBODY_IN_LSHOULDERY_NECKY_ANGLE, //223 + MNET_UPPERBODY_IN_LSHOULDERY_RSHOULDERY_ANGLE, //224 + MNET_UPPERBODY_IN_LSHOULDERY_HALFWAY_RSHOULDER_AND_RELBOWY_ANGLE, //225 + MNET_UPPERBODY_IN_LSHOULDERY_RELBOWY_ANGLE, //226 + MNET_UPPERBODY_IN_LSHOULDERY_HALFWAY_RELBOW_AND_RHANDY_ANGLE, //227 + MNET_UPPERBODY_IN_LSHOULDERY_RHANDY_ANGLE, //228 + MNET_UPPERBODY_IN_LSHOULDERY_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_ANGLE, //229 + MNET_UPPERBODY_IN_LSHOULDERY_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_ANGLE, //230 + MNET_UPPERBODY_IN_ANGLEUSEDFOR2DROTATION_11, //231 + MNET_UPPERBODY_IN_LSHOULDERY_HALFWAY_LSHOULDER_AND_LELBOWY_ANGLE, //232 + MNET_UPPERBODY_IN_LSHOULDERY_LELBOWY_ANGLE, //233 + MNET_UPPERBODY_IN_LSHOULDERY_HALFWAY_LELBOW_AND_LHANDY_ANGLE, //234 + MNET_UPPERBODY_IN_LSHOULDERY_LHANDY_ANGLE, //235 + MNET_UPPERBODY_IN_LSHOULDERY_HALFWAY_NECK_AND_HIPY_ANGLE, //236 + MNET_UPPERBODY_IN_HALFWAY_LSHOULDER_AND_LELBOWY_HIPY_ANGLE, //237 + MNET_UPPERBODY_IN_HALFWAY_LSHOULDER_AND_LELBOWY_ENDSITE_EYE_RY_ANGLE, //238 + MNET_UPPERBODY_IN_HALFWAY_LSHOULDER_AND_LELBOWY_ENDSITE_EYE_LY_ANGLE, //239 + MNET_UPPERBODY_IN_HALFWAY_LSHOULDER_AND_LELBOWY_NECKY_ANGLE, //240 + MNET_UPPERBODY_IN_HALFWAY_LSHOULDER_AND_LELBOWY_RSHOULDERY_ANGLE, //241 + MNET_UPPERBODY_IN_HALFWAY_LSHOULDER_AND_LELBOWY_HALFWAY_RSHOULDER_AND_RELBOWY_ANGLE, //242 + MNET_UPPERBODY_IN_HALFWAY_LSHOULDER_AND_LELBOWY_RELBOWY_ANGLE, //243 + MNET_UPPERBODY_IN_HALFWAY_LSHOULDER_AND_LELBOWY_HALFWAY_RELBOW_AND_RHANDY_ANGLE, //244 + MNET_UPPERBODY_IN_HALFWAY_LSHOULDER_AND_LELBOWY_RHANDY_ANGLE, //245 + MNET_UPPERBODY_IN_HALFWAY_LSHOULDER_AND_LELBOWY_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_ANGLE, //246 + MNET_UPPERBODY_IN_HALFWAY_LSHOULDER_AND_LELBOWY_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_ANGLE, //247 + MNET_UPPERBODY_IN_HALFWAY_LSHOULDER_AND_LELBOWY_LSHOULDERY_ANGLE, //248 + MNET_UPPERBODY_IN_ANGLEUSEDFOR2DROTATION_12, //249 + MNET_UPPERBODY_IN_HALFWAY_LSHOULDER_AND_LELBOWY_LELBOWY_ANGLE, //250 + MNET_UPPERBODY_IN_HALFWAY_LSHOULDER_AND_LELBOWY_HALFWAY_LELBOW_AND_LHANDY_ANGLE, //251 + MNET_UPPERBODY_IN_HALFWAY_LSHOULDER_AND_LELBOWY_LHANDY_ANGLE, //252 + MNET_UPPERBODY_IN_HALFWAY_LSHOULDER_AND_LELBOWY_HALFWAY_NECK_AND_HIPY_ANGLE, //253 + MNET_UPPERBODY_IN_LELBOWY_HIPY_ANGLE, //254 + MNET_UPPERBODY_IN_LELBOWY_ENDSITE_EYE_RY_ANGLE, //255 + MNET_UPPERBODY_IN_LELBOWY_ENDSITE_EYE_LY_ANGLE, //256 + MNET_UPPERBODY_IN_LELBOWY_NECKY_ANGLE, //257 + MNET_UPPERBODY_IN_LELBOWY_RSHOULDERY_ANGLE, //258 + MNET_UPPERBODY_IN_LELBOWY_HALFWAY_RSHOULDER_AND_RELBOWY_ANGLE, //259 + MNET_UPPERBODY_IN_LELBOWY_RELBOWY_ANGLE, //260 + MNET_UPPERBODY_IN_LELBOWY_HALFWAY_RELBOW_AND_RHANDY_ANGLE, //261 + MNET_UPPERBODY_IN_LELBOWY_RHANDY_ANGLE, //262 + MNET_UPPERBODY_IN_LELBOWY_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_ANGLE, //263 + MNET_UPPERBODY_IN_LELBOWY_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_ANGLE, //264 + MNET_UPPERBODY_IN_LELBOWY_LSHOULDERY_ANGLE, //265 + MNET_UPPERBODY_IN_LELBOWY_HALFWAY_LSHOULDER_AND_LELBOWY_ANGLE, //266 + MNET_UPPERBODY_IN_ANGLEUSEDFOR2DROTATION_13, //267 + MNET_UPPERBODY_IN_LELBOWY_HALFWAY_LELBOW_AND_LHANDY_ANGLE, //268 + MNET_UPPERBODY_IN_LELBOWY_LHANDY_ANGLE, //269 + MNET_UPPERBODY_IN_LELBOWY_HALFWAY_NECK_AND_HIPY_ANGLE, //270 + MNET_UPPERBODY_IN_HALFWAY_LELBOW_AND_LHANDY_HIPY_ANGLE, //271 + MNET_UPPERBODY_IN_HALFWAY_LELBOW_AND_LHANDY_ENDSITE_EYE_RY_ANGLE, //272 + MNET_UPPERBODY_IN_HALFWAY_LELBOW_AND_LHANDY_ENDSITE_EYE_LY_ANGLE, //273 + MNET_UPPERBODY_IN_HALFWAY_LELBOW_AND_LHANDY_NECKY_ANGLE, //274 + MNET_UPPERBODY_IN_HALFWAY_LELBOW_AND_LHANDY_RSHOULDERY_ANGLE, //275 + MNET_UPPERBODY_IN_HALFWAY_LELBOW_AND_LHANDY_HALFWAY_RSHOULDER_AND_RELBOWY_ANGLE, //276 + MNET_UPPERBODY_IN_HALFWAY_LELBOW_AND_LHANDY_RELBOWY_ANGLE, //277 + MNET_UPPERBODY_IN_HALFWAY_LELBOW_AND_LHANDY_HALFWAY_RELBOW_AND_RHANDY_ANGLE, //278 + MNET_UPPERBODY_IN_HALFWAY_LELBOW_AND_LHANDY_RHANDY_ANGLE, //279 + MNET_UPPERBODY_IN_HALFWAY_LELBOW_AND_LHANDY_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_ANGLE, //280 + MNET_UPPERBODY_IN_HALFWAY_LELBOW_AND_LHANDY_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_ANGLE, //281 + MNET_UPPERBODY_IN_HALFWAY_LELBOW_AND_LHANDY_LSHOULDERY_ANGLE, //282 + MNET_UPPERBODY_IN_HALFWAY_LELBOW_AND_LHANDY_HALFWAY_LSHOULDER_AND_LELBOWY_ANGLE, //283 + MNET_UPPERBODY_IN_HALFWAY_LELBOW_AND_LHANDY_LELBOWY_ANGLE, //284 + MNET_UPPERBODY_IN_ANGLEUSEDFOR2DROTATION_14, //285 + MNET_UPPERBODY_IN_HALFWAY_LELBOW_AND_LHANDY_LHANDY_ANGLE, //286 + MNET_UPPERBODY_IN_HALFWAY_LELBOW_AND_LHANDY_HALFWAY_NECK_AND_HIPY_ANGLE, //287 + MNET_UPPERBODY_IN_LHANDY_HIPY_ANGLE, //288 + MNET_UPPERBODY_IN_LHANDY_ENDSITE_EYE_RY_ANGLE, //289 + MNET_UPPERBODY_IN_LHANDY_ENDSITE_EYE_LY_ANGLE, //290 + MNET_UPPERBODY_IN_LHANDY_NECKY_ANGLE, //291 + MNET_UPPERBODY_IN_LHANDY_RSHOULDERY_ANGLE, //292 + MNET_UPPERBODY_IN_LHANDY_HALFWAY_RSHOULDER_AND_RELBOWY_ANGLE, //293 + MNET_UPPERBODY_IN_LHANDY_RELBOWY_ANGLE, //294 + MNET_UPPERBODY_IN_LHANDY_HALFWAY_RELBOW_AND_RHANDY_ANGLE, //295 + MNET_UPPERBODY_IN_LHANDY_RHANDY_ANGLE, //296 + MNET_UPPERBODY_IN_LHANDY_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_ANGLE, //297 + MNET_UPPERBODY_IN_LHANDY_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_ANGLE, //298 + MNET_UPPERBODY_IN_LHANDY_LSHOULDERY_ANGLE, //299 + MNET_UPPERBODY_IN_LHANDY_HALFWAY_LSHOULDER_AND_LELBOWY_ANGLE, //300 + MNET_UPPERBODY_IN_LHANDY_LELBOWY_ANGLE, //301 + MNET_UPPERBODY_IN_LHANDY_HALFWAY_LELBOW_AND_LHANDY_ANGLE, //302 + MNET_UPPERBODY_IN_ANGLEUSEDFOR2DROTATION_15, //303 + MNET_UPPERBODY_IN_LHANDY_HALFWAY_NECK_AND_HIPY_ANGLE, //304 + MNET_UPPERBODY_IN_HALFWAY_NECK_AND_HIPY_HIPY_ANGLE, //305 + MNET_UPPERBODY_IN_HALFWAY_NECK_AND_HIPY_ENDSITE_EYE_RY_ANGLE, //306 + MNET_UPPERBODY_IN_HALFWAY_NECK_AND_HIPY_ENDSITE_EYE_LY_ANGLE, //307 + MNET_UPPERBODY_IN_HALFWAY_NECK_AND_HIPY_NECKY_ANGLE, //308 + MNET_UPPERBODY_IN_HALFWAY_NECK_AND_HIPY_RSHOULDERY_ANGLE, //309 + MNET_UPPERBODY_IN_HALFWAY_NECK_AND_HIPY_HALFWAY_RSHOULDER_AND_RELBOWY_ANGLE, //310 + MNET_UPPERBODY_IN_HALFWAY_NECK_AND_HIPY_RELBOWY_ANGLE, //311 + MNET_UPPERBODY_IN_HALFWAY_NECK_AND_HIPY_HALFWAY_RELBOW_AND_RHANDY_ANGLE, //312 + MNET_UPPERBODY_IN_HALFWAY_NECK_AND_HIPY_RHANDY_ANGLE, //313 + MNET_UPPERBODY_IN_HALFWAY_NECK_AND_HIPY_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15Y_ANGLE, //314 + MNET_UPPERBODY_IN_HALFWAY_NECK_AND_HIPY_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15Y_ANGLE, //315 + MNET_UPPERBODY_IN_HALFWAY_NECK_AND_HIPY_LSHOULDERY_ANGLE, //316 + MNET_UPPERBODY_IN_HALFWAY_NECK_AND_HIPY_HALFWAY_LSHOULDER_AND_LELBOWY_ANGLE, //317 + MNET_UPPERBODY_IN_HALFWAY_NECK_AND_HIPY_LELBOWY_ANGLE, //318 + MNET_UPPERBODY_IN_HALFWAY_NECK_AND_HIPY_HALFWAY_LELBOW_AND_LHANDY_ANGLE, //319 + MNET_UPPERBODY_IN_HALFWAY_NECK_AND_HIPY_LHANDY_ANGLE, //320 + MNET_UPPERBODY_IN_ANGLEUSEDFOR2DROTATION_16, //321 + MNET_UPPERBODY_IN_NUMBER +}; + +/** @brief Programmer friendly enumerator of expected outputs + TODO: CAREFULL!*/ +enum mocapNET_Output_upperbody_enum +{ + MOCAPNET_UPPERBODY_OUTPUT_HIP_XPOSITION = 0, //0 + MOCAPNET_UPPERBODY_OUTPUT_HIP_YPOSITION, //1 + MOCAPNET_UPPERBODY_OUTPUT_HIP_ZPOSITION, //2 + MOCAPNET_UPPERBODY_OUTPUT_HIP_ZROTATION, //3 + MOCAPNET_UPPERBODY_OUTPUT_HIP_YROTATION, //4 + MOCAPNET_UPPERBODY_OUTPUT_HIP_XROTATION, //5 + MOCAPNET_UPPERBODY_OUTPUT_ABDOMEN_ZROTATION, //6 + MOCAPNET_UPPERBODY_OUTPUT_ABDOMEN_XROTATION, //7 + MOCAPNET_UPPERBODY_OUTPUT_ABDOMEN_YROTATION, //8 + MOCAPNET_UPPERBODY_OUTPUT_CHEST_ZROTATION, //9 + MOCAPNET_UPPERBODY_OUTPUT_CHEST_XROTATION, //10 + MOCAPNET_UPPERBODY_OUTPUT_CHEST_YROTATION, //11 + MOCAPNET_UPPERBODY_OUTPUT_NECK_ZROTATION, //12 + MOCAPNET_UPPERBODY_OUTPUT_NECK_XROTATION, //13 + MOCAPNET_UPPERBODY_OUTPUT_NECK_YROTATION, //14 + MOCAPNET_UPPERBODY_OUTPUT_HEAD_ZROTATION, //15 + MOCAPNET_UPPERBODY_OUTPUT_HEAD_XROTATION, //16 + MOCAPNET_UPPERBODY_OUTPUT_HEAD_YROTATION, //17 + MOCAPNET_UPPERBODY_OUTPUT_EYE_L_ZROTATION, //18 + MOCAPNET_UPPERBODY_OUTPUT_EYE_L_XROTATION, //19 + MOCAPNET_UPPERBODY_OUTPUT_EYE_L_YROTATION, //20 + MOCAPNET_UPPERBODY_OUTPUT_EYE_R_ZROTATION, //21 + MOCAPNET_UPPERBODY_OUTPUT_EYE_R_XROTATION, //22 + MOCAPNET_UPPERBODY_OUTPUT_EYE_R_YROTATION, //23 + MOCAPNET_UPPERBODY_OUTPUT_RSHOULDER_ZROTATION, //24 + MOCAPNET_UPPERBODY_OUTPUT_RSHOULDER_XROTATION, //25 + MOCAPNET_UPPERBODY_OUTPUT_RSHOULDER_YROTATION, //26 + MOCAPNET_UPPERBODY_OUTPUT_RELBOW_ZROTATION, //27 + MOCAPNET_UPPERBODY_OUTPUT_RELBOW_XROTATION, //28 + MOCAPNET_UPPERBODY_OUTPUT_RELBOW_YROTATION, //29 + MOCAPNET_UPPERBODY_OUTPUT_RHAND_ZROTATION, //30 + MOCAPNET_UPPERBODY_OUTPUT_RHAND_XROTATION, //31 + MOCAPNET_UPPERBODY_OUTPUT_RHAND_YROTATION, //32 + MOCAPNET_UPPERBODY_OUTPUT_LSHOULDER_ZROTATION, //33 + MOCAPNET_UPPERBODY_OUTPUT_LSHOULDER_XROTATION, //34 + MOCAPNET_UPPERBODY_OUTPUT_LSHOULDER_YROTATION, //35 + MOCAPNET_UPPERBODY_OUTPUT_LELBOW_ZROTATION, //36 + MOCAPNET_UPPERBODY_OUTPUT_LELBOW_XROTATION, //37 + MOCAPNET_UPPERBODY_OUTPUT_LELBOW_YROTATION, //38 + MOCAPNET_UPPERBODY_OUTPUT_LHAND_ZROTATION, //39 + MOCAPNET_UPPERBODY_OUTPUT_LHAND_XROTATION, //40 + MOCAPNET_UPPERBODY_OUTPUT_LHAND_YROTATION, //41 + MOCAPNET_UPPERBODY_OUTPUT_NUMBER +}; + +/** @brief Programmer friendly enumerator of NSDM elments*/ +enum mocapNET_NSDM_upperbody_enum +{ + MNET_NSDM_UPPERBODY_HIP = 0, //0 + MNET_NSDM_UPPERBODY_ENDSITE_EYE_R, //1 + MNET_NSDM_UPPERBODY_ENDSITE_EYE_L, //2 + MNET_NSDM_UPPERBODY_NECK, //3 + MNET_NSDM_UPPERBODY_RSHOULDER, //4 + MNET_NSDM_UPPERBODY_VIRTUAL_HALFWAY_BETWEEN_RSHOULDER_AND_RELBOW, //5 + MNET_NSDM_UPPERBODY_RELBOW, //6 + MNET_NSDM_UPPERBODY_VIRTUAL_HALFWAY_BETWEEN_RELBOW_AND_RHAND, //7 + MNET_NSDM_UPPERBODY_RHAND, //8 + MNET_NSDM_UPPERBODY_VIRTUAL_HIP_X_MINUS_0_15_Y_MINUS_0_15, //9 + MNET_NSDM_UPPERBODY_VIRTUAL_HIP_X_PLUS0_15_Y_MINUS_0_15, //10 + MNET_NSDM_UPPERBODY_LSHOULDER, //11 + MNET_NSDM_UPPERBODY_VIRTUAL_HALFWAY_BETWEEN_LSHOULDER_AND_LELBOW, //12 + MNET_NSDM_UPPERBODY_LELBOW, //13 + MNET_NSDM_UPPERBODY_VIRTUAL_HALFWAY_BETWEEN_LELBOW_AND_LHAND, //14 + MNET_NSDM_UPPERBODY_LHAND, //15 + MNET_NSDM_UPPERBODY_VIRTUAL_HALFWAY_BETWEEN_NECK_AND_HIP, //16 + MNET_NSDM_UPPERBODY_NUMBER +}; + +/** @brief This is a lookup table to immediately resolve referred Joints*/ +static const int mocapNET_ResolveJoint_upperbody[] = +{ + 0, //0 + 4, //1 + 3, //2 + 1, //3 + 5, //4 + 5, //5 + 6, //6 + 6, //7 + 7, //8 + 0, //9 + 0, //10 + 8, //11 + 8, //12 + 9, //13 + 9, //14 + 10, //15 + 1, //16 + 0//end of array +}; + +/** @brief This is a lookup table to immediately resolve referred Joints of second targets*/ +static const int mocapNET_ResolveSecondTargetJoint_upperbody[] = +{ + 0, //0 + 0, //1 + 0, //2 + 0, //3 + 0, //4 + 6, //5 + 0, //6 + 7, //7 + 0, //8 + 0, //9 + 0, //10 + 0, //11 + 9, //12 + 0, //13 + 10, //14 + 0, //15 + 0, //16 + 0//end of array +}; + +/** @brief This is the configuration of NSDM elements : + * A value of 0 is a normal 2D point + * A value of 1 is a 2D point plus some offset + * A value of 2 is a virtual point between two 2D points */ +static const int mocapNET_ArtificialJoint_upperbody[] = +{ + 0, //0 + 0, //1 + 0, //2 + 0, //3 + 0, //4 + 2, //5 + 0, //6 + 2, //7 + 0, //8 + 1, //9 + 1, //10 + 0, //11 + 2, //12 + 0, //13 + 2, //14 + 0, //15 + 2, //16 + 0//end of array +}; + +/** @brief These are X offsets for artificial joints of type 1 ( see mocapNET_ArtificialJoint_upperbody )*/ +static const float mocapNET_ArtificialJointXOffset_upperbody[] = +{ + 0, //0 + 0, //1 + 0, //2 + 0, //3 + 0, //4 + 0, //5 + 0, //6 + 0, //7 + 0, //8 + -0.15, //9 + 0.15, //10 + 0, //11 + 0, //12 + 0, //13 + 0, //14 + 0, //15 + 0, //16 + 0//end of array +}; + +/** @brief These are Y offsets for artificial joints of type 1 ( see mocapNET_ArtificialJoint_upperbody )*/ +static const float mocapNET_ArtificialJointYOffset_upperbody[] = +{ + 0, //0 + 0, //1 + 0, //2 + 0, //3 + 0, //4 + 0, //5 + 0, //6 + 0, //7 + 0, //8 + -0.15, //9 + -0.15, //10 + 0, //11 + 0, //12 + 0, //13 + 0, //14 + 0, //15 + 0, //16 + 0//end of array +}; + +/** @brief These are 2D Joints that are used as starting points for scaling vectors*/ +static const int mocapNET_ScalingStart_upperbody[] = +{ + 0, //0 + 0, //1 + 0//end of array +}; + +/** @brief These are 2D Joints that are used as ending points for scaling vectors*/ +static const int mocapNET_ScalingEnd_upperbody[] = +{ + 5, //0 + 8, //1 + 0//end of array +}; + +/** @brief These is a 2D Joints that is used as alignment for the skeleton*/ +static const int mocapNET_AlignmentStart_upperbody[] = +{ + 0, //0 + 0//end of array +}; + +/** @brief These is a 2D Joints that is used as alignment for the skeleton*/ +static const int mocapNET_AlignmentEnd_upperbody[] = +{ + 1, //0 + 0//end of array +}; + +/** @brief This function can be used to debug NSDM input and find in a user friendly what is missing..!*/ +static int upperbodyCountMissingNSDMElements(std::vector mocapNETInput,int verbose) +{ + unsigned int numberOfZeros=0; + for (int i=0; i skeletonSerialized %s\n ",mocapNET_upperbody[i],labels[i]); + } +} + +/** @brief This function returns the euclidean distance between two input 2D joints and zero if either of them is invalid*/ +static float getJoint2DDistance_UPPERBODY(std::vector in,int jointA,int jointB) +{ + float aX=in[jointA*3+0]; + float aY=in[jointA*3+1]; + float bX=in[jointB*3+0]; + float bY=in[jointB*3+1]; + if ( ((aX==0) && (aY==0)) || ((bX==0) && (bY==0)) ) { + return 0.0; + } + + + float xDistance=(float) bX-aX; + float yDistance=(float) bY-aY; + return sqrt( (xDistance*xDistance) + (yDistance*yDistance) ); +} +/* +static std::vector upperbodyCreateNDSM(std::vector in,float alignmentAngle2D,int havePositionalElements,int haveAngularElements,int doNormalization) +{ + std::vector result; + int secondTargetJointID; + float sIX,sIY,sJX,sJY; + for (int i=0; i0) + { + unsigned int numberOfDistanceSamples=0; + float sumOfDistanceSamples=0.0; + for ( int i=0; i0.0) + { + numberOfDistanceSamples=numberOfDistanceSamples+1; + sumOfDistanceSamples=sumOfDistanceSamples+distance; + } + } +//------------------------------------------------------------------------------------------------- + float scaleDistance=1.0; +//------------------------------------------------------------------------------------------------- + if (numberOfDistanceSamples>0) + { + scaleDistance=(float) sumOfDistanceSamples/numberOfDistanceSamples; + } +//------------------------------------------------------------------------------------------------- + if (scaleDistance!=1.0) + { + for (int i=0; imaxValue) { + maxValue=result[i]; + } + } + fprintf(stderr,"Original Min Value %0.2f, Max Value %0.2f \n",minValue,maxValue); + + + unsigned int iJointID=mocapNET_AlignmentStart_upperbody[0]; + unsigned int jJointID=mocapNET_AlignmentEnd_upperbody[0]; + float aX=in[iJointID*3+0]; + float aY=in[iJointID*3+1]; + float bX=in[jJointID*3+0]; + float bY=in[jJointID*3+1]; + float alignmentAngle=getAngleToAlignToZero_tools(aX,aY,bX,bY); + for (int i=0; imaxValue) { + maxValue=result[i]; + } + } + fprintf(stderr,"Aligned Min Value %0.2f, Max Value %0.2f \n",minValue,maxValue); + + + } +//------------------------------------------------------------------------------------------------- + + + } //If normalization is enabled.. + + +//New normalization code that overrides diagonal of Matrix + unsigned int elementID=0; + unsigned int firstJointID=mocapNET_ResolveJoint_upperbody[0]; + for (unsigned int i=0; i0) && (richDiagonal_upperbody) ) + { + unsigned int jJointID=mocapNET_ResolveJoint_upperbody[j]; + result[elementID]=getJoint2DDistance_UPPERBODY(in,firstJointID,jJointID); + } + } + elementID+=1; + } + } + return result; +} + +*/ \ No newline at end of file diff --git a/src/MocapNET4/MocapNETLib4/NSxM/calculations.c b/src/MocapNET4/MocapNETLib4/NSxM/calculations.c new file mode 100644 index 0000000..5069948 --- /dev/null +++ b/src/MocapNET4/MocapNETLib4/NSxM/calculations.c @@ -0,0 +1,137 @@ +#include "calculations.h" + +#include +#include + +#define NORMAL "\033[0m" +#define BLACK "\033[30m" /* Black */ +#define RED "\033[31m" /* Red */ +#define GREEN "\033[32m" /* Green */ +#define YELLOW "\033[33m" /* Yellow */ + + +const float goFromRadToDegrees=(float) 180.0 / M_PI; +const float goFromDegreesToRad=(float) M_PI / 180.0; + + +/** @brief This function returns the euclidean distance between two input 2D joints and zero if either of them is invalid*/ +float getJoint2DDistance_tools(float aX,float aY,float bX,float bY) +{ + float xDistance=(float) bX-aX; + float yDistance=(float) bY-aY; + return (float) sqrt( (xDistance*xDistance) + (yDistance*yDistance) ); +} + + +float getAngleToAlignToZero_tools(float aX,float aY,float bX,float bY) +{ + if ( (aX==bX) && (aY==bY) ) { return 0; } + + + //Bigger magnitudes.. + aX=100*aX; + aY=100*aY; + bX=100*bX; + bY=100*bY; + + //We have points a, b and c and we want to calculate angle b + float lengthBetweenAAndB = getJoint2DDistance_tools(aX,aY,bX,bY); + + + //We align vertically.. , Point C is B offset in Y direction + float cX = bX; + float cY = bY - lengthBetweenAAndB; + + //fprintf(stderr,"We want to align A(%0.2f,%0.2f) to C(%0.2f,%0.2f) with pivot B(%0.2f,%0.2f)\n",aX,aY,cX,cY,bX,bY); + //fprintf(stderr,"length AB = %0.2f\n",lengthBetweenAAndB); + //fprintf(stderr,"bY = %0.2f\n",bY); + //fprintf(stderr,"cY = %0.2f = %0.2f - %0.2f\n",cY,bY,lengthBetweenAAndB); + + + //Calulate vector a->b + float abX = bX - aX; + float abY = bY - aY; + + //calculate vector c->b + float cbX = bX - cX; + float cbY = bY - cY; + + + float dot = (abX * cbX + abY * cbY); // dot product + float cross = (abX * cbY - abY * cbX); // cross product + + float alpha = atan2(cross, dot); + + //fprintf(stderr,"Angle is %0.2f rad or %0.2f degrees \n",alpha,alpha*goFromRadToDegrees); + return (float) alpha;// * goFromRadToDegrees ; +} + + + +float getAngleToAlignToZero(float *positions,unsigned int centerJoint,unsigned int referenceJoint) +{ + //We have points a, b and c and we want to calculate angle b + float aX= positions[referenceJoint*3+0]; + float aY= positions[referenceJoint*3+1]; + + float bX= positions[centerJoint*3+0]; + float bY= positions[centerJoint*3+1]; + + return getAngleToAlignToZero_tools(aX,aY,bX,bY); +} + + + +int rotate2DPointsBasedOnJointAsCenter(float * positions,unsigned int positionsLength,float angle,unsigned int centerJoint) +{ + if (positionsLength%3!=0) + { + fprintf(stderr,RED "rotate2DPointsBasedOnJointAsCenter: incorrect positions.. \n" NORMAL); + return 0; + } + + if (positionsLength<=centerJoint*3) + { + fprintf(stderr,RED "rotate2DPointsBasedOnJointAsCenter: centerJoint out of bounds.. \n" NORMAL); + return 0; + } + + float s = sin((float) angle * goFromDegreesToRad ); + float c = cos((float) angle * goFromDegreesToRad ); + + float cx=positions[centerJoint*3+0]; + float cy=positions[centerJoint*3+1]; + float cVisibility=positions[centerJoint*3+2]; + + if (cVisibility==0.0) + { + fprintf(stderr,RED "rotate2DPointsBasedOnJointAsCenter: cannot work without pivot joint.. \n" NORMAL); + return 0; + } + + for (unsigned int jID=0; jID ",jX,jY,cx,cy,angle); + + //Translate point back to origin: + jX -= cx; + jY -= cy; + + //Rotate point + float xnew = jX * c - jY * s; + float ynew = jX * s + jY * c; + + //Translate point back: + positions[jID*3+0] = xnew + cx; + positions[jID*3+1] = ynew + cy; + + //fprintf(stderr,"%0.2f,%0.2f\n",positions[jID*3+0],positions[jID*3+1]); + } + + + return 1; +} + diff --git a/src/MocapNET4/MocapNETLib4/NSxM/calculations.h b/src/MocapNET4/MocapNETLib4/NSxM/calculations.h new file mode 100644 index 0000000..5ee5469 --- /dev/null +++ b/src/MocapNET4/MocapNETLib4/NSxM/calculations.h @@ -0,0 +1,27 @@ +/** @file calculations.h + * @brief calculations used for descriptors + * @author Ammar Qammaz (AmmarkoV) + */ + +#ifndef CALCULATIONS_H_INCLUDED +#define CALCULATIONS_H_INCLUDED + + +#ifdef __cplusplus +extern "C" +{ +#endif + + +#include + +float getJoint2DDistance_tools(float aX,float aY,float bX,float bY); + +#ifdef __cplusplus +} +#endif + + + + +#endif diff --git a/src/MocapNET4/MocapNETLib4/PCA/PCA.h b/src/MocapNET4/MocapNETLib4/PCA/PCA.h new file mode 100644 index 0000000..b0d89a2 --- /dev/null +++ b/src/MocapNET4/MocapNETLib4/PCA/PCA.h @@ -0,0 +1,446 @@ +/** @file PCA.h + * @brief An implementation of a PCA data loader + * @author Ammar Qammaz (AmmarkoV) + */ + +#ifndef PCA_H_INCLUDED +#define PCA_H_INCLUDED + + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include +#include "../JSON/nxjson.h" +#include "../tools.h" + + +#define NORMAL "\033[0m" +#define BLACK "\033[30m" /* Black */ +#define RED "\033[31m" /* Red */ +#define GREEN "\033[32m" /* Green */ +#define YELLOW "\033[33m" /* Yellow */ + +struct eigenVectorOpt +{ + float __attribute__((aligned(16))) * value; +}; + +struct complexNumber +{ + float realPart; + float imaginaryPart; +}; + +struct eigenVector +{ + struct complexNumber * value; +}; + +struct PCAData +{ + unsigned int numberOfSamplesUsedToCreatePCA; + unsigned int numberOfEigenValues; + float mean; + float std; + struct complexNumber * eigenValues; + struct eigenVector * eigenVectors; + float * screeProportion; + float * screeCumulative; +}; + +//Complex Numbers +//http://ebooks.edu.gr/ebooks/v/html/8547/2754/Mathimatika-B-Lykeiou-ThSp_html-apli/index5_2.html +static struct complexNumber addComplexNumbers(struct complexNumber a,struct complexNumber b) +{ + struct complexNumber result={0}; + result.realPart = a.realPart + b.realPart; + result.imaginaryPart = a.imaginaryPart + b.imaginaryPart; + return result; +} + +static struct complexNumber multiplyComplexNumbers(struct complexNumber a,struct complexNumber b) +{ + struct complexNumber result={0}; + // (A + B i) * (C + D i) = (AC-BD) + (AD+BC) i + // (a.realPart + a.imaginaryPart i) * (b.realPart + b.imaginaryPart i) = (a.realPart b.realPart - a.imaginaryPart b.imaginaryPart) + (a.realPart b.imaginaryPart + a.imaginaryPart b.realPart) i + result.realPart = (a.realPart*b.realPart) - (a.imaginaryPart*b.imaginaryPart); + result.imaginaryPart = (a.realPart*b.imaginaryPart) + (a.imaginaryPart*b.realPart); + return result; +} + +static char * recognizeComplexNumberStart(char * complexNumber) +{ + int l = strlen(complexNumber); + char * res = complexNumber; + //All Complex Numbers end with j + //------------------------------- + //We expect something like : + // -4.1106844e-12-4.560428e-12j + // or + // -4.1106844e-12-4.560428e+12j + // or + // -4.1106844e-12-4.560428j + // or + // -4.1106844e-12+48j + //------------------------------- + if (l>2) + { + if (complexNumber[l-1]=='j') + { + l=l-2; + while (l>0) + { + if ( + (complexNumber[l]=='+') || + (complexNumber[l]=='-') + ) + { + //We have reache a +/- symbol there are three cases : + //Case A ) if the previous character is an e then it is a scientific notation complex number! + //Case B ) if not then we found the complex number start! + //Case C ) if l is 0 then we reached the start of the string! + if (l>0) + { + if (complexNumber[l-1]!='e') + { + //This is not a scientific notation so Case B we found our result! + res = complexNumber+l; + break; + } + } + } + l=l-1; + } + } + } + return res; +} + + + + + + + +static struct complexNumber parseComplexNumber(const char * complexNumberString) +{ + struct complexNumber result={0}; + //--------------------------------------------------------------------------------------------- + if ( complexNumberString == 0 ) { return result; } + if (complexNumberString[0]=='(') { complexNumberString++; } //Point to second element to skip ( + + char localBuffer[128]; + snprintf(localBuffer,128,"%s",complexNumberString); + + + float imaginaryPart = 0.0; + char * seperator = strchr(localBuffer,')'); + if (seperator!=0) { *seperator = 0; } //Trim last ) + + + seperator = strstr(localBuffer,"+0j"); + if (seperator!=0) + { + //This is the easy case which means that the value is a clear float value and the +0j can be easily discarded! + *seperator = 0; + } else + { + unsigned int l = strlen(localBuffer); + if (localBuffer[l-1]=='j') + { + fprintf(stderr,"Parsing complex number `%s`\n",localBuffer); + seperator = recognizeComplexNumberStart(localBuffer); + if (seperator!=0) + { + *seperator = 0; + fprintf(stderr,"Imaginary part `%s` / keeping real part `%s` \n",seperator+1,localBuffer); + char * imaginaryPartString = seperator+1; + int lastCharacter = strlen(imaginaryPartString); + if (imaginaryPartString[lastCharacter-1]=='j') + { imaginaryPartString[lastCharacter-1]=0; } + imaginaryPart = strtof(imaginaryPartString,NULL); + } + } + } + + float cleanedFloat = strtof(localBuffer,NULL); + //fprintf(stderr,"Cleaned float is %f \n",cleanedFloat); + if (cleanedFloat!=cleanedFloat) + { + //If we got a NaN value then clean it.. + cleanedFloat = 0.0; + } + + + result.realPart = cleanedFloat; + result.imaginaryPart = imaginaryPart; + + return result; +} + + +static int unloadPCAData(struct PCAData* pca) +{ + if (pca!=0) + { + if (pca->screeProportion!=0) { free(pca->screeProportion); pca->screeProportion=0; } + if (pca->screeCumulative!=0) { free(pca->screeCumulative); pca->screeCumulative=0; } + if (pca->eigenValues!=0) { free(pca->eigenValues); pca->eigenValues=0; } + if (pca->eigenVectors!=0) + { + for (int i=0; inumberOfEigenValues; i++) + { + free(pca->eigenVectors[i].value); + } + free(pca->eigenVectors); + } + return 1; + } + return 0; +} + +static int loadPCADataFromJSON(struct PCAData* output, const char * jsonFilename) +{ + fprintf(stderr,"Loading PCA file %s ...\n",jsonFilename); + unsigned int inputLength=0; + char* input = readFileToMemory(jsonFilename,&inputLength); + if (input!=0) + { + //fprintf(stderr,"JSON DATA %s ...\n",input); + fprintf(stderr,"Parsing %s ...\n",jsonFilename); + const nx_json* json=nx_json_parse_utf8(input); + + //------------------------------------------------------------------- + const nx_json* j = nx_json_get(json,"numberOfSamplesFittedOn"); + fprintf(stderr,"key(%s)/type(%u)\n",j->key,j->type); + output->numberOfSamplesUsedToCreatePCA = (unsigned int) atoi(j->text_value); + //------------------------------------------------------------------- + j = nx_json_get(json,"expectedInputs"); + fprintf(stderr,"key(%s)/type(%u)\n",j->key,j->type); + output->numberOfEigenValues = (unsigned int) atoi(j->text_value); + //------------------------------------------------------------------- + j = nx_json_get(json,"std"); + fprintf(stderr,"key(%s)/type(%u)\n",j->key,j->type); + output->std = (float) atof(j->text_value); + j = nx_json_get(json,"mean"); + fprintf(stderr,"key(%s)/type(%u)\n",j->key,j->type); + output->mean = (float) atof(j->text_value); + //------------------------------------------------------------------- + + //Our Summary..! + //---------------------------------------------------------------------------------------------------------------------------- + fprintf(stderr,"Number Of Samples %u\n",output->numberOfSamplesUsedToCreatePCA); + fprintf(stderr,"Number Of Eigen Values %u\n",output->numberOfEigenValues); + fprintf(stderr,"Mean %0.2f / Std %0.2f\n",output->mean,output->std ); + + //We have now allocated enough space and are ready to parse all incoming values.. + //---------------------------------------------------------------------------------------------------------------------------- + output->eigenValues = (struct complexNumber*) malloc(sizeof(struct complexNumber) * output->numberOfEigenValues); + output->eigenVectors = (struct eigenVector*) malloc(sizeof(struct eigenVector) * output->numberOfEigenValues); + if (output->eigenVectors) + { + for (int i=0; inumberOfEigenValues; i++) + { + output->eigenVectors[i].value = (struct complexNumber*) malloc(sizeof(struct complexNumber) * output->numberOfEigenValues); + } + } + output->screeProportion = (float*) malloc(sizeof(float) * output->numberOfEigenValues); + output->screeCumulative = (float*) malloc(sizeof(float) * output->numberOfEigenValues); + //---------------------------------------------------------------------------------------------------------------------------- + + + j = nx_json_get(json,"eigenvalues"); + fprintf(stderr,"We encountered %u eigenvalues (header says %u) \n",j->length,output->numberOfEigenValues); + if (j->length == output->numberOfEigenValues) + { + //We have a correct number of eigenvalues so let's read them! + for (int idx=0; idxnumberOfEigenValues; idx++) + { + const nx_json* item = nx_json_item(j,idx); + //fprintf(stderr,"key(%s)/index(%u)/type(%u)\n",j->key,idx,item->type); + output->eigenValues[idx] = parseComplexNumber(item->text_value); + } + } + + + const nx_json* jY = nx_json_get(json,"eigenvectors"); + if (jY->length == output->numberOfEigenValues) + { + fprintf(stderr,"We encountered %u eigenvectors (header says %u) \n",jY->length,output->numberOfEigenValues); + //We have a correct number of eigenvalues so let's read them! + for (int idy=0; idynumberOfEigenValues; idy++) + { + const nx_json* itemY = nx_json_item(jY,idy); + if (itemY->length == output->numberOfEigenValues) + { + for (int idx=0; idxnumberOfEigenValues; idx++) + { + const nx_json* itemX = nx_json_item(itemY,idx); + //fprintf(stderr,"key(%s)/index(%u)/type(%u)\n",j->key,idx,item->type); + output->eigenVectors[idx].value[idy] = parseComplexNumber(itemX->text_value); + } + } else + { + fprintf(stderr,"Eigen Vector %d has an incorrect number of values (%u)\n",idy,itemY->length); + } + } + } + + + j = nx_json_get(json,"scree_proportion"); + fprintf(stderr,"We encountered %u scree_proportions (header says %u) \n",j->length,output->numberOfEigenValues); + if (j->length == output->numberOfEigenValues) + { + //We have a correct number of eigenvalues so let's read them! + for (int idx=0; idxnumberOfEigenValues; idx++) + { + const nx_json* item = nx_json_item(j,idx); + //fprintf(stderr,"key(%s)/index(%u)/type(%u)\n",j->key,idx,item->type); + output->screeProportion[idx] = strtof(item->text_value,NULL); + } + } + + + j = nx_json_get(json,"scree_cumulative"); + fprintf(stderr,"We encountered %u scree_cumulatives (header says %u) \n",j->length,output->numberOfEigenValues); + if (j->length == output->numberOfEigenValues) + { + //We have a correct number of eigenvalues so let's read them! + for (int idx=0; idxnumberOfEigenValues; idx++) + { + const nx_json* item = nx_json_item(j,idx); + //fprintf(stderr,"key(%s)/index(%u)/type(%u)\n",j->key,idx,item->type); + output->screeCumulative[idx] = strtof(item->text_value,NULL); + } + } + + nx_json_free(json); + free(input); + + return 1; + } + return 0; +} + + +float dotProduct(float * vect_A, float * vect_B, int n) +{ + float product = 0.0; + + for (int i = 0; i < n; i++) + { product += vect_A[i] * vect_B[i]; } + + return product; +} + + + +static int doPCATransform(float * output,int * outputSize,struct PCAData* pca,float * inputRaw,int inputSize,int selectedPCADimensions) +{ + if (pca==0) { return 0; } + if (pca->numberOfEigenValues!=inputSize) { fprintf(stderr, RED "PCA: Shape given as input (%d,) is not aligned with PCA loaded (%u,%d) \n" NORMAL,inputSize,pca->numberOfEigenValues,*outputSize); return 0; } + + float mean = pca->mean; + float std = pca->std; + if (std == 0.0 ) { std=1.0; } //Don't ever divide by zero + + fprintf(stderr," sample = list()\n"); + for (int i=0; iinputSize) + { + selectedPCADimensions = inputSize; + } + + fprintf(stderr,"We want dot product of %u dimensions : \n",selectedPCADimensions); + fprintf(stderr,"Input of size 0->%u\n",inputSize); + fprintf(stderr,"EigenVector of size 0->%u\n",pca->numberOfEigenValues); + + /* + * input is 1 x 458 + eigenvectors is 458 x 458 + result is 1 x 210 + + data is 1 x 461 + eigenvectors is 461 x 461 + result is 1 x 210 + a = [[0, 1 , 2]] +b = [[4, 1], [3, 2], [10,10] ] +#[[23 22]] +print(np.dot(a,b)) + * ./MocapNET4TestD + + * python3 DNN_Tensorflow2/principleComponentAnalysis.py + */ + + for (int i=0; inumberOfEigenValues; j++) + { + //fprintf(stderr,"%0.2f * %0.2f \n",input[j],pca->eigenVectors[i].value[j]); + //output[i] += input[j] * pca->eigenVectors[i].value[j]; + //output[i] += input[j] * pca->eigenVectors[i].value[j].realPart; + struct complexNumber thisOutputComplex = multiplyComplexNumbers(inputComplex[j],pca->eigenVectors[i].value[j]); + outputComplex[i].realPart += thisOutputComplex.realPart; + outputComplex[i].imaginaryPart += thisOutputComplex.imaginaryPart; + } + //--------------------------------- + } + + //We have gone through all of the complex arithmetic, now we will keep only the real part + for (int i=0; i Median is ",median,"Mean is ",mean," Std is ",std," Var is ",var) + + sys.exit(0) + +class PCA(): + def __init__(self, + inputData:np.array=np.array([]), + savedFile:str="" + ): + self.mean = 0.0 + self.std = 1.0 + self.eigenvalues = np.array([]) + self.eigenvectors = np.array([]) + self.proportional = list() + self.cumulative = list() + self.numberOfSamplesFittedOn = 0 + self.expectedInputs = 0 + + if (savedFile!=""): + self.load(savedFile) + elif inputData.size != 0: + self.fit(inputData) + else: + print("No PCA input given..!") + + def ok(self): + return self.numberOfSamplesFittedOn!=0 + + def getNumberOfExpectedSamples(self): + #return len(self.eigenvalues) + return np.size(self.eigenvalues, axis = 0) + + def fit(self,data): + #doPCAUsingSKLearn(data,"Test") + #getStatsPerColumn(data) + #print(data) + + self.numberOfSamplesFittedOn = data.shape[0] + + print("Doing PCA fit on ",self.numberOfSamplesFittedOn) + print(" please wait .. ") + + #Standardize data + #------------------------------------------------------------------------------------------------- + self.mean = data.mean() + data = data - self.mean + # Normalize + self.std = data.std() + if (self.std!=0.0): + data = data / self.std + #print('Data Mean : ',self.mean,'STD: ',self.std) + #------------------------------------------------------------------------------------------------- + + #Take the matrix, transpose it, and multiply the transposed matrix. This is the covariance matrix. + covarianceMatrix = np.dot(data.T,data) + + #Get an array of computed eigenvalues and a matrix whose columns are the normalized eigenvectors corresponding to the eigenvalues in that order. + #In this step it is important to make sure that the eigenvalues and its eigenvectors are sorted in descending order (from largest to smallest). Sort the eigenvalues and then the eigenvectors, accordingly. + self.eigenvalues, self.eigenvectors = np.linalg.eig(covarianceMatrix) + + #Sort eigenvectors according to eigenvalues + idx = self.eigenvalues.argsort()[::-1] + self.eigenvalues = self.eigenvalues[idx] + self.eigenvectors = self.eigenvectors[:,idx] + + #Assign P to the matrix of eigenvectors and D to the diagonal matrix with eigenvalues on the diagonal and values of zero everywhere else. + #The eigenvalues on the diagonal of D will be associated with the corresponding column in P. + D = np.diag(self.eigenvalues) + P = self.eigenvectors + + self.expectedInputs = len(self.eigenvalues) + + #1. Calculate the proportion of variance explained by each feature + sumOfEigenvalues = np.sum(self.eigenvalues) + self.proportional = [i/sumOfEigenvalues for i in self.eigenvalues] + #2. Calculate the cumulative variance + self.cumulative = [np.sum(self.proportional[:i+1]) for i in range(len(self.proportional))] + + def transform(self,data,selectedPCADimensions=0): + if (self.numberOfSamplesFittedOn==0): + print("Can't transform input with no PCA loaded ..") + return data + #Normalize input data + data = data - self.mean + if (self.std!=0.0): + data = data / self.std + #Do transform.. + if (selectedPCADimensions==0): + #Transform using all PCA components + return np.dot(data,self.eigenvectors) + else: + #print("eigenvectors is ",eigenvectors.shape[0]," x ",eigenvectors.shape[1]) + return data.dot(self.eigenvectors[:,:selectedPCADimensions]) + + def save(self,filename): + print("Saving PCA to ",filename) + outputDict = dict() + #------------------------------------------ + outputDict["numberOfSamplesFittedOn"]= str(self.numberOfSamplesFittedOn) + outputDict["expectedInputs"] = str(self.expectedInputs) + outputDict["mean"] = str(self.mean) + outputDict["std"] = str(self.std) + outputDict["eigenvalues"] = list() + outputDict["eigenvectors"] = list() + outputDict["scree_proportion"] = list() + outputDict["scree_cumulative"] = list() + #------------------------------------------ + for v in range(0,len(self.proportional)): + outputDict["scree_proportion"].append(str(self.proportional[v])) + outputDict["scree_cumulative"].append(str(self.cumulative[v])) + #------------------------------------------ + print("eigenvalues ",self.eigenvalues.shape[0]) + for v in range(0,self.eigenvalues.shape[0]): + outputDict["eigenvalues"].append(str(self.eigenvalues[v])) + #------------------------------------------ + print("eigenvectors ",self.eigenvectors.shape[0]," x ",self.eigenvectors.shape[1]) + for r in range(0,self.eigenvectors.shape[0]): + thisRow = list() + for c in range(0,self.eigenvectors.shape[1]): + thisRow.append(str(self.eigenvectors[r,c])) + outputDict["eigenvectors"].append(thisRow) + #------------------------------------------ + import json + json_obj = json.dumps(outputDict) + file = open(filename,'w',encoding="utf-8") + file.write(json_obj) + file.close() + + def load(self,filename): + print("Loading PCA from ",filename) + import json + file = open(filename,'r',encoding="utf-8") + data = json.load(file) + #----------------------------------------------------- + self.numberOfSamplesFittedOn = int(data["numberOfSamplesFittedOn"]) + self.expectedInputs = int(data["expectedInputs"]) + self.mean = float(data["mean"]) + self.std = float(data["std"]) + #----------------------------------------------------- + numberOfEigenValues = len(data["eigenvalues"]) + print("Eigen values = ",numberOfEigenValues) + self.eigenvalues = np.full([numberOfEigenValues],fill_value=0,dtype=np.complex_,order='C') + for i in range(0,numberOfEigenValues): + self.eigenvalues[i] = complex(data["eigenvalues"][i]) + #----------------------------------------------------- + numberOfEigenVectors = len(data["eigenvectors"]) + print("Eigen vectors = ",numberOfEigenVectors) + self.eigenvectors = np.full([numberOfEigenVectors,numberOfEigenVectors],fill_value=0,dtype=np.complex_,order='C') + for r in range(0,numberOfEigenVectors): + for c in range(0,numberOfEigenVectors): + self.eigenvectors[r,c] = complex(data["eigenvectors"][r][c]) + #----------------------------------------------------- + file.close() + return self.mean,self.std,self.eigenvalues,self.eigenvectors + + + def visualize(self,data,saveToFile="",onlyScreePlotNDimensions=0,label="PCA",colors=list(),colorLabel="Highlighting PC-4",viewAzimuth=45,viewElevation=45,showScree=1): + import matplotlib.pyplot as plt + + font = {'family' : 'normal', + 'weight' : 'bold', + 'size' : 28} + + plt.rc('font', **font) + plt.rc('xtick', labelsize=15) + plt.rc('ytick', labelsize=15) + # === Plot ========================================================================= + fig = plt.figure() + fig.set_size_inches(19.2, 10.8, forward=True) + + if (showScree==1): + ax2 = fig.add_subplot(1, 2, 1) + ax1 = fig.add_subplot(1, 2, 2,projection='3d') + else: + ax1 = fig.add_subplot(1, 1, 1,projection='3d') + #=================================================================================== + + #Number of PCA components to plot on first plot (our plot is 3D so max is 4 if we dont have a color ..! ) + keepNDimensions = 3 + if (len(colors)==0): + keepNDimensions = 4 + + #Do transform of our input using the PCA dimensions as new basis + #=================================================================================== + transformedData = self.transform(data,selectedPCADimensions=keepNDimensions).real + #=================================================================================== + + if (len(colors)==0): + colors = transformedData[:,3] + colorLabel = "highlighting PC-4" + else: + print("Using provided set of colorValues") + keepNDimensions = 3 + + #If there is no limit on Scree plot dimensions then plot all + if (onlyScreePlotNDimensions==0): + onlyScreePlotNDimensions = len(eigenvalues) + #=================================================================================== + plottedEigenValues = self.eigenvalues + plottedEigenValues=list() + for i in range(0,onlyScreePlotNDimensions): + plottedEigenValues.append(self.eigenvalues[i]) + #=================================================================================== + #1. Calculate the proportion of variance explained by each feature + sum_eigenvalues = np.sum(plottedEigenValues) + prop_var = [i/sum_eigenvalues for i in plottedEigenValues] + #2. Calculate the cumulative variance + cum_var = [np.sum(prop_var[:i+1]) for i in range(len(prop_var))] + #=================================================================================== + + ax1.view_init(viewAzimuth,viewElevation) + #=================================================================================== + ax1.scatter(transformedData[:,0],transformedData[:,1],transformedData[:,2],c=colors) + #=================================================================================== + + # Adding title, xlabel and ylabel + ax1.set_title('PCA %s %s '%(label,colorLabel)) # Title of the plot + ax1.set_xlabel('PC-1 (%0.2f %%) '% (100.0*float(prop_var[0])),labelpad=30) # X-Label + ax1.set_ylabel('PC-2 (%0.2f %%) '% (100.0*float(prop_var[1])),labelpad=30) # Y-Label + ax1.set_zlabel('PC-3 (%0.2f %%) '% (100.0*float(prop_var[2])),labelpad=30) # Z-Label + #ax1.tick_params(axis='x', pad=5) #fine tune numbers of plot + #=================================================================================== + #=================================================================================== + #=================================================================================== + if (showScree==1): + # Plot scree plot from PCA + x_labels = ['PC{}'.format(i+1) for i in range(len(prop_var))] + ax2.plot(x_labels, prop_var, marker='o', markersize=6, color='skyblue', linewidth=2, label='Proportion of variance') + ax2.plot(x_labels, cum_var, marker='o', color='orange', linewidth=2, label="Cumulative variance") + ax2.legend() + ax2.set_title('Scree plot %s '%label) + ax2.set_xlabel('Principal components') + ax2.set_ylabel('Proportion of variance') + #=================================================================================== + #=================================================================================== + #=================================================================================== + + plt.subplots_adjust(left=0.05, right=0.95, top=0.95, bottom=0.08) + + if (saveToFile!=""): + fig.savefig(saveToFile) + else: + plt.show() + + +if __name__ == '__main__': + pca = PCA(savedFile="../../../../dataset/combinedModel/mocapnet4/mode1/1.0/step1_upperbody_all/upperbody_all.pca") + inptR = [1.0] * 458 + inpt =np.asarray(inptR,dtype=np.float32) + outLength = 210 + out = pca.transform(inpt,selectedPCADimensions=outLength) + for i in range(0,outLength): + print("%u = %0.6f" % (i,out[i])) + diff --git a/src/MocapNET4/MocapNETLib4/config.h b/src/MocapNET4/MocapNETLib4/config.h new file mode 100644 index 0000000..9fe3f2e --- /dev/null +++ b/src/MocapNET4/MocapNETLib4/config.h @@ -0,0 +1,46 @@ +#ifndef MOCAPNET_CONFIGURATION_H_INCLUDED +#define MOCAPNET_CONFIGURATION_H_INCLUDED + +#ifdef __cplusplus +extern "C" +{ +#endif + +//Neural network orientations centered around 0 +#define NN_ORIENTATIONS_TRAINED_AROUND_ZERO_AND_REQUIRE_TRICK 0 + +//Also swap bvh rotations before IK step +#define APPLY_BVH_FIX_TO_IK_INPUT 0 + +//Test swapped +#define SWAP_LEFT_RIGHT_ENSEMBLES 0 + +//Hands mode ( 1 / 3 (deprecated) / 5 ) +#define HANDS_MODE 1 + +//Use flip for RHand Regression..! +#define RHAND_FLIP 1 + + +//Limits synced to scripts/createRandomizedDatset.sh +const float FRONT_MIN_ORIENTATION = -45.0; +const float FRONT_MAX_ORIENTATION = 45.0; +//-------------------------------- +const float BACK_MIN_ORIENTATION = 135.0; +const float BACK_MAX_ORIENTATION = 225.0; +const float BACK_ALT_MIN_ORIENTATION = -225; +const float BACK_ALT_MAX_ORIENTATION = -135; +//-------------------------------- +const float LEFT_MIN_ORIENTATION = -135.0; +const float LEFT_MAX_ORIENTATION = -45.0; +//-------------------------------- +const float RIGHT_MIN_ORIENTATION = 45.0; +const float RIGHT_MAX_ORIENTATION = 135.0; +//-------------------------------- + + +#ifdef __cplusplus +} +#endif + +#endif // MOCAPNET_CONFIGURATION_H_INCLUDED diff --git a/src/MocapNET4/MocapNETLib4/mocapnet4.cpp b/src/MocapNET4/MocapNETLib4/mocapnet4.cpp new file mode 100644 index 0000000..1917d2a --- /dev/null +++ b/src/MocapNET4/MocapNETLib4/mocapnet4.cpp @@ -0,0 +1,61 @@ +//MOCAPNET2 ------------------------------------ +#include "../MocapNETLib4/mocapnet4.h" +//---------------------------------------------- +#include "../MocapNETLib4/config.h" +#include "../MocapNETLib4/JSON/readModelConfiguration.h" +//---------------------------------------------- + +#include "tools.h" +#include "../../../dependencies/nxjson/nxjson.h" + +#include +#include + +#define NORMAL "\033[0m" +#define BLACK "\033[30m" /* Black */ +#define RED "\033[31m" /* Red */ +#define GREEN "\033[32m" /* Green */ +#define YELLOW "\033[33m" /* Yellow */ + + + + +int loadMocapNET4( + struct MocapNET4 * mnet, + const char * description + ) +{ + + unsigned int length = 0; + char * data = readFileToMemory("dataset/combinedModel/mocapnet4/mode1/1.0/step1_upperbody_all",&length); + + struct ModelConfigurationData modelConfiguration={0}; + loadModelConfigurationData(&modelConfiguration,"dataset/combinedModel/mocapnet4/mode1/1.0/step1_upperbody_all/upperbody_configuration.json"); + + return 0; +} + + + +std::vector runMocapNET4( + struct MocapNET4 * mnet, + struct skeletonSerialized * input, + int doLowerbody, + int doHands, + int doFace, + int doGestureDetection, + unsigned int useInverseKinematics, + int doOutputFiltering + ) +{ + std::vector emptyResult; + return emptyResult; +} + + + + +int unloadMocapNET4(struct MocapNET4 * mnet) +{ + return 0; +} diff --git a/src/MocapNET4/MocapNETLib4/mocapnet4.h b/src/MocapNET4/MocapNETLib4/mocapnet4.h new file mode 100644 index 0000000..9cca20e --- /dev/null +++ b/src/MocapNET4/MocapNETLib4/mocapnet4.h @@ -0,0 +1,3464 @@ +#pragma once +/** @file mocapnet4.hpp + * @brief The MocapNET C library + * As seen in https://www.youtube.com/watch?v=fH5e-KMBvM0 , the MocapNET network requires two types of input. + * The first is an uncompressed list of (x,y,v) joints and the second an NSDM array. To add to those the output consists of BVH + * frames that must be accompanied by a header. This library internally handles all of these details. + * @author Ammar Qammaz (AmmarkoV) + */ + + +#include +#include + +/** + * @brief MocapNET version + */ +static const char MocapNETVersion[] = { "4.0" }; + +/** + * @brief MocapNET has been trained on 1920x1080 frames, so all the received coordinates are normalized in the +* 0..1 range based on that. This means that the NN learns the X and Y variations. If a joint lies at pixel 500,500 +* it will be represented as 500/1920 , 500/1080. +* Now if a user uses another configuration, let's say a vertical (portrait) feed where the resolution is 1080x1920 +* the 2D points will get normalized at 500/1080 , 500/1920 and the resulting 2D joint cloud won't work as well +* This is why it is better to change the aspect ratio while normalizing + */ +static const unsigned int MocapNETTrainingWidth=1920, MocapNETTrainingHeight=1080; + + + +/** + * @brief MocapNET output joint names that correspond to the BVH file + * These should correspond to `cat dataset/headerWithHeadAndOneMotion.bvh | grep JOINT` +*/ +static const char * MocapNETOutputJointNames[] = +{ +"hip", +"abdomen", +"chest", +"neck", +"neck1", +"head", +"__jaw", +"jaw", +"special04", +"oris02", +"oris01", +"oris06.l", +"oris07.l", +"oris06.r", +"oris07.r", +"tongue00", +"tongue01", +"tongue02", +"tongue03", +"__tongue04", +"tongue04", +"tongue07.l", +"tongue07.r", +"tongue06.l", +"tongue06.r", +"tongue05.l", +"tongue05.r", +"__levator02.l", +"levator02.l", +"levator03.l", +"levator04.l", +"levator05.l", +"__levator02.r", +"levator02.r", +"levator03.r", +"levator04.r", +"levator05.r", +"__special01", +"special01", +"oris04.l", +"oris03.l", +"oris04.r", +"oris03.r", +"oris06", +"oris05", +"__special03", +"special03", +"__levator06.l", +"levator06.l", +"__levator06.r", +"levator06.r", +"special06.l", +"special05.l", +"eye.l", +"orbicularis03.l", +"orbicularis04.l", +"special06.r", +"special05.r", +"eye.r", +"orbicularis03.r", +"orbicularis04.r", +"__temporalis01.l", +"temporalis01.l", +"oculi02.l", +"oculi01.l", +"__temporalis01.r", +"temporalis01.r", +"oculi02.r", +"oculi01.r", +"__temporalis02.l", +"temporalis02.l", +"risorius02.l", +"risorius03.l", +"__temporalis02.r", +"temporalis02.r", +"risorius02.r", +"risorius03.r", +"rCollar", +"rShldr", +"rForeArm", +"rHand", +"metacarpal1.r", +"finger2-1.r", +"finger2-2.r", +"finger2-3.r", +"metacarpal2.r", +"finger3-1.r", +"finger3-2.r", +"finger3-3.r", +"__metacarpal3.r", +"metacarpal3.r", +"finger4-1.r", +"finger4-2.r", +"finger4-3.r", +"__metacarpal4.r", +"metacarpal4.r", +"finger5-1.r", +"finger5-2.r", +"finger5-3.r", +"rthumbBase", +"rthumb", +"finger1-2.r", +"finger1-3.r", +"lCollar", +"lShldr", +"lForeArm", +"lHand", +"metacarpal1.l", +"finger2-1.l", +"finger2-2.l", +"finger2-3.l", +"metacarpal2.l", +"finger3-1.l", +"finger3-2.l", +"finger3-3.l", +"__metacarpal3.l", +"metacarpal3.l", +"finger4-1.l", +"finger4-2.l", +"finger4-3.l", +"__metacarpal4.l", +"metacarpal4.l", +"finger5-1.l", +"finger5-2.l", +"finger5-3.l", +"lthumbBase", +"lthumb", +"finger1-2.l", +"finger1-3.l", +"rButtock", +"rThigh", +"rShin", +"rFoot", +"toe1-1.R", +"toe1-2.R", +"toe2-1.R", +"toe2-2.R", +"toe2-3.R", +"toe3-1.R", +"toe3-2.R", +"toe3-3.R", +"toe4-1.R", +"toe4-2.R", +"toe4-3.R", +"toe5-1.R", +"toe5-2.R", +"toe5-3.R", +"lButtock", +"lThigh", +"lShin", +"lFoot", +"toe1-1.L", +"toe1-2.L", +"toe2-1.L", +"toe2-2.L", +"toe2-3.L", +"toe3-1.L", +"toe3-2.L", +"toe3-3.L", +"toe4-1.L", +"toe4-2.L", +"toe4-3.L", +"toe5-1.L", +"toe5-2.L", +"toe5-3.L" +}; + + + + + + +/** + * @brief This is a programmer friendly enumerator of joint output extracted from MocapNET. + * Use ./GroundTruthDumper --from dataset/headerWithHeadAndOneMotion.bvh --printc + * to extract this automatically + */ +enum MOCAPNET_Output_Joint_Name_ENUM +{ +MOCAPNET_OUTPUT_JOINT_HIP, +MOCAPNET_OUTPUT_JOINT_ABDOMEN, +MOCAPNET_OUTPUT_JOINT_CHEST, +MOCAPNET_OUTPUT_JOINT_NECK, +MOCAPNET_OUTPUT_JOINT_NECK1, +MOCAPNET_OUTPUT_JOINT_HEAD, +MOCAPNET_OUTPUT_JOINT___JAW, +MOCAPNET_OUTPUT_JOINT_JAW, +MOCAPNET_OUTPUT_JOINT_SPECIAL04, +MOCAPNET_OUTPUT_JOINT_ORIS02, +MOCAPNET_OUTPUT_JOINT_ORIS01, +MOCAPNET_OUTPUT_JOINT_ORIS06_L, +MOCAPNET_OUTPUT_JOINT_ORIS07_L, +MOCAPNET_OUTPUT_JOINT_ORIS06_R, +MOCAPNET_OUTPUT_JOINT_ORIS07_R, +MOCAPNET_OUTPUT_JOINT_TONGUE00, +MOCAPNET_OUTPUT_JOINT_TONGUE01, +MOCAPNET_OUTPUT_JOINT_TONGUE02, +MOCAPNET_OUTPUT_JOINT_TONGUE03, +MOCAPNET_OUTPUT_JOINT___TONGUE04, +MOCAPNET_OUTPUT_JOINT_TONGUE04, +MOCAPNET_OUTPUT_JOINT_TONGUE07_L, +MOCAPNET_OUTPUT_JOINT_TONGUE07_R, +MOCAPNET_OUTPUT_JOINT_TONGUE06_L, +MOCAPNET_OUTPUT_JOINT_TONGUE06_R, +MOCAPNET_OUTPUT_JOINT_TONGUE05_L, +MOCAPNET_OUTPUT_JOINT_TONGUE05_R, +MOCAPNET_OUTPUT_JOINT___LEVATOR02_L, +MOCAPNET_OUTPUT_JOINT_LEVATOR02_L, +MOCAPNET_OUTPUT_JOINT_LEVATOR03_L, +MOCAPNET_OUTPUT_JOINT_LEVATOR04_L, +MOCAPNET_OUTPUT_JOINT_LEVATOR05_L, +MOCAPNET_OUTPUT_JOINT___LEVATOR02_R, +MOCAPNET_OUTPUT_JOINT_LEVATOR02_R, +MOCAPNET_OUTPUT_JOINT_LEVATOR03_R, +MOCAPNET_OUTPUT_JOINT_LEVATOR04_R, +MOCAPNET_OUTPUT_JOINT_LEVATOR05_R, +MOCAPNET_OUTPUT_JOINT___SPECIAL01, +MOCAPNET_OUTPUT_JOINT_SPECIAL01, +MOCAPNET_OUTPUT_JOINT_ORIS04_L, +MOCAPNET_OUTPUT_JOINT_ORIS03_L, +MOCAPNET_OUTPUT_JOINT_ORIS04_R, +MOCAPNET_OUTPUT_JOINT_ORIS03_R, +MOCAPNET_OUTPUT_JOINT_ORIS06, +MOCAPNET_OUTPUT_JOINT_ORIS05, +MOCAPNET_OUTPUT_JOINT___SPECIAL03, +MOCAPNET_OUTPUT_JOINT_SPECIAL03, +MOCAPNET_OUTPUT_JOINT___LEVATOR06_L, +MOCAPNET_OUTPUT_JOINT_LEVATOR06_L, +MOCAPNET_OUTPUT_JOINT___LEVATOR06_R, +MOCAPNET_OUTPUT_JOINT_LEVATOR06_R, +MOCAPNET_OUTPUT_JOINT_SPECIAL06_L, +MOCAPNET_OUTPUT_JOINT_SPECIAL05_L, +MOCAPNET_OUTPUT_JOINT_EYE_L, +MOCAPNET_OUTPUT_JOINT_ORBICULARIS03_L, +MOCAPNET_OUTPUT_JOINT_ORBICULARIS04_L, +MOCAPNET_OUTPUT_JOINT_SPECIAL06_R, +MOCAPNET_OUTPUT_JOINT_SPECIAL05_R, +MOCAPNET_OUTPUT_JOINT_EYE_R, +MOCAPNET_OUTPUT_JOINT_ORBICULARIS03_R, +MOCAPNET_OUTPUT_JOINT_ORBICULARIS04_R, +MOCAPNET_OUTPUT_JOINT___TEMPORALIS01_L, +MOCAPNET_OUTPUT_JOINT_TEMPORALIS01_L, +MOCAPNET_OUTPUT_JOINT_OCULI02_L, +MOCAPNET_OUTPUT_JOINT_OCULI01_L, +MOCAPNET_OUTPUT_JOINT___TEMPORALIS01_R, +MOCAPNET_OUTPUT_JOINT_TEMPORALIS01_R, +MOCAPNET_OUTPUT_JOINT_OCULI02_R, +MOCAPNET_OUTPUT_JOINT_OCULI01_R, +MOCAPNET_OUTPUT_JOINT___TEMPORALIS02_L, +MOCAPNET_OUTPUT_JOINT_TEMPORALIS02_L, +MOCAPNET_OUTPUT_JOINT_RISORIUS02_L, +MOCAPNET_OUTPUT_JOINT_RISORIUS03_L, +MOCAPNET_OUTPUT_JOINT___TEMPORALIS02_R, +MOCAPNET_OUTPUT_JOINT_TEMPORALIS02_R, +MOCAPNET_OUTPUT_JOINT_RISORIUS02_R, +MOCAPNET_OUTPUT_JOINT_RISORIUS03_R, +MOCAPNET_OUTPUT_JOINT_RCOLLAR, +MOCAPNET_OUTPUT_JOINT_RSHLDR, +MOCAPNET_OUTPUT_JOINT_RFOREARM, +MOCAPNET_OUTPUT_JOINT_RHAND, +MOCAPNET_OUTPUT_JOINT_METACARPAL1_R, +MOCAPNET_OUTPUT_JOINT_FINGER2_1_R, +MOCAPNET_OUTPUT_JOINT_FINGER2_2_R, +MOCAPNET_OUTPUT_JOINT_FINGER2_3_R, +MOCAPNET_OUTPUT_JOINT_METACARPAL2_R, +MOCAPNET_OUTPUT_JOINT_FINGER3_1_R, +MOCAPNET_OUTPUT_JOINT_FINGER3_2_R, +MOCAPNET_OUTPUT_JOINT_FINGER3_3_R, +MOCAPNET_OUTPUT_JOINT___METACARPAL3_R, +MOCAPNET_OUTPUT_JOINT_METACARPAL3_R, +MOCAPNET_OUTPUT_JOINT_FINGER4_1_R, +MOCAPNET_OUTPUT_JOINT_FINGER4_2_R, +MOCAPNET_OUTPUT_JOINT_FINGER4_3_R, +MOCAPNET_OUTPUT_JOINT___METACARPAL4_R, +MOCAPNET_OUTPUT_JOINT_METACARPAL4_R, +MOCAPNET_OUTPUT_JOINT_FINGER5_1_R, +MOCAPNET_OUTPUT_JOINT_FINGER5_2_R, +MOCAPNET_OUTPUT_JOINT_FINGER5_3_R, +MOCAPNET_OUTPUT_JOINT_RTHUMBBASE, +MOCAPNET_OUTPUT_JOINT_RTHUMB, +MOCAPNET_OUTPUT_JOINT_FINGER1_2_R, +MOCAPNET_OUTPUT_JOINT_FINGER1_3_R, +MOCAPNET_OUTPUT_JOINT_LCOLLAR, +MOCAPNET_OUTPUT_JOINT_LSHLDR, +MOCAPNET_OUTPUT_JOINT_LFOREARM, +MOCAPNET_OUTPUT_JOINT_LHAND, +MOCAPNET_OUTPUT_JOINT_METACARPAL1_L, +MOCAPNET_OUTPUT_JOINT_FINGER2_1_L, +MOCAPNET_OUTPUT_JOINT_FINGER2_2_L, +MOCAPNET_OUTPUT_JOINT_FINGER2_3_L, +MOCAPNET_OUTPUT_JOINT_METACARPAL2_L, +MOCAPNET_OUTPUT_JOINT_FINGER3_1_L, +MOCAPNET_OUTPUT_JOINT_FINGER3_2_L, +MOCAPNET_OUTPUT_JOINT_FINGER3_3_L, +MOCAPNET_OUTPUT_JOINT___METACARPAL3_L, +MOCAPNET_OUTPUT_JOINT_METACARPAL3_L, +MOCAPNET_OUTPUT_JOINT_FINGER4_1_L, +MOCAPNET_OUTPUT_JOINT_FINGER4_2_L, +MOCAPNET_OUTPUT_JOINT_FINGER4_3_L, +MOCAPNET_OUTPUT_JOINT___METACARPAL4_L, +MOCAPNET_OUTPUT_JOINT_METACARPAL4_L, +MOCAPNET_OUTPUT_JOINT_FINGER5_1_L, +MOCAPNET_OUTPUT_JOINT_FINGER5_2_L, +MOCAPNET_OUTPUT_JOINT_FINGER5_3_L, +MOCAPNET_OUTPUT_JOINT_LTHUMBBASE, +MOCAPNET_OUTPUT_JOINT_LTHUMB, +MOCAPNET_OUTPUT_JOINT_FINGER1_2_L, +MOCAPNET_OUTPUT_JOINT_FINGER1_3_L, +MOCAPNET_OUTPUT_JOINT_RBUTTOCK, +MOCAPNET_OUTPUT_JOINT_RTHIGH, +MOCAPNET_OUTPUT_JOINT_RSHIN, +MOCAPNET_OUTPUT_JOINT_RFOOT, +MOCAPNET_OUTPUT_JOINT_TOE1_1_R, +MOCAPNET_OUTPUT_JOINT_TOE1_2_R, +MOCAPNET_OUTPUT_JOINT_TOE2_1_R, +MOCAPNET_OUTPUT_JOINT_TOE2_2_R, +MOCAPNET_OUTPUT_JOINT_TOE2_3_R, +MOCAPNET_OUTPUT_JOINT_TOE3_1_R, +MOCAPNET_OUTPUT_JOINT_TOE3_2_R, +MOCAPNET_OUTPUT_JOINT_TOE3_3_R, +MOCAPNET_OUTPUT_JOINT_TOE4_1_R, +MOCAPNET_OUTPUT_JOINT_TOE4_2_R, +MOCAPNET_OUTPUT_JOINT_TOE4_3_R, +MOCAPNET_OUTPUT_JOINT_TOE5_1_R, +MOCAPNET_OUTPUT_JOINT_TOE5_2_R, +MOCAPNET_OUTPUT_JOINT_TOE5_3_R, +MOCAPNET_OUTPUT_JOINT_LBUTTOCK, +MOCAPNET_OUTPUT_JOINT_LTHIGH, +MOCAPNET_OUTPUT_JOINT_LSHIN, +MOCAPNET_OUTPUT_JOINT_LFOOT, +MOCAPNET_OUTPUT_JOINT_TOE1_1_L, +MOCAPNET_OUTPUT_JOINT_TOE1_2_L, +MOCAPNET_OUTPUT_JOINT_TOE2_1_L, +MOCAPNET_OUTPUT_JOINT_TOE2_2_L, +MOCAPNET_OUTPUT_JOINT_TOE2_3_L, +MOCAPNET_OUTPUT_JOINT_TOE3_1_L, +MOCAPNET_OUTPUT_JOINT_TOE3_2_L, +MOCAPNET_OUTPUT_JOINT_TOE3_3_L, +MOCAPNET_OUTPUT_JOINT_TOE4_1_L, +MOCAPNET_OUTPUT_JOINT_TOE4_2_L, +MOCAPNET_OUTPUT_JOINT_TOE4_3_L, +MOCAPNET_OUTPUT_JOINT_TOE5_1_L, +MOCAPNET_OUTPUT_JOINT_TOE5_2_L, +MOCAPNET_OUTPUT_JOINT_TOE5_3_L +}; + + + + +/** + * @brief This is a programmer friendly enumerator to access 3D output extracted from the BVH file_ + * Use _/GroundTruthDumper __from dataset/headerWithHeadAndOneMotion_bvh __printc to extract this automatically + */ +enum MOCAPNET_2D_Output_Joints +{ +MOCAPNET_2DPOINT_HIPX,//0 +MOCAPNET_2DPOINT_HIPY,//1 +MOCAPNET_2DPOINT_ABDOMENX,//2 +MOCAPNET_2DPOINT_ABDOMENY,//3 +MOCAPNET_2DPOINT_CHESTX,//4 +MOCAPNET_2DPOINT_CHESTY,//5 +MOCAPNET_2DPOINT_NECKX,//6 +MOCAPNET_2DPOINT_NECKY,//7 +MOCAPNET_2DPOINT_NECK1X,//8 +MOCAPNET_2DPOINT_NECK1Y,//9 +MOCAPNET_2DPOINT_HEADX,//10 +MOCAPNET_2DPOINT_HEADY,//11 +MOCAPNET_2DPOINT___JAWX,//12 +MOCAPNET_2DPOINT___JAWY,//13 +MOCAPNET_2DPOINT_JAWX,//14 +MOCAPNET_2DPOINT_JAWY,//15 +MOCAPNET_2DPOINT_SPECIAL04X,//16 +MOCAPNET_2DPOINT_SPECIAL04Y,//17 +MOCAPNET_2DPOINT_ORIS02X,//18 +MOCAPNET_2DPOINT_ORIS02Y,//19 +MOCAPNET_2DPOINT_ORIS01X,//20 +MOCAPNET_2DPOINT_ORIS01Y,//21 +MOCAPNET_2DPOINT_ENDSITE_ORIS01X,//22 +MOCAPNET_2DPOINT_ENDSITE_ORIS01Y,//23 +MOCAPNET_2DPOINT_ORIS06_LX,//24 +MOCAPNET_2DPOINT_ORIS06_LY,//25 +MOCAPNET_2DPOINT_ORIS07_LX,//26 +MOCAPNET_2DPOINT_ORIS07_LY,//27 +MOCAPNET_2DPOINT_ENDSITE_ORIS07_LX,//28 +MOCAPNET_2DPOINT_ENDSITE_ORIS07_LY,//29 +MOCAPNET_2DPOINT_ORIS06_RX,//30 +MOCAPNET_2DPOINT_ORIS06_RY,//31 +MOCAPNET_2DPOINT_ORIS07_RX,//32 +MOCAPNET_2DPOINT_ORIS07_RY,//33 +MOCAPNET_2DPOINT_ENDSITE_ORIS07_RX,//34 +MOCAPNET_2DPOINT_ENDSITE_ORIS07_RY,//35 +MOCAPNET_2DPOINT_TONGUE00X,//36 +MOCAPNET_2DPOINT_TONGUE00Y,//37 +MOCAPNET_2DPOINT_TONGUE01X,//38 +MOCAPNET_2DPOINT_TONGUE01Y,//39 +MOCAPNET_2DPOINT_TONGUE02X,//40 +MOCAPNET_2DPOINT_TONGUE02Y,//41 +MOCAPNET_2DPOINT_TONGUE03X,//42 +MOCAPNET_2DPOINT_TONGUE03Y,//43 +MOCAPNET_2DPOINT___TONGUE04X,//44 +MOCAPNET_2DPOINT___TONGUE04Y,//45 +MOCAPNET_2DPOINT_TONGUE04X,//46 +MOCAPNET_2DPOINT_TONGUE04Y,//47 +MOCAPNET_2DPOINT_ENDSITE_TONGUE04X,//48 +MOCAPNET_2DPOINT_ENDSITE_TONGUE04Y,//49 +MOCAPNET_2DPOINT_TONGUE07_LX,//50 +MOCAPNET_2DPOINT_TONGUE07_LY,//51 +MOCAPNET_2DPOINT_ENDSITE_TONGUE07_LX,//52 +MOCAPNET_2DPOINT_ENDSITE_TONGUE07_LY,//53 +MOCAPNET_2DPOINT_TONGUE07_RX,//54 +MOCAPNET_2DPOINT_TONGUE07_RY,//55 +MOCAPNET_2DPOINT_ENDSITE_TONGUE07_RX,//56 +MOCAPNET_2DPOINT_ENDSITE_TONGUE07_RY,//57 +MOCAPNET_2DPOINT_TONGUE06_LX,//58 +MOCAPNET_2DPOINT_TONGUE06_LY,//59 +MOCAPNET_2DPOINT_ENDSITE_TONGUE06_LX,//60 +MOCAPNET_2DPOINT_ENDSITE_TONGUE06_LY,//61 +MOCAPNET_2DPOINT_TONGUE06_RX,//62 +MOCAPNET_2DPOINT_TONGUE06_RY,//63 +MOCAPNET_2DPOINT_ENDSITE_TONGUE06_RX,//64 +MOCAPNET_2DPOINT_ENDSITE_TONGUE06_RY,//65 +MOCAPNET_2DPOINT_TONGUE05_LX,//66 +MOCAPNET_2DPOINT_TONGUE05_LY,//67 +MOCAPNET_2DPOINT_ENDSITE_TONGUE05_LX,//68 +MOCAPNET_2DPOINT_ENDSITE_TONGUE05_LY,//69 +MOCAPNET_2DPOINT_TONGUE05_RX,//70 +MOCAPNET_2DPOINT_TONGUE05_RY,//71 +MOCAPNET_2DPOINT_ENDSITE_TONGUE05_RX,//72 +MOCAPNET_2DPOINT_ENDSITE_TONGUE05_RY,//73 +MOCAPNET_2DPOINT___LEVATOR02_LX,//74 +MOCAPNET_2DPOINT___LEVATOR02_LY,//75 +MOCAPNET_2DPOINT_LEVATOR02_LX,//76 +MOCAPNET_2DPOINT_LEVATOR02_LY,//77 +MOCAPNET_2DPOINT_LEVATOR03_LX,//78 +MOCAPNET_2DPOINT_LEVATOR03_LY,//79 +MOCAPNET_2DPOINT_LEVATOR04_LX,//80 +MOCAPNET_2DPOINT_LEVATOR04_LY,//81 +MOCAPNET_2DPOINT_LEVATOR05_LX,//82 +MOCAPNET_2DPOINT_LEVATOR05_LY,//83 +MOCAPNET_2DPOINT_ENDSITE_LEVATOR05_LX,//84 +MOCAPNET_2DPOINT_ENDSITE_LEVATOR05_LY,//85 +MOCAPNET_2DPOINT___LEVATOR02_RX,//86 +MOCAPNET_2DPOINT___LEVATOR02_RY,//87 +MOCAPNET_2DPOINT_LEVATOR02_RX,//88 +MOCAPNET_2DPOINT_LEVATOR02_RY,//89 +MOCAPNET_2DPOINT_LEVATOR03_RX,//90 +MOCAPNET_2DPOINT_LEVATOR03_RY,//91 +MOCAPNET_2DPOINT_LEVATOR04_RX,//92 +MOCAPNET_2DPOINT_LEVATOR04_RY,//93 +MOCAPNET_2DPOINT_LEVATOR05_RX,//94 +MOCAPNET_2DPOINT_LEVATOR05_RY,//95 +MOCAPNET_2DPOINT_ENDSITE_LEVATOR05_RX,//96 +MOCAPNET_2DPOINT_ENDSITE_LEVATOR05_RY,//97 +MOCAPNET_2DPOINT___SPECIAL01X,//98 +MOCAPNET_2DPOINT___SPECIAL01Y,//99 +MOCAPNET_2DPOINT_SPECIAL01X,//100 +MOCAPNET_2DPOINT_SPECIAL01Y,//101 +MOCAPNET_2DPOINT_ORIS04_LX,//102 +MOCAPNET_2DPOINT_ORIS04_LY,//103 +MOCAPNET_2DPOINT_ORIS03_LX,//104 +MOCAPNET_2DPOINT_ORIS03_LY,//105 +MOCAPNET_2DPOINT_ENDSITE_ORIS03_LX,//106 +MOCAPNET_2DPOINT_ENDSITE_ORIS03_LY,//107 +MOCAPNET_2DPOINT_ORIS04_RX,//108 +MOCAPNET_2DPOINT_ORIS04_RY,//109 +MOCAPNET_2DPOINT_ORIS03_RX,//110 +MOCAPNET_2DPOINT_ORIS03_RY,//111 +MOCAPNET_2DPOINT_ENDSITE_ORIS03_RX,//112 +MOCAPNET_2DPOINT_ENDSITE_ORIS03_RY,//113 +MOCAPNET_2DPOINT_ORIS06X,//114 +MOCAPNET_2DPOINT_ORIS06Y,//115 +MOCAPNET_2DPOINT_ORIS05X,//116 +MOCAPNET_2DPOINT_ORIS05Y,//117 +MOCAPNET_2DPOINT_ENDSITE_ORIS05X,//118 +MOCAPNET_2DPOINT_ENDSITE_ORIS05Y,//119 +MOCAPNET_2DPOINT___SPECIAL03X,//120 +MOCAPNET_2DPOINT___SPECIAL03Y,//121 +MOCAPNET_2DPOINT_SPECIAL03X,//122 +MOCAPNET_2DPOINT_SPECIAL03Y,//123 +MOCAPNET_2DPOINT___LEVATOR06_LX,//124 +MOCAPNET_2DPOINT___LEVATOR06_LY,//125 +MOCAPNET_2DPOINT_LEVATOR06_LX,//126 +MOCAPNET_2DPOINT_LEVATOR06_LY,//127 +MOCAPNET_2DPOINT_ENDSITE_LEVATOR06_LX,//128 +MOCAPNET_2DPOINT_ENDSITE_LEVATOR06_LY,//129 +MOCAPNET_2DPOINT___LEVATOR06_RX,//130 +MOCAPNET_2DPOINT___LEVATOR06_RY,//131 +MOCAPNET_2DPOINT_LEVATOR06_RX,//132 +MOCAPNET_2DPOINT_LEVATOR06_RY,//133 +MOCAPNET_2DPOINT_ENDSITE_LEVATOR06_RX,//134 +MOCAPNET_2DPOINT_ENDSITE_LEVATOR06_RY,//135 +MOCAPNET_2DPOINT_SPECIAL06_LX,//136 +MOCAPNET_2DPOINT_SPECIAL06_LY,//137 +MOCAPNET_2DPOINT_SPECIAL05_LX,//138 +MOCAPNET_2DPOINT_SPECIAL05_LY,//139 +MOCAPNET_2DPOINT_EYE_LX,//140 +MOCAPNET_2DPOINT_EYE_LY,//141 +MOCAPNET_2DPOINT_ENDSITE_EYE_LX,//142 +MOCAPNET_2DPOINT_ENDSITE_EYE_LY,//143 +MOCAPNET_2DPOINT_ORBICULARIS03_LX,//144 +MOCAPNET_2DPOINT_ORBICULARIS03_LY,//145 +MOCAPNET_2DPOINT_ENDSITE_ORBICULARIS03_LX,//146 +MOCAPNET_2DPOINT_ENDSITE_ORBICULARIS03_LY,//147 +MOCAPNET_2DPOINT_ORBICULARIS04_LX,//148 +MOCAPNET_2DPOINT_ORBICULARIS04_LY,//149 +MOCAPNET_2DPOINT_ENDSITE_ORBICULARIS04_LX,//150 +MOCAPNET_2DPOINT_ENDSITE_ORBICULARIS04_LY,//151 +MOCAPNET_2DPOINT_SPECIAL06_RX,//152 +MOCAPNET_2DPOINT_SPECIAL06_RY,//153 +MOCAPNET_2DPOINT_SPECIAL05_RX,//154 +MOCAPNET_2DPOINT_SPECIAL05_RY,//155 +MOCAPNET_2DPOINT_EYE_RX,//156 +MOCAPNET_2DPOINT_EYE_RY,//157 +MOCAPNET_2DPOINT_ENDSITE_EYE_RX,//158 +MOCAPNET_2DPOINT_ENDSITE_EYE_RY,//159 +MOCAPNET_2DPOINT_ORBICULARIS03_RX,//160 +MOCAPNET_2DPOINT_ORBICULARIS03_RY,//161 +MOCAPNET_2DPOINT_ENDSITE_ORBICULARIS03_RX,//162 +MOCAPNET_2DPOINT_ENDSITE_ORBICULARIS03_RY,//163 +MOCAPNET_2DPOINT_ORBICULARIS04_RX,//164 +MOCAPNET_2DPOINT_ORBICULARIS04_RY,//165 +MOCAPNET_2DPOINT_ENDSITE_ORBICULARIS04_RX,//166 +MOCAPNET_2DPOINT_ENDSITE_ORBICULARIS04_RY,//167 +MOCAPNET_2DPOINT___TEMPORALIS01_LX,//168 +MOCAPNET_2DPOINT___TEMPORALIS01_LY,//169 +MOCAPNET_2DPOINT_TEMPORALIS01_LX,//170 +MOCAPNET_2DPOINT_TEMPORALIS01_LY,//171 +MOCAPNET_2DPOINT_OCULI02_LX,//172 +MOCAPNET_2DPOINT_OCULI02_LY,//173 +MOCAPNET_2DPOINT_OCULI01_LX,//174 +MOCAPNET_2DPOINT_OCULI01_LY,//175 +MOCAPNET_2DPOINT_ENDSITE_OCULI01_LX,//176 +MOCAPNET_2DPOINT_ENDSITE_OCULI01_LY,//177 +MOCAPNET_2DPOINT___TEMPORALIS01_RX,//178 +MOCAPNET_2DPOINT___TEMPORALIS01_RY,//179 +MOCAPNET_2DPOINT_TEMPORALIS01_RX,//180 +MOCAPNET_2DPOINT_TEMPORALIS01_RY,//181 +MOCAPNET_2DPOINT_OCULI02_RX,//182 +MOCAPNET_2DPOINT_OCULI02_RY,//183 +MOCAPNET_2DPOINT_OCULI01_RX,//184 +MOCAPNET_2DPOINT_OCULI01_RY,//185 +MOCAPNET_2DPOINT_ENDSITE_OCULI01_RX,//186 +MOCAPNET_2DPOINT_ENDSITE_OCULI01_RY,//187 +MOCAPNET_2DPOINT___TEMPORALIS02_LX,//188 +MOCAPNET_2DPOINT___TEMPORALIS02_LY,//189 +MOCAPNET_2DPOINT_TEMPORALIS02_LX,//190 +MOCAPNET_2DPOINT_TEMPORALIS02_LY,//191 +MOCAPNET_2DPOINT_RISORIUS02_LX,//192 +MOCAPNET_2DPOINT_RISORIUS02_LY,//193 +MOCAPNET_2DPOINT_RISORIUS03_LX,//194 +MOCAPNET_2DPOINT_RISORIUS03_LY,//195 +MOCAPNET_2DPOINT_ENDSITE_RISORIUS03_LX,//196 +MOCAPNET_2DPOINT_ENDSITE_RISORIUS03_LY,//197 +MOCAPNET_2DPOINT___TEMPORALIS02_RX,//198 +MOCAPNET_2DPOINT___TEMPORALIS02_RY,//199 +MOCAPNET_2DPOINT_TEMPORALIS02_RX,//200 +MOCAPNET_2DPOINT_TEMPORALIS02_RY,//201 +MOCAPNET_2DPOINT_RISORIUS02_RX,//202 +MOCAPNET_2DPOINT_RISORIUS02_RY,//203 +MOCAPNET_2DPOINT_RISORIUS03_RX,//204 +MOCAPNET_2DPOINT_RISORIUS03_RY,//205 +MOCAPNET_2DPOINT_ENDSITE_RISORIUS03_RX,//206 +MOCAPNET_2DPOINT_ENDSITE_RISORIUS03_RY,//207 +MOCAPNET_2DPOINT_RCOLLARX,//208 +MOCAPNET_2DPOINT_RCOLLARY,//209 +MOCAPNET_2DPOINT_RSHOULDERX,//210 +MOCAPNET_2DPOINT_RSHOULDERY,//211 +MOCAPNET_2DPOINT_RELBOWX,//212 +MOCAPNET_2DPOINT_RELBOWY,//213 +MOCAPNET_2DPOINT_RHANDX,//214 +MOCAPNET_2DPOINT_RHANDY,//215 +MOCAPNET_2DPOINT_METACARPAL1_RX,//216 +MOCAPNET_2DPOINT_METACARPAL1_RY,//217 +MOCAPNET_2DPOINT_FINGER2_1_RX,//218 +MOCAPNET_2DPOINT_FINGER2_1_RY,//219 +MOCAPNET_2DPOINT_FINGER2_2_RX,//220 +MOCAPNET_2DPOINT_FINGER2_2_RY,//221 +MOCAPNET_2DPOINT_FINGER2_3_RX,//222 +MOCAPNET_2DPOINT_FINGER2_3_RY,//223 +MOCAPNET_2DPOINT_ENDSITE_FINGER2_3_RX,//224 +MOCAPNET_2DPOINT_ENDSITE_FINGER2_3_RY,//225 +MOCAPNET_2DPOINT_METACARPAL2_RX,//226 +MOCAPNET_2DPOINT_METACARPAL2_RY,//227 +MOCAPNET_2DPOINT_FINGER3_1_RX,//228 +MOCAPNET_2DPOINT_FINGER3_1_RY,//229 +MOCAPNET_2DPOINT_FINGER3_2_RX,//230 +MOCAPNET_2DPOINT_FINGER3_2_RY,//231 +MOCAPNET_2DPOINT_FINGER3_3_RX,//232 +MOCAPNET_2DPOINT_FINGER3_3_RY,//233 +MOCAPNET_2DPOINT_ENDSITE_FINGER3_3_RX,//234 +MOCAPNET_2DPOINT_ENDSITE_FINGER3_3_RY,//235 +MOCAPNET_2DPOINT___METACARPAL3_RX,//236 +MOCAPNET_2DPOINT___METACARPAL3_RY,//237 +MOCAPNET_2DPOINT_METACARPAL3_RX,//238 +MOCAPNET_2DPOINT_METACARPAL3_RY,//239 +MOCAPNET_2DPOINT_FINGER4_1_RX,//240 +MOCAPNET_2DPOINT_FINGER4_1_RY,//241 +MOCAPNET_2DPOINT_FINGER4_2_RX,//242 +MOCAPNET_2DPOINT_FINGER4_2_RY,//243 +MOCAPNET_2DPOINT_FINGER4_3_RX,//244 +MOCAPNET_2DPOINT_FINGER4_3_RY,//245 +MOCAPNET_2DPOINT_ENDSITE_FINGER4_3_RX,//246 +MOCAPNET_2DPOINT_ENDSITE_FINGER4_3_RY,//247 +MOCAPNET_2DPOINT___METACARPAL4_RX,//248 +MOCAPNET_2DPOINT___METACARPAL4_RY,//249 +MOCAPNET_2DPOINT_METACARPAL4_RX,//250 +MOCAPNET_2DPOINT_METACARPAL4_RY,//251 +MOCAPNET_2DPOINT_FINGER5_1_RX,//252 +MOCAPNET_2DPOINT_FINGER5_1_RY,//253 +MOCAPNET_2DPOINT_FINGER5_2_RX,//254 +MOCAPNET_2DPOINT_FINGER5_2_RY,//255 +MOCAPNET_2DPOINT_FINGER5_3_RX,//256 +MOCAPNET_2DPOINT_FINGER5_3_RY,//257 +MOCAPNET_2DPOINT_ENDSITE_FINGER5_3_RX,//258 +MOCAPNET_2DPOINT_ENDSITE_FINGER5_3_RY,//259 +MOCAPNET_2DPOINT_RTHUMBBASEX,//260 +MOCAPNET_2DPOINT_RTHUMBBASEY,//261 +MOCAPNET_2DPOINT_RTHUMBX,//262 +MOCAPNET_2DPOINT_RTHUMBY,//263 +MOCAPNET_2DPOINT_FINGER1_2_RX,//264 +MOCAPNET_2DPOINT_FINGER1_2_RY,//265 +MOCAPNET_2DPOINT_FINGER1_3_RX,//266 +MOCAPNET_2DPOINT_FINGER1_3_RY,//267 +MOCAPNET_2DPOINT_ENDSITE_FINGER1_3_RX,//268 +MOCAPNET_2DPOINT_ENDSITE_FINGER1_3_RY,//269 +MOCAPNET_2DPOINT_LCOLLARX,//270 +MOCAPNET_2DPOINT_LCOLLARY,//271 +MOCAPNET_2DPOINT_LSHOULDERX,//272 +MOCAPNET_2DPOINT_LSHOULDERY,//273 +MOCAPNET_2DPOINT_LELBOWX,//274 +MOCAPNET_2DPOINT_LELBOWY,//275 +MOCAPNET_2DPOINT_LHANDX,//276 +MOCAPNET_2DPOINT_LHANDY,//277 +MOCAPNET_2DPOINT_METACARPAL1_LX,//278 +MOCAPNET_2DPOINT_METACARPAL1_LY,//279 +MOCAPNET_2DPOINT_FINGER2_1_LX,//280 +MOCAPNET_2DPOINT_FINGER2_1_LY,//281 +MOCAPNET_2DPOINT_FINGER2_2_LX,//282 +MOCAPNET_2DPOINT_FINGER2_2_LY,//283 +MOCAPNET_2DPOINT_FINGER2_3_LX,//284 +MOCAPNET_2DPOINT_FINGER2_3_LY,//285 +MOCAPNET_2DPOINT_ENDSITE_FINGER2_3_LX,//286 +MOCAPNET_2DPOINT_ENDSITE_FINGER2_3_LY,//287 +MOCAPNET_2DPOINT_METACARPAL2_LX,//288 +MOCAPNET_2DPOINT_METACARPAL2_LY,//289 +MOCAPNET_2DPOINT_FINGER3_1_LX,//290 +MOCAPNET_2DPOINT_FINGER3_1_LY,//291 +MOCAPNET_2DPOINT_FINGER3_2_LX,//292 +MOCAPNET_2DPOINT_FINGER3_2_LY,//293 +MOCAPNET_2DPOINT_FINGER3_3_LX,//294 +MOCAPNET_2DPOINT_FINGER3_3_LY,//295 +MOCAPNET_2DPOINT_ENDSITE_FINGER3_3_LX,//296 +MOCAPNET_2DPOINT_ENDSITE_FINGER3_3_LY,//297 +MOCAPNET_2DPOINT___METACARPAL3_LX,//298 +MOCAPNET_2DPOINT___METACARPAL3_LY,//299 +MOCAPNET_2DPOINT_METACARPAL3_LX,//300 +MOCAPNET_2DPOINT_METACARPAL3_LY,//301 +MOCAPNET_2DPOINT_FINGER4_1_LX,//302 +MOCAPNET_2DPOINT_FINGER4_1_LY,//303 +MOCAPNET_2DPOINT_FINGER4_2_LX,//304 +MOCAPNET_2DPOINT_FINGER4_2_LY,//305 +MOCAPNET_2DPOINT_FINGER4_3_LX,//306 +MOCAPNET_2DPOINT_FINGER4_3_LY,//307 +MOCAPNET_2DPOINT_ENDSITE_FINGER4_3_LX,//308 +MOCAPNET_2DPOINT_ENDSITE_FINGER4_3_LY,//309 +MOCAPNET_2DPOINT___METACARPAL4_LX,//310 +MOCAPNET_2DPOINT___METACARPAL4_LY,//311 +MOCAPNET_2DPOINT_METACARPAL4_LX,//312 +MOCAPNET_2DPOINT_METACARPAL4_LY,//313 +MOCAPNET_2DPOINT_FINGER5_1_LX,//314 +MOCAPNET_2DPOINT_FINGER5_1_LY,//315 +MOCAPNET_2DPOINT_FINGER5_2_LX,//316 +MOCAPNET_2DPOINT_FINGER5_2_LY,//317 +MOCAPNET_2DPOINT_FINGER5_3_LX,//318 +MOCAPNET_2DPOINT_FINGER5_3_LY,//319 +MOCAPNET_2DPOINT_ENDSITE_FINGER5_3_LX,//320 +MOCAPNET_2DPOINT_ENDSITE_FINGER5_3_LY,//321 +MOCAPNET_2DPOINT_LTHUMBBASEX,//322 +MOCAPNET_2DPOINT_LTHUMBBASEY,//323 +MOCAPNET_2DPOINT_LTHUMBX,//324 +MOCAPNET_2DPOINT_LTHUMBY,//325 +MOCAPNET_2DPOINT_FINGER1_2_LX,//326 +MOCAPNET_2DPOINT_FINGER1_2_LY,//327 +MOCAPNET_2DPOINT_FINGER1_3_LX,//328 +MOCAPNET_2DPOINT_FINGER1_3_LY,//329 +MOCAPNET_2DPOINT_ENDSITE_FINGER1_3_LX,//330 +MOCAPNET_2DPOINT_ENDSITE_FINGER1_3_LY,//331 +MOCAPNET_2DPOINT_RBUTTOCKX,//332 +MOCAPNET_2DPOINT_RBUTTOCKY,//333 +MOCAPNET_2DPOINT_RHIPX,//334 +MOCAPNET_2DPOINT_RHIPY,//335 +MOCAPNET_2DPOINT_RKNEEX,//336 +MOCAPNET_2DPOINT_RKNEEY,//337 +MOCAPNET_2DPOINT_RFOOTX,//338 +MOCAPNET_2DPOINT_RFOOTY,//339 +MOCAPNET_2DPOINT_TOE1_1_RX,//340 +MOCAPNET_2DPOINT_TOE1_1_RY,//341 +MOCAPNET_2DPOINT_TOE1_2_RX,//342 +MOCAPNET_2DPOINT_TOE1_2_RY,//343 +MOCAPNET_2DPOINT_ENDSITE_TOE1_2_RX,//344 +MOCAPNET_2DPOINT_ENDSITE_TOE1_2_RY,//345 +MOCAPNET_2DPOINT_TOE2_1_RX,//346 +MOCAPNET_2DPOINT_TOE2_1_RY,//347 +MOCAPNET_2DPOINT_TOE2_2_RX,//348 +MOCAPNET_2DPOINT_TOE2_2_RY,//349 +MOCAPNET_2DPOINT_TOE2_3_RX,//350 +MOCAPNET_2DPOINT_TOE2_3_RY,//351 +MOCAPNET_2DPOINT_ENDSITE_TOE2_3_RX,//352 +MOCAPNET_2DPOINT_ENDSITE_TOE2_3_RY,//353 +MOCAPNET_2DPOINT_TOE3_1_RX,//354 +MOCAPNET_2DPOINT_TOE3_1_RY,//355 +MOCAPNET_2DPOINT_TOE3_2_RX,//356 +MOCAPNET_2DPOINT_TOE3_2_RY,//357 +MOCAPNET_2DPOINT_TOE3_3_RX,//358 +MOCAPNET_2DPOINT_TOE3_3_RY,//359 +MOCAPNET_2DPOINT_ENDSITE_TOE3_3_RX,//360 +MOCAPNET_2DPOINT_ENDSITE_TOE3_3_RY,//361 +MOCAPNET_2DPOINT_TOE4_1_RX,//362 +MOCAPNET_2DPOINT_TOE4_1_RY,//363 +MOCAPNET_2DPOINT_TOE4_2_RX,//364 +MOCAPNET_2DPOINT_TOE4_2_RY,//365 +MOCAPNET_2DPOINT_TOE4_3_RX,//366 +MOCAPNET_2DPOINT_TOE4_3_RY,//367 +MOCAPNET_2DPOINT_ENDSITE_TOE4_3_RX,//368 +MOCAPNET_2DPOINT_ENDSITE_TOE4_3_RY,//369 +MOCAPNET_2DPOINT_TOE5_1_RX,//370 +MOCAPNET_2DPOINT_TOE5_1_RY,//371 +MOCAPNET_2DPOINT_TOE5_2_RX,//372 +MOCAPNET_2DPOINT_TOE5_2_RY,//373 +MOCAPNET_2DPOINT_TOE5_3_RX,//374 +MOCAPNET_2DPOINT_TOE5_3_RY,//375 +MOCAPNET_2DPOINT_ENDSITE_TOE5_3_RX,//376 +MOCAPNET_2DPOINT_ENDSITE_TOE5_3_RY,//377 +MOCAPNET_2DPOINT_LBUTTOCKX,//378 +MOCAPNET_2DPOINT_LBUTTOCKY,//379 +MOCAPNET_2DPOINT_LHIPX,//380 +MOCAPNET_2DPOINT_LHIPY,//381 +MOCAPNET_2DPOINT_LKNEEX,//382 +MOCAPNET_2DPOINT_LKNEEY,//383 +MOCAPNET_2DPOINT_LFOOTX,//384 +MOCAPNET_2DPOINT_LFOOTY,//385 +MOCAPNET_2DPOINT_TOE1_1_LX,//386 +MOCAPNET_2DPOINT_TOE1_1_LY,//387 +MOCAPNET_2DPOINT_TOE1_2_LX,//388 +MOCAPNET_2DPOINT_TOE1_2_LY,//389 +MOCAPNET_2DPOINT_ENDSITE_TOE1_2_LX,//390 +MOCAPNET_2DPOINT_ENDSITE_TOE1_2_LY,//391 +MOCAPNET_2DPOINT_TOE2_1_LX,//392 +MOCAPNET_2DPOINT_TOE2_1_LY,//393 +MOCAPNET_2DPOINT_TOE2_2_LX,//394 +MOCAPNET_2DPOINT_TOE2_2_LY,//395 +MOCAPNET_2DPOINT_TOE2_3_LX,//396 +MOCAPNET_2DPOINT_TOE2_3_LY,//397 +MOCAPNET_2DPOINT_ENDSITE_TOE2_3_LX,//398 +MOCAPNET_2DPOINT_ENDSITE_TOE2_3_LY,//399 +MOCAPNET_2DPOINT_TOE3_1_LX,//400 +MOCAPNET_2DPOINT_TOE3_1_LY,//401 +MOCAPNET_2DPOINT_TOE3_2_LX,//402 +MOCAPNET_2DPOINT_TOE3_2_LY,//403 +MOCAPNET_2DPOINT_TOE3_3_LX,//404 +MOCAPNET_2DPOINT_TOE3_3_LY,//405 +MOCAPNET_2DPOINT_ENDSITE_TOE3_3_LX,//406 +MOCAPNET_2DPOINT_ENDSITE_TOE3_3_LY,//407 +MOCAPNET_2DPOINT_TOE4_1_LX,//408 +MOCAPNET_2DPOINT_TOE4_1_LY,//409 +MOCAPNET_2DPOINT_TOE4_2_LX,//410 +MOCAPNET_2DPOINT_TOE4_2_LY,//411 +MOCAPNET_2DPOINT_TOE4_3_LX,//412 +MOCAPNET_2DPOINT_TOE4_3_LY,//413 +MOCAPNET_2DPOINT_ENDSITE_TOE4_3_LX,//414 +MOCAPNET_2DPOINT_ENDSITE_TOE4_3_LY,//415 +MOCAPNET_2DPOINT_TOE5_1_LX,//416 +MOCAPNET_2DPOINT_TOE5_1_LY,//417 +MOCAPNET_2DPOINT_TOE5_2_LX,//418 +MOCAPNET_2DPOINT_TOE5_2_LY,//419 +MOCAPNET_2DPOINT_TOE5_3_LX,//420 +MOCAPNET_2DPOINT_TOE5_3_LY,//421 +MOCAPNET_2DPOINT_ENDSITE_TOE5_3_LX,//422 +MOCAPNET_2DPOINT_ENDSITE_TOE5_3_LY//423 +}; + + + + +/** + * @brief This is a programmer friendly enumerator to access 3D output extracted from the BVH file_ + * Use _/GroundTruthDumper __from dataset/headerWithHeadAndOneMotion_bvh __printc to extract this automatically + */ +enum MOCAPNET_JointHierarchy_Joints +{ +MOCAPNET_JOINT_HIP,//0 +MOCAPNET_JOINT_ABDOMEN,//1 +MOCAPNET_JOINT_CHEST,//2 +MOCAPNET_JOINT_NECK,//3 +MOCAPNET_JOINT_NECK1,//4 +MOCAPNET_JOINT_HEAD,//5 +MOCAPNET_JOINT___JAW,//6 +MOCAPNET_JOINT_JAW,//7 +MOCAPNET_JOINT_SPECIAL04,//8 +MOCAPNET_JOINT_ORIS02,//9 +MOCAPNET_JOINT_ORIS01,//10 +MOCAPNET_JOINT_ENDSITE_ORIS01,//11 +MOCAPNET_JOINT_ORIS06_L,//12 +MOCAPNET_JOINT_ORIS07_L,//13 +MOCAPNET_JOINT_ENDSITE_ORIS07_L,//14 +MOCAPNET_JOINT_ORIS06_R,//15 +MOCAPNET_JOINT_ORIS07_R,//16 +MOCAPNET_JOINT_ENDSITE_ORIS07_R,//17 +MOCAPNET_JOINT_TONGUE00,//18 +MOCAPNET_JOINT_TONGUE01,//19 +MOCAPNET_JOINT_TONGUE02,//20 +MOCAPNET_JOINT_TONGUE03,//21 +MOCAPNET_JOINT___TONGUE04,//22 +MOCAPNET_JOINT_TONGUE04,//23 +MOCAPNET_JOINT_ENDSITE_TONGUE04,//24 +MOCAPNET_JOINT_TONGUE07_L,//25 +MOCAPNET_JOINT_ENDSITE_TONGUE07_L,//26 +MOCAPNET_JOINT_TONGUE07_R,//27 +MOCAPNET_JOINT_ENDSITE_TONGUE07_R,//28 +MOCAPNET_JOINT_TONGUE06_L,//29 +MOCAPNET_JOINT_ENDSITE_TONGUE06_L,//30 +MOCAPNET_JOINT_TONGUE06_R,//31 +MOCAPNET_JOINT_ENDSITE_TONGUE06_R,//32 +MOCAPNET_JOINT_TONGUE05_L,//33 +MOCAPNET_JOINT_ENDSITE_TONGUE05_L,//34 +MOCAPNET_JOINT_TONGUE05_R,//35 +MOCAPNET_JOINT_ENDSITE_TONGUE05_R,//36 +MOCAPNET_JOINT___LEVATOR02_L,//37 +MOCAPNET_JOINT_LEVATOR02_L,//38 +MOCAPNET_JOINT_LEVATOR03_L,//39 +MOCAPNET_JOINT_LEVATOR04_L,//40 +MOCAPNET_JOINT_LEVATOR05_L,//41 +MOCAPNET_JOINT_ENDSITE_LEVATOR05_L,//42 +MOCAPNET_JOINT___LEVATOR02_R,//43 +MOCAPNET_JOINT_LEVATOR02_R,//44 +MOCAPNET_JOINT_LEVATOR03_R,//45 +MOCAPNET_JOINT_LEVATOR04_R,//46 +MOCAPNET_JOINT_LEVATOR05_R,//47 +MOCAPNET_JOINT_ENDSITE_LEVATOR05_R,//48 +MOCAPNET_JOINT___SPECIAL01,//49 +MOCAPNET_JOINT_SPECIAL01,//50 +MOCAPNET_JOINT_ORIS04_L,//51 +MOCAPNET_JOINT_ORIS03_L,//52 +MOCAPNET_JOINT_ENDSITE_ORIS03_L,//53 +MOCAPNET_JOINT_ORIS04_R,//54 +MOCAPNET_JOINT_ORIS03_R,//55 +MOCAPNET_JOINT_ENDSITE_ORIS03_R,//56 +MOCAPNET_JOINT_ORIS06,//57 +MOCAPNET_JOINT_ORIS05,//58 +MOCAPNET_JOINT_ENDSITE_ORIS05,//59 +MOCAPNET_JOINT___SPECIAL03,//60 +MOCAPNET_JOINT_SPECIAL03,//61 +MOCAPNET_JOINT___LEVATOR06_L,//62 +MOCAPNET_JOINT_LEVATOR06_L,//63 +MOCAPNET_JOINT_ENDSITE_LEVATOR06_L,//64 +MOCAPNET_JOINT___LEVATOR06_R,//65 +MOCAPNET_JOINT_LEVATOR06_R,//66 +MOCAPNET_JOINT_ENDSITE_LEVATOR06_R,//67 +MOCAPNET_JOINT_SPECIAL06_L,//68 +MOCAPNET_JOINT_SPECIAL05_L,//69 +MOCAPNET_JOINT_EYE_L,//70 +MOCAPNET_JOINT_ENDSITE_EYE_L,//71 +MOCAPNET_JOINT_ORBICULARIS03_L,//72 +MOCAPNET_JOINT_ENDSITE_ORBICULARIS03_L,//73 +MOCAPNET_JOINT_ORBICULARIS04_L,//74 +MOCAPNET_JOINT_ENDSITE_ORBICULARIS04_L,//75 +MOCAPNET_JOINT_SPECIAL06_R,//76 +MOCAPNET_JOINT_SPECIAL05_R,//77 +MOCAPNET_JOINT_EYE_R,//78 +MOCAPNET_JOINT_ENDSITE_EYE_R,//79 +MOCAPNET_JOINT_ORBICULARIS03_R,//80 +MOCAPNET_JOINT_ENDSITE_ORBICULARIS03_R,//81 +MOCAPNET_JOINT_ORBICULARIS04_R,//82 +MOCAPNET_JOINT_ENDSITE_ORBICULARIS04_R,//83 +MOCAPNET_JOINT___TEMPORALIS01_L,//84 +MOCAPNET_JOINT_TEMPORALIS01_L,//85 +MOCAPNET_JOINT_OCULI02_L,//86 +MOCAPNET_JOINT_OCULI01_L,//87 +MOCAPNET_JOINT_ENDSITE_OCULI01_L,//88 +MOCAPNET_JOINT___TEMPORALIS01_R,//89 +MOCAPNET_JOINT_TEMPORALIS01_R,//90 +MOCAPNET_JOINT_OCULI02_R,//91 +MOCAPNET_JOINT_OCULI01_R,//92 +MOCAPNET_JOINT_ENDSITE_OCULI01_R,//93 +MOCAPNET_JOINT___TEMPORALIS02_L,//94 +MOCAPNET_JOINT_TEMPORALIS02_L,//95 +MOCAPNET_JOINT_RISORIUS02_L,//96 +MOCAPNET_JOINT_RISORIUS03_L,//97 +MOCAPNET_JOINT_ENDSITE_RISORIUS03_L,//98 +MOCAPNET_JOINT___TEMPORALIS02_R,//99 +MOCAPNET_JOINT_TEMPORALIS02_R,//100 +MOCAPNET_JOINT_RISORIUS02_R,//101 +MOCAPNET_JOINT_RISORIUS03_R,//102 +MOCAPNET_JOINT_ENDSITE_RISORIUS03_R,//103 +MOCAPNET_JOINT_RCOLLAR,//104 +MOCAPNET_JOINT_RSHOULDER,//105 +MOCAPNET_JOINT_RELBOW,//106 +MOCAPNET_JOINT_RHAND,//107 +MOCAPNET_JOINT_METACARPAL1_R,//108 +MOCAPNET_JOINT_FINGER2_1_R,//109 +MOCAPNET_JOINT_FINGER2_2_R,//110 +MOCAPNET_JOINT_FINGER2_3_R,//111 +MOCAPNET_JOINT_ENDSITE_FINGER2_3_R,//112 +MOCAPNET_JOINT_METACARPAL2_R,//113 +MOCAPNET_JOINT_FINGER3_1_R,//114 +MOCAPNET_JOINT_FINGER3_2_R,//115 +MOCAPNET_JOINT_FINGER3_3_R,//116 +MOCAPNET_JOINT_ENDSITE_FINGER3_3_R,//117 +MOCAPNET_JOINT___METACARPAL3_R,//118 +MOCAPNET_JOINT_METACARPAL3_R,//119 +MOCAPNET_JOINT_FINGER4_1_R,//120 +MOCAPNET_JOINT_FINGER4_2_R,//121 +MOCAPNET_JOINT_FINGER4_3_R,//122 +MOCAPNET_JOINT_ENDSITE_FINGER4_3_R,//123 +MOCAPNET_JOINT___METACARPAL4_R,//124 +MOCAPNET_JOINT_METACARPAL4_R,//125 +MOCAPNET_JOINT_FINGER5_1_R,//126 +MOCAPNET_JOINT_FINGER5_2_R,//127 +MOCAPNET_JOINT_FINGER5_3_R,//128 +MOCAPNET_JOINT_ENDSITE_FINGER5_3_R,//129 +MOCAPNET_JOINT_RTHUMBBASE,//130 +MOCAPNET_JOINT_RTHUMB,//131 +MOCAPNET_JOINT_FINGER1_2_R,//132 +MOCAPNET_JOINT_FINGER1_3_R,//133 +MOCAPNET_JOINT_ENDSITE_FINGER1_3_R,//134 +MOCAPNET_JOINT_LCOLLAR,//135 +MOCAPNET_JOINT_LSHOULDER,//136 +MOCAPNET_JOINT_LELBOW,//137 +MOCAPNET_JOINT_LHAND,//138 +MOCAPNET_JOINT_METACARPAL1_L,//139 +MOCAPNET_JOINT_FINGER2_1_L,//140 +MOCAPNET_JOINT_FINGER2_2_L,//141 +MOCAPNET_JOINT_FINGER2_3_L,//142 +MOCAPNET_JOINT_ENDSITE_FINGER2_3_L,//143 +MOCAPNET_JOINT_METACARPAL2_L,//144 +MOCAPNET_JOINT_FINGER3_1_L,//145 +MOCAPNET_JOINT_FINGER3_2_L,//146 +MOCAPNET_JOINT_FINGER3_3_L,//147 +MOCAPNET_JOINT_ENDSITE_FINGER3_3_L,//148 +MOCAPNET_JOINT___METACARPAL3_L,//149 +MOCAPNET_JOINT_METACARPAL3_L,//150 +MOCAPNET_JOINT_FINGER4_1_L,//151 +MOCAPNET_JOINT_FINGER4_2_L,//152 +MOCAPNET_JOINT_FINGER4_3_L,//153 +MOCAPNET_JOINT_ENDSITE_FINGER4_3_L,//154 +MOCAPNET_JOINT___METACARPAL4_L,//155 +MOCAPNET_JOINT_METACARPAL4_L,//156 +MOCAPNET_JOINT_FINGER5_1_L,//157 +MOCAPNET_JOINT_FINGER5_2_L,//158 +MOCAPNET_JOINT_FINGER5_3_L,//159 +MOCAPNET_JOINT_ENDSITE_FINGER5_3_L,//160 +MOCAPNET_JOINT_LTHUMBBASE,//161 +MOCAPNET_JOINT_LTHUMB,//162 +MOCAPNET_JOINT_FINGER1_2_L,//163 +MOCAPNET_JOINT_FINGER1_3_L,//164 +MOCAPNET_JOINT_ENDSITE_FINGER1_3_L,//165 +MOCAPNET_JOINT_RBUTTOCK,//166 +MOCAPNET_JOINT_RHIP,//167 +MOCAPNET_JOINT_RKNEE,//168 +MOCAPNET_JOINT_RFOOT,//169 +MOCAPNET_JOINT_TOE1_1_R,//170 +MOCAPNET_JOINT_TOE1_2_R,//171 +MOCAPNET_JOINT_ENDSITE_TOE1_2_R,//172 +MOCAPNET_JOINT_TOE2_1_R,//173 +MOCAPNET_JOINT_TOE2_2_R,//174 +MOCAPNET_JOINT_TOE2_3_R,//175 +MOCAPNET_JOINT_ENDSITE_TOE2_3_R,//176 +MOCAPNET_JOINT_TOE3_1_R,//177 +MOCAPNET_JOINT_TOE3_2_R,//178 +MOCAPNET_JOINT_TOE3_3_R,//179 +MOCAPNET_JOINT_ENDSITE_TOE3_3_R,//180 +MOCAPNET_JOINT_TOE4_1_R,//181 +MOCAPNET_JOINT_TOE4_2_R,//182 +MOCAPNET_JOINT_TOE4_3_R,//183 +MOCAPNET_JOINT_ENDSITE_TOE4_3_R,//184 +MOCAPNET_JOINT_TOE5_1_R,//185 +MOCAPNET_JOINT_TOE5_2_R,//186 +MOCAPNET_JOINT_TOE5_3_R,//187 +MOCAPNET_JOINT_ENDSITE_TOE5_3_R,//188 +MOCAPNET_JOINT_LBUTTOCK,//189 +MOCAPNET_JOINT_LHIP,//190 +MOCAPNET_JOINT_LKNEE,//191 +MOCAPNET_JOINT_LFOOT,//192 +MOCAPNET_JOINT_TOE1_1_L,//193 +MOCAPNET_JOINT_TOE1_2_L,//194 +MOCAPNET_JOINT_ENDSITE_TOE1_2_L,//195 +MOCAPNET_JOINT_TOE2_1_L,//196 +MOCAPNET_JOINT_TOE2_2_L,//197 +MOCAPNET_JOINT_TOE2_3_L,//198 +MOCAPNET_JOINT_ENDSITE_TOE2_3_L,//199 +MOCAPNET_JOINT_TOE3_1_L,//200 +MOCAPNET_JOINT_TOE3_2_L,//201 +MOCAPNET_JOINT_TOE3_3_L,//202 +MOCAPNET_JOINT_ENDSITE_TOE3_3_L,//203 +MOCAPNET_JOINT_TOE4_1_L,//204 +MOCAPNET_JOINT_TOE4_2_L,//205 +MOCAPNET_JOINT_TOE4_3_L,//206 +MOCAPNET_JOINT_ENDSITE_TOE4_3_L,//207 +MOCAPNET_JOINT_TOE5_1_L,//208 +MOCAPNET_JOINT_TOE5_2_L,//209 +MOCAPNET_JOINT_TOE5_3_L,//210 +MOCAPNET_JOINT_ENDSITE_TOE5_3_L//211 +}; + + + + +/** + * @brief An array with string labels for what each element of an input should be after concatenating uncompressed and compressed input. + * Use ./GroundTruthDumper --from dataset/headerWithHeadAndOneMotion.bvh --printc + * to extract this automatically + */ +static const char * MocapNETOutputArrayNames[] = +{ +"hip_Xposition", // 0 +"hip_Yposition", // 1 +"hip_Zposition", // 2 +"hip_Zrotation", // 3 +"hip_Yrotation", // 4 +"hip_Xrotation", // 5 +"abdomen_Zrotation", // 6 + "abdomen_Xrotation", // 7 + "abdomen_Yrotation", // 8 + "chest_Zrotation", // 9 + "chest_Xrotation", // 10 + "chest_Yrotation", // 11 + "neck_Zrotation", // 12 + "neck_Xrotation", // 13 + "neck_Yrotation", // 14 + "neck1_Zrotation", // 15 + "neck1_Xrotation", // 16 + "neck1_Yrotation", // 17 + "head_Zrotation", // 18 + "head_Xrotation", // 19 + "head_Yrotation", // 20 + "__jaw_Zrotation", // 21 + "__jaw_Xrotation", // 22 + "__jaw_Yrotation", // 23 + "jaw_Zrotation", // 24 + "jaw_Xrotation", // 25 + "jaw_Yrotation", // 26 + "special04_Zrotation", // 27 + "special04_Xrotation", // 28 + "special04_Yrotation", // 29 + "oris02_Zrotation", // 30 + "oris02_Xrotation", // 31 + "oris02_Yrotation", // 32 + "oris01_Zrotation", // 33 + "oris01_Xrotation", // 34 + "oris01_Yrotation", // 35 + "oris06.l_Zrotation", // 36 + "oris06.l_Xrotation", // 37 + "oris06.l_Yrotation", // 38 + "oris07.l_Zrotation", // 39 + "oris07.l_Xrotation", // 40 + "oris07.l_Yrotation", // 41 + "oris06.r_Zrotation", // 42 + "oris06.r_Xrotation", // 43 + "oris06.r_Yrotation", // 44 + "oris07.r_Zrotation", // 45 + "oris07.r_Xrotation", // 46 + "oris07.r_Yrotation", // 47 + "tongue00_Zrotation", // 48 + "tongue00_Xrotation", // 49 + "tongue00_Yrotation", // 50 + "tongue01_Zrotation", // 51 + "tongue01_Xrotation", // 52 + "tongue01_Yrotation", // 53 + "tongue02_Zrotation", // 54 + "tongue02_Xrotation", // 55 + "tongue02_Yrotation", // 56 + "tongue03_Zrotation", // 57 + "tongue03_Xrotation", // 58 + "tongue03_Yrotation", // 59 + "__tongue04_Zrotation", // 60 + "__tongue04_Xrotation", // 61 + "__tongue04_Yrotation", // 62 + "tongue04_Zrotation", // 63 + "tongue04_Xrotation", // 64 + "tongue04_Yrotation", // 65 + "tongue07.l_Zrotation", // 66 + "tongue07.l_Xrotation", // 67 + "tongue07.l_Yrotation", // 68 + "tongue07.r_Zrotation", // 69 + "tongue07.r_Xrotation", // 70 + "tongue07.r_Yrotation", // 71 + "tongue06.l_Zrotation", // 72 + "tongue06.l_Xrotation", // 73 + "tongue06.l_Yrotation", // 74 + "tongue06.r_Zrotation", // 75 + "tongue06.r_Xrotation", // 76 + "tongue06.r_Yrotation", // 77 + "tongue05.l_Zrotation", // 78 + "tongue05.l_Xrotation", // 79 + "tongue05.l_Yrotation", // 80 + "tongue05.r_Zrotation", // 81 + "tongue05.r_Xrotation", // 82 + "tongue05.r_Yrotation", // 83 + "__levator02.l_Zrotation", // 84 + "__levator02.l_Xrotation", // 85 + "__levator02.l_Yrotation", // 86 + "levator02.l_Zrotation", // 87 + "levator02.l_Xrotation", // 88 + "levator02.l_Yrotation", // 89 + "levator03.l_Zrotation", // 90 + "levator03.l_Xrotation", // 91 + "levator03.l_Yrotation", // 92 + "levator04.l_Zrotation", // 93 + "levator04.l_Xrotation", // 94 + "levator04.l_Yrotation", // 95 + "levator05.l_Zrotation", // 96 + "levator05.l_Xrotation", // 97 + "levator05.l_Yrotation", // 98 + "__levator02.r_Zrotation", // 99 + "__levator02.r_Xrotation", // 100 + "__levator02.r_Yrotation", // 101 + "levator02.r_Zrotation", // 102 + "levator02.r_Xrotation", // 103 + "levator02.r_Yrotation", // 104 + "levator03.r_Zrotation", // 105 + "levator03.r_Xrotation", // 106 + "levator03.r_Yrotation", // 107 + "levator04.r_Zrotation", // 108 + "levator04.r_Xrotation", // 109 + "levator04.r_Yrotation", // 110 + "levator05.r_Zrotation", // 111 + "levator05.r_Xrotation", // 112 + "levator05.r_Yrotation", // 113 + "__special01_Zrotation", // 114 + "__special01_Xrotation", // 115 + "__special01_Yrotation", // 116 + "special01_Zrotation", // 117 + "special01_Xrotation", // 118 + "special01_Yrotation", // 119 + "oris04.l_Zrotation", // 120 + "oris04.l_Xrotation", // 121 + "oris04.l_Yrotation", // 122 + "oris03.l_Zrotation", // 123 + "oris03.l_Xrotation", // 124 + "oris03.l_Yrotation", // 125 + "oris04.r_Zrotation", // 126 + "oris04.r_Xrotation", // 127 + "oris04.r_Yrotation", // 128 + "oris03.r_Zrotation", // 129 + "oris03.r_Xrotation", // 130 + "oris03.r_Yrotation", // 131 + "oris06_Zrotation", // 132 + "oris06_Xrotation", // 133 + "oris06_Yrotation", // 134 + "oris05_Zrotation", // 135 + "oris05_Xrotation", // 136 + "oris05_Yrotation", // 137 + "__special03_Zrotation", // 138 + "__special03_Xrotation", // 139 + "__special03_Yrotation", // 140 + "special03_Zrotation", // 141 + "special03_Xrotation", // 142 + "special03_Yrotation", // 143 + "__levator06.l_Zrotation", // 144 + "__levator06.l_Xrotation", // 145 + "__levator06.l_Yrotation", // 146 + "levator06.l_Zrotation", // 147 + "levator06.l_Xrotation", // 148 + "levator06.l_Yrotation", // 149 + "__levator06.r_Zrotation", // 150 + "__levator06.r_Xrotation", // 151 + "__levator06.r_Yrotation", // 152 + "levator06.r_Zrotation", // 153 + "levator06.r_Xrotation", // 154 + "levator06.r_Yrotation", // 155 + "special06.l_Zrotation", // 156 + "special06.l_Xrotation", // 157 + "special06.l_Yrotation", // 158 + "special05.l_Zrotation", // 159 + "special05.l_Xrotation", // 160 + "special05.l_Yrotation", // 161 + "eye.l_Zrotation", // 162 + "eye.l_Xrotation", // 163 + "eye.l_Yrotation", // 164 + "orbicularis03.l_Zrotation", // 165 + "orbicularis03.l_Xrotation", // 166 + "orbicularis03.l_Yrotation", // 167 + "orbicularis04.l_Zrotation", // 168 + "orbicularis04.l_Xrotation", // 169 + "orbicularis04.l_Yrotation", // 170 + "special06.r_Zrotation", // 171 + "special06.r_Xrotation", // 172 + "special06.r_Yrotation", // 173 + "special05.r_Zrotation", // 174 + "special05.r_Xrotation", // 175 + "special05.r_Yrotation", // 176 + "eye.r_Zrotation", // 177 + "eye.r_Xrotation", // 178 + "eye.r_Yrotation", // 179 + "orbicularis03.r_Zrotation", // 180 + "orbicularis03.r_Xrotation", // 181 + "orbicularis03.r_Yrotation", // 182 + "orbicularis04.r_Zrotation", // 183 + "orbicularis04.r_Xrotation", // 184 + "orbicularis04.r_Yrotation", // 185 + "__temporalis01.l_Zrotation", // 186 + "__temporalis01.l_Xrotation", // 187 + "__temporalis01.l_Yrotation", // 188 + "temporalis01.l_Zrotation", // 189 + "temporalis01.l_Xrotation", // 190 + "temporalis01.l_Yrotation", // 191 + "oculi02.l_Zrotation", // 192 + "oculi02.l_Xrotation", // 193 + "oculi02.l_Yrotation", // 194 + "oculi01.l_Zrotation", // 195 + "oculi01.l_Xrotation", // 196 + "oculi01.l_Yrotation", // 197 + "__temporalis01.r_Zrotation", // 198 + "__temporalis01.r_Xrotation", // 199 + "__temporalis01.r_Yrotation", // 200 + "temporalis01.r_Zrotation", // 201 + "temporalis01.r_Xrotation", // 202 + "temporalis01.r_Yrotation", // 203 + "oculi02.r_Zrotation", // 204 + "oculi02.r_Xrotation", // 205 + "oculi02.r_Yrotation", // 206 + "oculi01.r_Zrotation", // 207 + "oculi01.r_Xrotation", // 208 + "oculi01.r_Yrotation", // 209 + "__temporalis02.l_Zrotation", // 210 + "__temporalis02.l_Xrotation", // 211 + "__temporalis02.l_Yrotation", // 212 + "temporalis02.l_Zrotation", // 213 + "temporalis02.l_Xrotation", // 214 + "temporalis02.l_Yrotation", // 215 + "risorius02.l_Zrotation", // 216 + "risorius02.l_Xrotation", // 217 + "risorius02.l_Yrotation", // 218 + "risorius03.l_Zrotation", // 219 + "risorius03.l_Xrotation", // 220 + "risorius03.l_Yrotation", // 221 + "__temporalis02.r_Zrotation", // 222 + "__temporalis02.r_Xrotation", // 223 + "__temporalis02.r_Yrotation", // 224 + "temporalis02.r_Zrotation", // 225 + "temporalis02.r_Xrotation", // 226 + "temporalis02.r_Yrotation", // 227 + "risorius02.r_Zrotation", // 228 + "risorius02.r_Xrotation", // 229 + "risorius02.r_Yrotation", // 230 + "risorius03.r_Zrotation", // 231 + "risorius03.r_Xrotation", // 232 + "risorius03.r_Yrotation", // 233 + "rcollar_Zrotation", // 234 + "rcollar_Xrotation", // 235 + "rcollar_Yrotation", // 236 + "rshoulder_Zrotation", // 237 + "rshoulder_Xrotation", // 238 + "rshoulder_Yrotation", // 239 + "relbow_Zrotation", // 240 + "relbow_Xrotation", // 241 + "relbow_Yrotation", // 242 + "rhand_Zrotation", // 243 + "rhand_Xrotation", // 244 + "rhand_Yrotation", // 245 + "metacarpal1.r_Zrotation", // 246 + "metacarpal1.r_Xrotation", // 247 + "metacarpal1.r_Yrotation", // 248 + "finger2-1.r_Zrotation", // 249 + "finger2-1.r_Xrotation", // 250 + "finger2-1.r_Yrotation", // 251 + "finger2-2.r_Zrotation", // 252 + "finger2-2.r_Xrotation", // 253 + "finger2-2.r_Yrotation", // 254 + "finger2-3.r_Zrotation", // 255 + "finger2-3.r_Xrotation", // 256 + "finger2-3.r_Yrotation", // 257 + "metacarpal2.r_Zrotation", // 258 + "metacarpal2.r_Xrotation", // 259 + "metacarpal2.r_Yrotation", // 260 + "finger3-1.r_Zrotation", // 261 + "finger3-1.r_Xrotation", // 262 + "finger3-1.r_Yrotation", // 263 + "finger3-2.r_Zrotation", // 264 + "finger3-2.r_Xrotation", // 265 + "finger3-2.r_Yrotation", // 266 + "finger3-3.r_Zrotation", // 267 + "finger3-3.r_Xrotation", // 268 + "finger3-3.r_Yrotation", // 269 + "__metacarpal3.r_Zrotation", // 270 + "__metacarpal3.r_Xrotation", // 271 + "__metacarpal3.r_Yrotation", // 272 + "metacarpal3.r_Zrotation", // 273 + "metacarpal3.r_Xrotation", // 274 + "metacarpal3.r_Yrotation", // 275 + "finger4-1.r_Zrotation", // 276 + "finger4-1.r_Xrotation", // 277 + "finger4-1.r_Yrotation", // 278 + "finger4-2.r_Zrotation", // 279 + "finger4-2.r_Xrotation", // 280 + "finger4-2.r_Yrotation", // 281 + "finger4-3.r_Zrotation", // 282 + "finger4-3.r_Xrotation", // 283 + "finger4-3.r_Yrotation", // 284 + "__metacarpal4.r_Zrotation", // 285 + "__metacarpal4.r_Xrotation", // 286 + "__metacarpal4.r_Yrotation", // 287 + "metacarpal4.r_Zrotation", // 288 + "metacarpal4.r_Xrotation", // 289 + "metacarpal4.r_Yrotation", // 290 + "finger5-1.r_Zrotation", // 291 + "finger5-1.r_Xrotation", // 292 + "finger5-1.r_Yrotation", // 293 + "finger5-2.r_Zrotation", // 294 + "finger5-2.r_Xrotation", // 295 + "finger5-2.r_Yrotation", // 296 + "finger5-3.r_Zrotation", // 297 + "finger5-3.r_Xrotation", // 298 + "finger5-3.r_Yrotation", // 299 + "rthumbBase_Zrotation", // 300 + "rthumbBase_Xrotation", // 301 + "rthumbBase_Yrotation", // 302 + "rthumb_Zrotation", // 303 + "rthumb_Xrotation", // 304 + "rthumb_Yrotation", // 305 + "finger1-2.r_Zrotation", // 306 + "finger1-2.r_Xrotation", // 307 + "finger1-2.r_Yrotation", // 308 + "finger1-3.r_Zrotation", // 309 + "finger1-3.r_Xrotation", // 310 + "finger1-3.r_Yrotation", // 311 + "lcollar_Zrotation", // 312 + "lcollar_Xrotation", // 313 + "lcollar_Yrotation", // 314 + "lshoulder_Zrotation", // 315 + "lshoulder_Xrotation", // 316 + "lshoulder_Yrotation", // 317 + "lelbow_Zrotation", // 318 + "lelbow_Xrotation", // 319 + "lelbow_Yrotation", // 320 + "lhand_Zrotation", // 321 + "lhand_Xrotation", // 322 + "lhand_Yrotation", // 323 + "metacarpal1.l_Zrotation", // 324 + "metacarpal1.l_Xrotation", // 325 + "metacarpal1.l_Yrotation", // 326 + "finger2-1.l_Zrotation", // 327 + "finger2-1.l_Xrotation", // 328 + "finger2-1.l_Yrotation", // 329 + "finger2-2.l_Zrotation", // 330 + "finger2-2.l_Xrotation", // 331 + "finger2-2.l_Yrotation", // 332 + "finger2-3.l_Zrotation", // 333 + "finger2-3.l_Xrotation", // 334 + "finger2-3.l_Yrotation", // 335 + "metacarpal2.l_Zrotation", // 336 + "metacarpal2.l_Xrotation", // 337 + "metacarpal2.l_Yrotation", // 338 + "finger3-1.l_Zrotation", // 339 + "finger3-1.l_Xrotation", // 340 + "finger3-1.l_Yrotation", // 341 + "finger3-2.l_Zrotation", // 342 + "finger3-2.l_Xrotation", // 343 + "finger3-2.l_Yrotation", // 344 + "finger3-3.l_Zrotation", // 345 + "finger3-3.l_Xrotation", // 346 + "finger3-3.l_Yrotation", // 347 + "__metacarpal3.l_Zrotation", // 348 + "__metacarpal3.l_Xrotation", // 349 + "__metacarpal3.l_Yrotation", // 350 + "metacarpal3.l_Zrotation", // 351 + "metacarpal3.l_Xrotation", // 352 + "metacarpal3.l_Yrotation", // 353 + "finger4-1.l_Zrotation", // 354 + "finger4-1.l_Xrotation", // 355 + "finger4-1.l_Yrotation", // 356 + "finger4-2.l_Zrotation", // 357 + "finger4-2.l_Xrotation", // 358 + "finger4-2.l_Yrotation", // 359 + "finger4-3.l_Zrotation", // 360 + "finger4-3.l_Xrotation", // 361 + "finger4-3.l_Yrotation", // 362 + "__metacarpal4.l_Zrotation", // 363 + "__metacarpal4.l_Xrotation", // 364 + "__metacarpal4.l_Yrotation", // 365 + "metacarpal4.l_Zrotation", // 366 + "metacarpal4.l_Xrotation", // 367 + "metacarpal4.l_Yrotation", // 368 + "finger5-1.l_Zrotation", // 369 + "finger5-1.l_Xrotation", // 370 + "finger5-1.l_Yrotation", // 371 + "finger5-2.l_Zrotation", // 372 + "finger5-2.l_Xrotation", // 373 + "finger5-2.l_Yrotation", // 374 + "finger5-3.l_Zrotation", // 375 + "finger5-3.l_Xrotation", // 376 + "finger5-3.l_Yrotation", // 377 + "lthumbBase_Zrotation", // 378 + "lthumbBase_Xrotation", // 379 + "lthumbBase_Yrotation", // 380 + "lthumb_Zrotation", // 381 + "lthumb_Xrotation", // 382 + "lthumb_Yrotation", // 383 + "finger1-2.l_Zrotation", // 384 + "finger1-2.l_Xrotation", // 385 + "finger1-2.l_Yrotation", // 386 + "finger1-3.l_Zrotation", // 387 + "finger1-3.l_Xrotation", // 388 + "finger1-3.l_Yrotation", // 389 + "rbuttock_Zrotation", // 390 + "rbuttock_Xrotation", // 391 + "rbuttock_Yrotation", // 392 + "rhip_Zrotation", // 393 + "rhip_Xrotation", // 394 + "rhip_Yrotation", // 395 + "rknee_Zrotation", // 396 + "rknee_Xrotation", // 397 + "rknee_Yrotation", // 398 + "rfoot_Zrotation", // 399 + "rfoot_Xrotation", // 400 + "rfoot_Yrotation", // 401 + "toe1-1.r_Zrotation", // 402 + "toe1-1.r_Xrotation", // 403 + "toe1-1.r_Yrotation", // 404 + "toe1-2.r_Zrotation", // 405 + "toe1-2.r_Xrotation", // 406 + "toe1-2.r_Yrotation", // 407 + "toe2-1.r_Zrotation", // 408 + "toe2-1.r_Xrotation", // 409 + "toe2-1.r_Yrotation", // 410 + "toe2-2.r_Zrotation", // 411 + "toe2-2.r_Xrotation", // 412 + "toe2-2.r_Yrotation", // 413 + "toe2-3.r_Zrotation", // 414 + "toe2-3.r_Xrotation", // 415 + "toe2-3.r_Yrotation", // 416 + "toe3-1.r_Zrotation", // 417 + "toe3-1.r_Xrotation", // 418 + "toe3-1.r_Yrotation", // 419 + "toe3-2.r_Zrotation", // 420 + "toe3-2.r_Xrotation", // 421 + "toe3-2.r_Yrotation", // 422 + "toe3-3.r_Zrotation", // 423 + "toe3-3.r_Xrotation", // 424 + "toe3-3.r_Yrotation", // 425 + "toe4-1.r_Zrotation", // 426 + "toe4-1.r_Xrotation", // 427 + "toe4-1.r_Yrotation", // 428 + "toe4-2.r_Zrotation", // 429 + "toe4-2.r_Xrotation", // 430 + "toe4-2.r_Yrotation", // 431 + "toe4-3.r_Zrotation", // 432 + "toe4-3.r_Xrotation", // 433 + "toe4-3.r_Yrotation", // 434 + "toe5-1.r_Zrotation", // 435 + "toe5-1.r_Xrotation", // 436 + "toe5-1.r_Yrotation", // 437 + "toe5-2.r_Zrotation", // 438 + "toe5-2.r_Xrotation", // 439 + "toe5-2.r_Yrotation", // 440 + "toe5-3.r_Zrotation", // 441 + "toe5-3.r_Xrotation", // 442 + "toe5-3.r_Yrotation", // 443 + "lbuttock_Zrotation", // 444 + "lbuttock_Xrotation", // 445 + "lbuttock_Yrotation", // 446 + "lhip_Zrotation", // 447 + "lhip_Xrotation", // 448 + "lhip_Yrotation", // 449 + "lknee_Zrotation", // 450 + "lknee_Xrotation", // 451 + "lknee_Yrotation", // 452 + "lfoot_Zrotation", // 453 + "lfoot_Xrotation", // 454 + "lfoot_Yrotation", // 455 + "toe1-1.l_Zrotation", // 456 + "toe1-1.l_Xrotation", // 457 + "toe1-1.l_Yrotation", // 458 + "toe1-2.l_Zrotation", // 459 + "toe1-2.l_Xrotation", // 460 + "toe1-2.l_Yrotation", // 461 + "toe2-1.l_Zrotation", // 462 + "toe2-1.l_Xrotation", // 463 + "toe2-1.l_Yrotation", // 464 + "toe2-2.l_Zrotation", // 465 + "toe2-2.l_Xrotation", // 466 + "toe2-2.l_Yrotation", // 467 + "toe2-3.l_Zrotation", // 468 + "toe2-3.l_Xrotation", // 469 + "toe2-3.l_Yrotation", // 470 + "toe3-1.l_Zrotation", // 471 + "toe3-1.l_Xrotation", // 472 + "toe3-1.l_Yrotation", // 473 + "toe3-2.l_Zrotation", // 474 + "toe3-2.l_Xrotation", // 475 + "toe3-2.l_Yrotation", // 476 + "toe3-3.l_Zrotation", // 477 + "toe3-3.l_Xrotation", // 478 + "toe3-3.l_Yrotation", // 479 + "toe4-1.l_Zrotation", // 480 + "toe4-1.l_Xrotation", // 481 + "toe4-1.l_Yrotation", // 482 + "toe4-2.l_Zrotation", // 483 + "toe4-2.l_Xrotation", // 484 + "toe4-2.l_Yrotation", // 485 + "toe4-3.l_Zrotation", // 486 + "toe4-3.l_Xrotation", // 487 + "toe4-3.l_Yrotation", // 488 + "toe5-1.l_Zrotation", // 489 + "toe5-1.l_Xrotation", // 490 + "toe5-1.l_Yrotation", // 491 + "toe5-2.l_Zrotation", // 492 + "toe5-2.l_Xrotation", // 493 + "toe5-2.l_Yrotation", // 494 + "toe5-3.l_Zrotation", // 495 + "toe5-3.l_Xrotation", // 496 + "toe5-3.l_Yrotation" // 497 +}; + + + +/** + * @brief This is a programmer friendly enumerator of joint output extracted from MocapNET. + * Use ./GroundTruthDumper --from dataset/headerWithHeadAndOneMotion.bvh --printc + * to extract this automatically + */ +enum MOCAPNET_Output_Joints +{ +MOCAPNET_OUTPUT_HIP_XPOSITION = 0, +MOCAPNET_OUTPUT_HIP_YPOSITION,//1 +MOCAPNET_OUTPUT_HIP_ZPOSITION,//2 +MOCAPNET_OUTPUT_HIP_ZROTATION,//3 +MOCAPNET_OUTPUT_HIP_YROTATION,//4 +MOCAPNET_OUTPUT_HIP_XROTATION,//5 +MOCAPNET_OUTPUT_ABDOMEN_ZROTATION,//6 +MOCAPNET_OUTPUT_ABDOMEN_XROTATION,//7 +MOCAPNET_OUTPUT_ABDOMEN_YROTATION,//8 +MOCAPNET_OUTPUT_CHEST_ZROTATION,//9 +MOCAPNET_OUTPUT_CHEST_XROTATION,//10 +MOCAPNET_OUTPUT_CHEST_YROTATION,//11 +MOCAPNET_OUTPUT_NECK_ZROTATION,//12 +MOCAPNET_OUTPUT_NECK_XROTATION,//13 +MOCAPNET_OUTPUT_NECK_YROTATION,//14 +MOCAPNET_OUTPUT_NECK1_ZROTATION,//15 +MOCAPNET_OUTPUT_NECK1_XROTATION,//16 +MOCAPNET_OUTPUT_NECK1_YROTATION,//17 +MOCAPNET_OUTPUT_HEAD_ZROTATION,//18 +MOCAPNET_OUTPUT_HEAD_XROTATION,//19 +MOCAPNET_OUTPUT_HEAD_YROTATION,//20 +MOCAPNET_OUTPUT___JAW_ZROTATION,//21 +MOCAPNET_OUTPUT___JAW_XROTATION,//22 +MOCAPNET_OUTPUT___JAW_YROTATION,//23 +MOCAPNET_OUTPUT_JAW_ZROTATION,//24 +MOCAPNET_OUTPUT_JAW_XROTATION,//25 +MOCAPNET_OUTPUT_JAW_YROTATION,//26 +MOCAPNET_OUTPUT_SPECIAL04_ZROTATION,//27 +MOCAPNET_OUTPUT_SPECIAL04_XROTATION,//28 +MOCAPNET_OUTPUT_SPECIAL04_YROTATION,//29 +MOCAPNET_OUTPUT_ORIS02_ZROTATION,//30 +MOCAPNET_OUTPUT_ORIS02_XROTATION,//31 +MOCAPNET_OUTPUT_ORIS02_YROTATION,//32 +MOCAPNET_OUTPUT_ORIS01_ZROTATION,//33 +MOCAPNET_OUTPUT_ORIS01_XROTATION,//34 +MOCAPNET_OUTPUT_ORIS01_YROTATION,//35 +MOCAPNET_OUTPUT_ORIS06_L_ZROTATION,//36 +MOCAPNET_OUTPUT_ORIS06_L_XROTATION,//37 +MOCAPNET_OUTPUT_ORIS06_L_YROTATION,//38 +MOCAPNET_OUTPUT_ORIS07_L_ZROTATION,//39 +MOCAPNET_OUTPUT_ORIS07_L_XROTATION,//40 +MOCAPNET_OUTPUT_ORIS07_L_YROTATION,//41 +MOCAPNET_OUTPUT_ORIS06_R_ZROTATION,//42 +MOCAPNET_OUTPUT_ORIS06_R_XROTATION,//43 +MOCAPNET_OUTPUT_ORIS06_R_YROTATION,//44 +MOCAPNET_OUTPUT_ORIS07_R_ZROTATION,//45 +MOCAPNET_OUTPUT_ORIS07_R_XROTATION,//46 +MOCAPNET_OUTPUT_ORIS07_R_YROTATION,//47 +MOCAPNET_OUTPUT_TONGUE00_ZROTATION,//48 +MOCAPNET_OUTPUT_TONGUE00_XROTATION,//49 +MOCAPNET_OUTPUT_TONGUE00_YROTATION,//50 +MOCAPNET_OUTPUT_TONGUE01_ZROTATION,//51 +MOCAPNET_OUTPUT_TONGUE01_XROTATION,//52 +MOCAPNET_OUTPUT_TONGUE01_YROTATION,//53 +MOCAPNET_OUTPUT_TONGUE02_ZROTATION,//54 +MOCAPNET_OUTPUT_TONGUE02_XROTATION,//55 +MOCAPNET_OUTPUT_TONGUE02_YROTATION,//56 +MOCAPNET_OUTPUT_TONGUE03_ZROTATION,//57 +MOCAPNET_OUTPUT_TONGUE03_XROTATION,//58 +MOCAPNET_OUTPUT_TONGUE03_YROTATION,//59 +MOCAPNET_OUTPUT___TONGUE04_ZROTATION,//60 +MOCAPNET_OUTPUT___TONGUE04_XROTATION,//61 +MOCAPNET_OUTPUT___TONGUE04_YROTATION,//62 +MOCAPNET_OUTPUT_TONGUE04_ZROTATION,//63 +MOCAPNET_OUTPUT_TONGUE04_XROTATION,//64 +MOCAPNET_OUTPUT_TONGUE04_YROTATION,//65 +MOCAPNET_OUTPUT_TONGUE07_L_ZROTATION,//66 +MOCAPNET_OUTPUT_TONGUE07_L_XROTATION,//67 +MOCAPNET_OUTPUT_TONGUE07_L_YROTATION,//68 +MOCAPNET_OUTPUT_TONGUE07_R_ZROTATION,//69 +MOCAPNET_OUTPUT_TONGUE07_R_XROTATION,//70 +MOCAPNET_OUTPUT_TONGUE07_R_YROTATION,//71 +MOCAPNET_OUTPUT_TONGUE06_L_ZROTATION,//72 +MOCAPNET_OUTPUT_TONGUE06_L_XROTATION,//73 +MOCAPNET_OUTPUT_TONGUE06_L_YROTATION,//74 +MOCAPNET_OUTPUT_TONGUE06_R_ZROTATION,//75 +MOCAPNET_OUTPUT_TONGUE06_R_XROTATION,//76 +MOCAPNET_OUTPUT_TONGUE06_R_YROTATION,//77 +MOCAPNET_OUTPUT_TONGUE05_L_ZROTATION,//78 +MOCAPNET_OUTPUT_TONGUE05_L_XROTATION,//79 +MOCAPNET_OUTPUT_TONGUE05_L_YROTATION,//80 +MOCAPNET_OUTPUT_TONGUE05_R_ZROTATION,//81 +MOCAPNET_OUTPUT_TONGUE05_R_XROTATION,//82 +MOCAPNET_OUTPUT_TONGUE05_R_YROTATION,//83 +MOCAPNET_OUTPUT___LEVATOR02_L_ZROTATION,//84 +MOCAPNET_OUTPUT___LEVATOR02_L_XROTATION,//85 +MOCAPNET_OUTPUT___LEVATOR02_L_YROTATION,//86 +MOCAPNET_OUTPUT_LEVATOR02_L_ZROTATION,//87 +MOCAPNET_OUTPUT_LEVATOR02_L_XROTATION,//88 +MOCAPNET_OUTPUT_LEVATOR02_L_YROTATION,//89 +MOCAPNET_OUTPUT_LEVATOR03_L_ZROTATION,//90 +MOCAPNET_OUTPUT_LEVATOR03_L_XROTATION,//91 +MOCAPNET_OUTPUT_LEVATOR03_L_YROTATION,//92 +MOCAPNET_OUTPUT_LEVATOR04_L_ZROTATION,//93 +MOCAPNET_OUTPUT_LEVATOR04_L_XROTATION,//94 +MOCAPNET_OUTPUT_LEVATOR04_L_YROTATION,//95 +MOCAPNET_OUTPUT_LEVATOR05_L_ZROTATION,//96 +MOCAPNET_OUTPUT_LEVATOR05_L_XROTATION,//97 +MOCAPNET_OUTPUT_LEVATOR05_L_YROTATION,//98 +MOCAPNET_OUTPUT___LEVATOR02_R_ZROTATION,//99 +MOCAPNET_OUTPUT___LEVATOR02_R_XROTATION,//100 +MOCAPNET_OUTPUT___LEVATOR02_R_YROTATION,//101 +MOCAPNET_OUTPUT_LEVATOR02_R_ZROTATION,//102 +MOCAPNET_OUTPUT_LEVATOR02_R_XROTATION,//103 +MOCAPNET_OUTPUT_LEVATOR02_R_YROTATION,//104 +MOCAPNET_OUTPUT_LEVATOR03_R_ZROTATION,//105 +MOCAPNET_OUTPUT_LEVATOR03_R_XROTATION,//106 +MOCAPNET_OUTPUT_LEVATOR03_R_YROTATION,//107 +MOCAPNET_OUTPUT_LEVATOR04_R_ZROTATION,//108 +MOCAPNET_OUTPUT_LEVATOR04_R_XROTATION,//109 +MOCAPNET_OUTPUT_LEVATOR04_R_YROTATION,//110 +MOCAPNET_OUTPUT_LEVATOR05_R_ZROTATION,//111 +MOCAPNET_OUTPUT_LEVATOR05_R_XROTATION,//112 +MOCAPNET_OUTPUT_LEVATOR05_R_YROTATION,//113 +MOCAPNET_OUTPUT___SPECIAL01_ZROTATION,//114 +MOCAPNET_OUTPUT___SPECIAL01_XROTATION,//115 +MOCAPNET_OUTPUT___SPECIAL01_YROTATION,//116 +MOCAPNET_OUTPUT_SPECIAL01_ZROTATION,//117 +MOCAPNET_OUTPUT_SPECIAL01_XROTATION,//118 +MOCAPNET_OUTPUT_SPECIAL01_YROTATION,//119 +MOCAPNET_OUTPUT_ORIS04_L_ZROTATION,//120 +MOCAPNET_OUTPUT_ORIS04_L_XROTATION,//121 +MOCAPNET_OUTPUT_ORIS04_L_YROTATION,//122 +MOCAPNET_OUTPUT_ORIS03_L_ZROTATION,//123 +MOCAPNET_OUTPUT_ORIS03_L_XROTATION,//124 +MOCAPNET_OUTPUT_ORIS03_L_YROTATION,//125 +MOCAPNET_OUTPUT_ORIS04_R_ZROTATION,//126 +MOCAPNET_OUTPUT_ORIS04_R_XROTATION,//127 +MOCAPNET_OUTPUT_ORIS04_R_YROTATION,//128 +MOCAPNET_OUTPUT_ORIS03_R_ZROTATION,//129 +MOCAPNET_OUTPUT_ORIS03_R_XROTATION,//130 +MOCAPNET_OUTPUT_ORIS03_R_YROTATION,//131 +MOCAPNET_OUTPUT_ORIS06_ZROTATION,//132 +MOCAPNET_OUTPUT_ORIS06_XROTATION,//133 +MOCAPNET_OUTPUT_ORIS06_YROTATION,//134 +MOCAPNET_OUTPUT_ORIS05_ZROTATION,//135 +MOCAPNET_OUTPUT_ORIS05_XROTATION,//136 +MOCAPNET_OUTPUT_ORIS05_YROTATION,//137 +MOCAPNET_OUTPUT___SPECIAL03_ZROTATION,//138 +MOCAPNET_OUTPUT___SPECIAL03_XROTATION,//139 +MOCAPNET_OUTPUT___SPECIAL03_YROTATION,//140 +MOCAPNET_OUTPUT_SPECIAL03_ZROTATION,//141 +MOCAPNET_OUTPUT_SPECIAL03_XROTATION,//142 +MOCAPNET_OUTPUT_SPECIAL03_YROTATION,//143 +MOCAPNET_OUTPUT___LEVATOR06_L_ZROTATION,//144 +MOCAPNET_OUTPUT___LEVATOR06_L_XROTATION,//145 +MOCAPNET_OUTPUT___LEVATOR06_L_YROTATION,//146 +MOCAPNET_OUTPUT_LEVATOR06_L_ZROTATION,//147 +MOCAPNET_OUTPUT_LEVATOR06_L_XROTATION,//148 +MOCAPNET_OUTPUT_LEVATOR06_L_YROTATION,//149 +MOCAPNET_OUTPUT___LEVATOR06_R_ZROTATION,//150 +MOCAPNET_OUTPUT___LEVATOR06_R_XROTATION,//151 +MOCAPNET_OUTPUT___LEVATOR06_R_YROTATION,//152 +MOCAPNET_OUTPUT_LEVATOR06_R_ZROTATION,//153 +MOCAPNET_OUTPUT_LEVATOR06_R_XROTATION,//154 +MOCAPNET_OUTPUT_LEVATOR06_R_YROTATION,//155 +MOCAPNET_OUTPUT_SPECIAL06_L_ZROTATION,//156 +MOCAPNET_OUTPUT_SPECIAL06_L_XROTATION,//157 +MOCAPNET_OUTPUT_SPECIAL06_L_YROTATION,//158 +MOCAPNET_OUTPUT_SPECIAL05_L_ZROTATION,//159 +MOCAPNET_OUTPUT_SPECIAL05_L_XROTATION,//160 +MOCAPNET_OUTPUT_SPECIAL05_L_YROTATION,//161 +MOCAPNET_OUTPUT_EYE_L_ZROTATION,//162 +MOCAPNET_OUTPUT_EYE_L_XROTATION,//163 +MOCAPNET_OUTPUT_EYE_L_YROTATION,//164 +MOCAPNET_OUTPUT_ORBICULARIS03_L_ZROTATION,//165 +MOCAPNET_OUTPUT_ORBICULARIS03_L_XROTATION,//166 +MOCAPNET_OUTPUT_ORBICULARIS03_L_YROTATION,//167 +MOCAPNET_OUTPUT_ORBICULARIS04_L_ZROTATION,//168 +MOCAPNET_OUTPUT_ORBICULARIS04_L_XROTATION,//169 +MOCAPNET_OUTPUT_ORBICULARIS04_L_YROTATION,//170 +MOCAPNET_OUTPUT_SPECIAL06_R_ZROTATION,//171 +MOCAPNET_OUTPUT_SPECIAL06_R_XROTATION,//172 +MOCAPNET_OUTPUT_SPECIAL06_R_YROTATION,//173 +MOCAPNET_OUTPUT_SPECIAL05_R_ZROTATION,//174 +MOCAPNET_OUTPUT_SPECIAL05_R_XROTATION,//175 +MOCAPNET_OUTPUT_SPECIAL05_R_YROTATION,//176 +MOCAPNET_OUTPUT_EYE_R_ZROTATION,//177 +MOCAPNET_OUTPUT_EYE_R_XROTATION,//178 +MOCAPNET_OUTPUT_EYE_R_YROTATION,//179 +MOCAPNET_OUTPUT_ORBICULARIS03_R_ZROTATION,//180 +MOCAPNET_OUTPUT_ORBICULARIS03_R_XROTATION,//181 +MOCAPNET_OUTPUT_ORBICULARIS03_R_YROTATION,//182 +MOCAPNET_OUTPUT_ORBICULARIS04_R_ZROTATION,//183 +MOCAPNET_OUTPUT_ORBICULARIS04_R_XROTATION,//184 +MOCAPNET_OUTPUT_ORBICULARIS04_R_YROTATION,//185 +MOCAPNET_OUTPUT___TEMPORALIS01_L_ZROTATION,//186 +MOCAPNET_OUTPUT___TEMPORALIS01_L_XROTATION,//187 +MOCAPNET_OUTPUT___TEMPORALIS01_L_YROTATION,//188 +MOCAPNET_OUTPUT_TEMPORALIS01_L_ZROTATION,//189 +MOCAPNET_OUTPUT_TEMPORALIS01_L_XROTATION,//190 +MOCAPNET_OUTPUT_TEMPORALIS01_L_YROTATION,//191 +MOCAPNET_OUTPUT_OCULI02_L_ZROTATION,//192 +MOCAPNET_OUTPUT_OCULI02_L_XROTATION,//193 +MOCAPNET_OUTPUT_OCULI02_L_YROTATION,//194 +MOCAPNET_OUTPUT_OCULI01_L_ZROTATION,//195 +MOCAPNET_OUTPUT_OCULI01_L_XROTATION,//196 +MOCAPNET_OUTPUT_OCULI01_L_YROTATION,//197 +MOCAPNET_OUTPUT___TEMPORALIS01_R_ZROTATION,//198 +MOCAPNET_OUTPUT___TEMPORALIS01_R_XROTATION,//199 +MOCAPNET_OUTPUT___TEMPORALIS01_R_YROTATION,//200 +MOCAPNET_OUTPUT_TEMPORALIS01_R_ZROTATION,//201 +MOCAPNET_OUTPUT_TEMPORALIS01_R_XROTATION,//202 +MOCAPNET_OUTPUT_TEMPORALIS01_R_YROTATION,//203 +MOCAPNET_OUTPUT_OCULI02_R_ZROTATION,//204 +MOCAPNET_OUTPUT_OCULI02_R_XROTATION,//205 +MOCAPNET_OUTPUT_OCULI02_R_YROTATION,//206 +MOCAPNET_OUTPUT_OCULI01_R_ZROTATION,//207 +MOCAPNET_OUTPUT_OCULI01_R_XROTATION,//208 +MOCAPNET_OUTPUT_OCULI01_R_YROTATION,//209 +MOCAPNET_OUTPUT___TEMPORALIS02_L_ZROTATION,//210 +MOCAPNET_OUTPUT___TEMPORALIS02_L_XROTATION,//211 +MOCAPNET_OUTPUT___TEMPORALIS02_L_YROTATION,//212 +MOCAPNET_OUTPUT_TEMPORALIS02_L_ZROTATION,//213 +MOCAPNET_OUTPUT_TEMPORALIS02_L_XROTATION,//214 +MOCAPNET_OUTPUT_TEMPORALIS02_L_YROTATION,//215 +MOCAPNET_OUTPUT_RISORIUS02_L_ZROTATION,//216 +MOCAPNET_OUTPUT_RISORIUS02_L_XROTATION,//217 +MOCAPNET_OUTPUT_RISORIUS02_L_YROTATION,//218 +MOCAPNET_OUTPUT_RISORIUS03_L_ZROTATION,//219 +MOCAPNET_OUTPUT_RISORIUS03_L_XROTATION,//220 +MOCAPNET_OUTPUT_RISORIUS03_L_YROTATION,//221 +MOCAPNET_OUTPUT___TEMPORALIS02_R_ZROTATION,//222 +MOCAPNET_OUTPUT___TEMPORALIS02_R_XROTATION,//223 +MOCAPNET_OUTPUT___TEMPORALIS02_R_YROTATION,//224 +MOCAPNET_OUTPUT_TEMPORALIS02_R_ZROTATION,//225 +MOCAPNET_OUTPUT_TEMPORALIS02_R_XROTATION,//226 +MOCAPNET_OUTPUT_TEMPORALIS02_R_YROTATION,//227 +MOCAPNET_OUTPUT_RISORIUS02_R_ZROTATION,//228 +MOCAPNET_OUTPUT_RISORIUS02_R_XROTATION,//229 +MOCAPNET_OUTPUT_RISORIUS02_R_YROTATION,//230 +MOCAPNET_OUTPUT_RISORIUS03_R_ZROTATION,//231 +MOCAPNET_OUTPUT_RISORIUS03_R_XROTATION,//232 +MOCAPNET_OUTPUT_RISORIUS03_R_YROTATION,//233 +MOCAPNET_OUTPUT_RCOLLAR_ZROTATION,//234 +MOCAPNET_OUTPUT_RCOLLAR_XROTATION,//235 +MOCAPNET_OUTPUT_RCOLLAR_YROTATION,//236 +MOCAPNET_OUTPUT_RSHOULDER_ZROTATION,//237 +MOCAPNET_OUTPUT_RSHOULDER_XROTATION,//238 +MOCAPNET_OUTPUT_RSHOULDER_YROTATION,//239 +MOCAPNET_OUTPUT_RELBOW_ZROTATION,//240 +MOCAPNET_OUTPUT_RELBOW_XROTATION,//241 +MOCAPNET_OUTPUT_RELBOW_YROTATION,//242 +MOCAPNET_OUTPUT_RHAND_ZROTATION,//243 +MOCAPNET_OUTPUT_RHAND_XROTATION,//244 +MOCAPNET_OUTPUT_RHAND_YROTATION,//245 +MOCAPNET_OUTPUT_METACARPAL1_R_ZROTATION,//246 +MOCAPNET_OUTPUT_METACARPAL1_R_XROTATION,//247 +MOCAPNET_OUTPUT_METACARPAL1_R_YROTATION,//248 +MOCAPNET_OUTPUT_FINGER2_1_R_ZROTATION,//249 +MOCAPNET_OUTPUT_FINGER2_1_R_XROTATION,//250 +MOCAPNET_OUTPUT_FINGER2_1_R_YROTATION,//251 +MOCAPNET_OUTPUT_FINGER2_2_R_ZROTATION,//252 +MOCAPNET_OUTPUT_FINGER2_2_R_XROTATION,//253 +MOCAPNET_OUTPUT_FINGER2_2_R_YROTATION,//254 +MOCAPNET_OUTPUT_FINGER2_3_R_ZROTATION,//255 +MOCAPNET_OUTPUT_FINGER2_3_R_XROTATION,//256 +MOCAPNET_OUTPUT_FINGER2_3_R_YROTATION,//257 +MOCAPNET_OUTPUT_METACARPAL2_R_ZROTATION,//258 +MOCAPNET_OUTPUT_METACARPAL2_R_XROTATION,//259 +MOCAPNET_OUTPUT_METACARPAL2_R_YROTATION,//260 +MOCAPNET_OUTPUT_FINGER3_1_R_ZROTATION,//261 +MOCAPNET_OUTPUT_FINGER3_1_R_XROTATION,//262 +MOCAPNET_OUTPUT_FINGER3_1_R_YROTATION,//263 +MOCAPNET_OUTPUT_FINGER3_2_R_ZROTATION,//264 +MOCAPNET_OUTPUT_FINGER3_2_R_XROTATION,//265 +MOCAPNET_OUTPUT_FINGER3_2_R_YROTATION,//266 +MOCAPNET_OUTPUT_FINGER3_3_R_ZROTATION,//267 +MOCAPNET_OUTPUT_FINGER3_3_R_XROTATION,//268 +MOCAPNET_OUTPUT_FINGER3_3_R_YROTATION,//269 +MOCAPNET_OUTPUT___METACARPAL3_R_ZROTATION,//270 +MOCAPNET_OUTPUT___METACARPAL3_R_XROTATION,//271 +MOCAPNET_OUTPUT___METACARPAL3_R_YROTATION,//272 +MOCAPNET_OUTPUT_METACARPAL3_R_ZROTATION,//273 +MOCAPNET_OUTPUT_METACARPAL3_R_XROTATION,//274 +MOCAPNET_OUTPUT_METACARPAL3_R_YROTATION,//275 +MOCAPNET_OUTPUT_FINGER4_1_R_ZROTATION,//276 +MOCAPNET_OUTPUT_FINGER4_1_R_XROTATION,//277 +MOCAPNET_OUTPUT_FINGER4_1_R_YROTATION,//278 +MOCAPNET_OUTPUT_FINGER4_2_R_ZROTATION,//279 +MOCAPNET_OUTPUT_FINGER4_2_R_XROTATION,//280 +MOCAPNET_OUTPUT_FINGER4_2_R_YROTATION,//281 +MOCAPNET_OUTPUT_FINGER4_3_R_ZROTATION,//282 +MOCAPNET_OUTPUT_FINGER4_3_R_XROTATION,//283 +MOCAPNET_OUTPUT_FINGER4_3_R_YROTATION,//284 +MOCAPNET_OUTPUT___METACARPAL4_R_ZROTATION,//285 +MOCAPNET_OUTPUT___METACARPAL4_R_XROTATION,//286 +MOCAPNET_OUTPUT___METACARPAL4_R_YROTATION,//287 +MOCAPNET_OUTPUT_METACARPAL4_R_ZROTATION,//288 +MOCAPNET_OUTPUT_METACARPAL4_R_XROTATION,//289 +MOCAPNET_OUTPUT_METACARPAL4_R_YROTATION,//290 +MOCAPNET_OUTPUT_FINGER5_1_R_ZROTATION,//291 +MOCAPNET_OUTPUT_FINGER5_1_R_XROTATION,//292 +MOCAPNET_OUTPUT_FINGER5_1_R_YROTATION,//293 +MOCAPNET_OUTPUT_FINGER5_2_R_ZROTATION,//294 +MOCAPNET_OUTPUT_FINGER5_2_R_XROTATION,//295 +MOCAPNET_OUTPUT_FINGER5_2_R_YROTATION,//296 +MOCAPNET_OUTPUT_FINGER5_3_R_ZROTATION,//297 +MOCAPNET_OUTPUT_FINGER5_3_R_XROTATION,//298 +MOCAPNET_OUTPUT_FINGER5_3_R_YROTATION,//299 +MOCAPNET_OUTPUT_RTHUMBBASE_ZROTATION,//300 +MOCAPNET_OUTPUT_RTHUMBBASE_XROTATION,//301 +MOCAPNET_OUTPUT_RTHUMBBASE_YROTATION,//302 +MOCAPNET_OUTPUT_RTHUMB_ZROTATION,//303 +MOCAPNET_OUTPUT_RTHUMB_XROTATION,//304 +MOCAPNET_OUTPUT_RTHUMB_YROTATION,//305 +MOCAPNET_OUTPUT_FINGER1_2_R_ZROTATION,//306 +MOCAPNET_OUTPUT_FINGER1_2_R_XROTATION,//307 +MOCAPNET_OUTPUT_FINGER1_2_R_YROTATION,//308 +MOCAPNET_OUTPUT_FINGER1_3_R_ZROTATION,//309 +MOCAPNET_OUTPUT_FINGER1_3_R_XROTATION,//310 +MOCAPNET_OUTPUT_FINGER1_3_R_YROTATION,//311 +MOCAPNET_OUTPUT_LCOLLAR_ZROTATION,//312 +MOCAPNET_OUTPUT_LCOLLAR_XROTATION,//313 +MOCAPNET_OUTPUT_LCOLLAR_YROTATION,//314 +MOCAPNET_OUTPUT_LSHOULDER_ZROTATION,//315 +MOCAPNET_OUTPUT_LSHOULDER_XROTATION,//316 +MOCAPNET_OUTPUT_LSHOULDER_YROTATION,//317 +MOCAPNET_OUTPUT_LELBOW_ZROTATION,//318 +MOCAPNET_OUTPUT_LELBOW_XROTATION,//319 +MOCAPNET_OUTPUT_LELBOW_YROTATION,//320 +MOCAPNET_OUTPUT_LHAND_ZROTATION,//321 +MOCAPNET_OUTPUT_LHAND_XROTATION,//322 +MOCAPNET_OUTPUT_LHAND_YROTATION,//323 +MOCAPNET_OUTPUT_METACARPAL1_L_ZROTATION,//324 +MOCAPNET_OUTPUT_METACARPAL1_L_XROTATION,//325 +MOCAPNET_OUTPUT_METACARPAL1_L_YROTATION,//326 +MOCAPNET_OUTPUT_FINGER2_1_L_ZROTATION,//327 +MOCAPNET_OUTPUT_FINGER2_1_L_XROTATION,//328 +MOCAPNET_OUTPUT_FINGER2_1_L_YROTATION,//329 +MOCAPNET_OUTPUT_FINGER2_2_L_ZROTATION,//330 +MOCAPNET_OUTPUT_FINGER2_2_L_XROTATION,//331 +MOCAPNET_OUTPUT_FINGER2_2_L_YROTATION,//332 +MOCAPNET_OUTPUT_FINGER2_3_L_ZROTATION,//333 +MOCAPNET_OUTPUT_FINGER2_3_L_XROTATION,//334 +MOCAPNET_OUTPUT_FINGER2_3_L_YROTATION,//335 +MOCAPNET_OUTPUT_METACARPAL2_L_ZROTATION,//336 +MOCAPNET_OUTPUT_METACARPAL2_L_XROTATION,//337 +MOCAPNET_OUTPUT_METACARPAL2_L_YROTATION,//338 +MOCAPNET_OUTPUT_FINGER3_1_L_ZROTATION,//339 +MOCAPNET_OUTPUT_FINGER3_1_L_XROTATION,//340 +MOCAPNET_OUTPUT_FINGER3_1_L_YROTATION,//341 +MOCAPNET_OUTPUT_FINGER3_2_L_ZROTATION,//342 +MOCAPNET_OUTPUT_FINGER3_2_L_XROTATION,//343 +MOCAPNET_OUTPUT_FINGER3_2_L_YROTATION,//344 +MOCAPNET_OUTPUT_FINGER3_3_L_ZROTATION,//345 +MOCAPNET_OUTPUT_FINGER3_3_L_XROTATION,//346 +MOCAPNET_OUTPUT_FINGER3_3_L_YROTATION,//347 +MOCAPNET_OUTPUT___METACARPAL3_L_ZROTATION,//348 +MOCAPNET_OUTPUT___METACARPAL3_L_XROTATION,//349 +MOCAPNET_OUTPUT___METACARPAL3_L_YROTATION,//350 +MOCAPNET_OUTPUT_METACARPAL3_L_ZROTATION,//351 +MOCAPNET_OUTPUT_METACARPAL3_L_XROTATION,//352 +MOCAPNET_OUTPUT_METACARPAL3_L_YROTATION,//353 +MOCAPNET_OUTPUT_FINGER4_1_L_ZROTATION,//354 +MOCAPNET_OUTPUT_FINGER4_1_L_XROTATION,//355 +MOCAPNET_OUTPUT_FINGER4_1_L_YROTATION,//356 +MOCAPNET_OUTPUT_FINGER4_2_L_ZROTATION,//357 +MOCAPNET_OUTPUT_FINGER4_2_L_XROTATION,//358 +MOCAPNET_OUTPUT_FINGER4_2_L_YROTATION,//359 +MOCAPNET_OUTPUT_FINGER4_3_L_ZROTATION,//360 +MOCAPNET_OUTPUT_FINGER4_3_L_XROTATION,//361 +MOCAPNET_OUTPUT_FINGER4_3_L_YROTATION,//362 +MOCAPNET_OUTPUT___METACARPAL4_L_ZROTATION,//363 +MOCAPNET_OUTPUT___METACARPAL4_L_XROTATION,//364 +MOCAPNET_OUTPUT___METACARPAL4_L_YROTATION,//365 +MOCAPNET_OUTPUT_METACARPAL4_L_ZROTATION,//366 +MOCAPNET_OUTPUT_METACARPAL4_L_XROTATION,//367 +MOCAPNET_OUTPUT_METACARPAL4_L_YROTATION,//368 +MOCAPNET_OUTPUT_FINGER5_1_L_ZROTATION,//369 +MOCAPNET_OUTPUT_FINGER5_1_L_XROTATION,//370 +MOCAPNET_OUTPUT_FINGER5_1_L_YROTATION,//371 +MOCAPNET_OUTPUT_FINGER5_2_L_ZROTATION,//372 +MOCAPNET_OUTPUT_FINGER5_2_L_XROTATION,//373 +MOCAPNET_OUTPUT_FINGER5_2_L_YROTATION,//374 +MOCAPNET_OUTPUT_FINGER5_3_L_ZROTATION,//375 +MOCAPNET_OUTPUT_FINGER5_3_L_XROTATION,//376 +MOCAPNET_OUTPUT_FINGER5_3_L_YROTATION,//377 +MOCAPNET_OUTPUT_LTHUMBBASE_ZROTATION,//378 +MOCAPNET_OUTPUT_LTHUMBBASE_XROTATION,//379 +MOCAPNET_OUTPUT_LTHUMBBASE_YROTATION,//380 +MOCAPNET_OUTPUT_LTHUMB_ZROTATION,//381 +MOCAPNET_OUTPUT_LTHUMB_XROTATION,//382 +MOCAPNET_OUTPUT_LTHUMB_YROTATION,//383 +MOCAPNET_OUTPUT_FINGER1_2_L_ZROTATION,//384 +MOCAPNET_OUTPUT_FINGER1_2_L_XROTATION,//385 +MOCAPNET_OUTPUT_FINGER1_2_L_YROTATION,//386 +MOCAPNET_OUTPUT_FINGER1_3_L_ZROTATION,//387 +MOCAPNET_OUTPUT_FINGER1_3_L_XROTATION,//388 +MOCAPNET_OUTPUT_FINGER1_3_L_YROTATION,//389 +MOCAPNET_OUTPUT_RBUTTOCK_ZROTATION,//390 +MOCAPNET_OUTPUT_RBUTTOCK_XROTATION,//391 +MOCAPNET_OUTPUT_RBUTTOCK_YROTATION,//392 +MOCAPNET_OUTPUT_RHIP_ZROTATION,//393 +MOCAPNET_OUTPUT_RHIP_XROTATION,//394 +MOCAPNET_OUTPUT_RHIP_YROTATION,//395 +MOCAPNET_OUTPUT_RKNEE_ZROTATION,//396 +MOCAPNET_OUTPUT_RKNEE_XROTATION,//397 +MOCAPNET_OUTPUT_RKNEE_YROTATION,//398 +MOCAPNET_OUTPUT_RFOOT_ZROTATION,//399 +MOCAPNET_OUTPUT_RFOOT_XROTATION,//400 +MOCAPNET_OUTPUT_RFOOT_YROTATION,//401 +MOCAPNET_OUTPUT_TOE1_1_R_ZROTATION,//402 +MOCAPNET_OUTPUT_TOE1_1_R_XROTATION,//403 +MOCAPNET_OUTPUT_TOE1_1_R_YROTATION,//404 +MOCAPNET_OUTPUT_TOE1_2_R_ZROTATION,//405 +MOCAPNET_OUTPUT_TOE1_2_R_XROTATION,//406 +MOCAPNET_OUTPUT_TOE1_2_R_YROTATION,//407 +MOCAPNET_OUTPUT_TOE2_1_R_ZROTATION,//408 +MOCAPNET_OUTPUT_TOE2_1_R_XROTATION,//409 +MOCAPNET_OUTPUT_TOE2_1_R_YROTATION,//410 +MOCAPNET_OUTPUT_TOE2_2_R_ZROTATION,//411 +MOCAPNET_OUTPUT_TOE2_2_R_XROTATION,//412 +MOCAPNET_OUTPUT_TOE2_2_R_YROTATION,//413 +MOCAPNET_OUTPUT_TOE2_3_R_ZROTATION,//414 +MOCAPNET_OUTPUT_TOE2_3_R_XROTATION,//415 +MOCAPNET_OUTPUT_TOE2_3_R_YROTATION,//416 +MOCAPNET_OUTPUT_TOE3_1_R_ZROTATION,//417 +MOCAPNET_OUTPUT_TOE3_1_R_XROTATION,//418 +MOCAPNET_OUTPUT_TOE3_1_R_YROTATION,//419 +MOCAPNET_OUTPUT_TOE3_2_R_ZROTATION,//420 +MOCAPNET_OUTPUT_TOE3_2_R_XROTATION,//421 +MOCAPNET_OUTPUT_TOE3_2_R_YROTATION,//422 +MOCAPNET_OUTPUT_TOE3_3_R_ZROTATION,//423 +MOCAPNET_OUTPUT_TOE3_3_R_XROTATION,//424 +MOCAPNET_OUTPUT_TOE3_3_R_YROTATION,//425 +MOCAPNET_OUTPUT_TOE4_1_R_ZROTATION,//426 +MOCAPNET_OUTPUT_TOE4_1_R_XROTATION,//427 +MOCAPNET_OUTPUT_TOE4_1_R_YROTATION,//428 +MOCAPNET_OUTPUT_TOE4_2_R_ZROTATION,//429 +MOCAPNET_OUTPUT_TOE4_2_R_XROTATION,//430 +MOCAPNET_OUTPUT_TOE4_2_R_YROTATION,//431 +MOCAPNET_OUTPUT_TOE4_3_R_ZROTATION,//432 +MOCAPNET_OUTPUT_TOE4_3_R_XROTATION,//433 +MOCAPNET_OUTPUT_TOE4_3_R_YROTATION,//434 +MOCAPNET_OUTPUT_TOE5_1_R_ZROTATION,//435 +MOCAPNET_OUTPUT_TOE5_1_R_XROTATION,//436 +MOCAPNET_OUTPUT_TOE5_1_R_YROTATION,//437 +MOCAPNET_OUTPUT_TOE5_2_R_ZROTATION,//438 +MOCAPNET_OUTPUT_TOE5_2_R_XROTATION,//439 +MOCAPNET_OUTPUT_TOE5_2_R_YROTATION,//440 +MOCAPNET_OUTPUT_TOE5_3_R_ZROTATION,//441 +MOCAPNET_OUTPUT_TOE5_3_R_XROTATION,//442 +MOCAPNET_OUTPUT_TOE5_3_R_YROTATION,//443 +MOCAPNET_OUTPUT_LBUTTOCK_ZROTATION,//444 +MOCAPNET_OUTPUT_LBUTTOCK_XROTATION,//445 +MOCAPNET_OUTPUT_LBUTTOCK_YROTATION,//446 +MOCAPNET_OUTPUT_LHIP_ZROTATION,//447 +MOCAPNET_OUTPUT_LHIP_XROTATION,//448 +MOCAPNET_OUTPUT_LHIP_YROTATION,//449 +MOCAPNET_OUTPUT_LKNEE_ZROTATION,//450 +MOCAPNET_OUTPUT_LKNEE_XROTATION,//451 +MOCAPNET_OUTPUT_LKNEE_YROTATION,//452 +MOCAPNET_OUTPUT_LFOOT_ZROTATION,//453 +MOCAPNET_OUTPUT_LFOOT_XROTATION,//454 +MOCAPNET_OUTPUT_LFOOT_YROTATION,//455 +MOCAPNET_OUTPUT_TOE1_1_L_ZROTATION,//456 +MOCAPNET_OUTPUT_TOE1_1_L_XROTATION,//457 +MOCAPNET_OUTPUT_TOE1_1_L_YROTATION,//458 +MOCAPNET_OUTPUT_TOE1_2_L_ZROTATION,//459 +MOCAPNET_OUTPUT_TOE1_2_L_XROTATION,//460 +MOCAPNET_OUTPUT_TOE1_2_L_YROTATION,//461 +MOCAPNET_OUTPUT_TOE2_1_L_ZROTATION,//462 +MOCAPNET_OUTPUT_TOE2_1_L_XROTATION,//463 +MOCAPNET_OUTPUT_TOE2_1_L_YROTATION,//464 +MOCAPNET_OUTPUT_TOE2_2_L_ZROTATION,//465 +MOCAPNET_OUTPUT_TOE2_2_L_XROTATION,//466 +MOCAPNET_OUTPUT_TOE2_2_L_YROTATION,//467 +MOCAPNET_OUTPUT_TOE2_3_L_ZROTATION,//468 +MOCAPNET_OUTPUT_TOE2_3_L_XROTATION,//469 +MOCAPNET_OUTPUT_TOE2_3_L_YROTATION,//470 +MOCAPNET_OUTPUT_TOE3_1_L_ZROTATION,//471 +MOCAPNET_OUTPUT_TOE3_1_L_XROTATION,//472 +MOCAPNET_OUTPUT_TOE3_1_L_YROTATION,//473 +MOCAPNET_OUTPUT_TOE3_2_L_ZROTATION,//474 +MOCAPNET_OUTPUT_TOE3_2_L_XROTATION,//475 +MOCAPNET_OUTPUT_TOE3_2_L_YROTATION,//476 +MOCAPNET_OUTPUT_TOE3_3_L_ZROTATION,//477 +MOCAPNET_OUTPUT_TOE3_3_L_XROTATION,//478 +MOCAPNET_OUTPUT_TOE3_3_L_YROTATION,//479 +MOCAPNET_OUTPUT_TOE4_1_L_ZROTATION,//480 +MOCAPNET_OUTPUT_TOE4_1_L_XROTATION,//481 +MOCAPNET_OUTPUT_TOE4_1_L_YROTATION,//482 +MOCAPNET_OUTPUT_TOE4_2_L_ZROTATION,//483 +MOCAPNET_OUTPUT_TOE4_2_L_XROTATION,//484 +MOCAPNET_OUTPUT_TOE4_2_L_YROTATION,//485 +MOCAPNET_OUTPUT_TOE4_3_L_ZROTATION,//486 +MOCAPNET_OUTPUT_TOE4_3_L_XROTATION,//487 +MOCAPNET_OUTPUT_TOE4_3_L_YROTATION,//488 +MOCAPNET_OUTPUT_TOE5_1_L_ZROTATION,//489 +MOCAPNET_OUTPUT_TOE5_1_L_XROTATION,//490 +MOCAPNET_OUTPUT_TOE5_1_L_YROTATION,//491 +MOCAPNET_OUTPUT_TOE5_2_L_ZROTATION,//492 +MOCAPNET_OUTPUT_TOE5_2_L_XROTATION,//493 +MOCAPNET_OUTPUT_TOE5_2_L_YROTATION,//494 +MOCAPNET_OUTPUT_TOE5_3_L_ZROTATION,//495 +MOCAPNET_OUTPUT_TOE5_3_L_XROTATION,//496 +MOCAPNET_OUTPUT_TOE5_3_L_YROTATION,//497 +//----------------------------- +MOCAPNET_OUTPUT_NUMBER +}; + + + + + + + + + +/** + * @brief This is a programmer friendly enumerator to access 3D output extracted from MocapNET + * Use ./GroundTruthDumper --from dataset/headerWithHeadAndOneMotion.bvh --printc + * to extract this automatically + */ +enum MNET_3D_Output_Joints +{ +MOCAPNET_3DPOINT_HIPX,//0 +MOCAPNET_3DPOINT_HIPY,//1 +MOCAPNET_3DPOINT_HIPZ,//2 +MOCAPNET_3DPOINT_ABDOMENX,//3 +MOCAPNET_3DPOINT_ABDOMENY,//4 +MOCAPNET_3DPOINT_ABDOMENZ,//5 +MOCAPNET_3DPOINT_CHESTX,//6 +MOCAPNET_3DPOINT_CHESTY,//7 +MOCAPNET_3DPOINT_CHESTZ,//8 +MOCAPNET_3DPOINT_NECKX,//9 +MOCAPNET_3DPOINT_NECKY,//10 +MOCAPNET_3DPOINT_NECKZ,//11 +MOCAPNET_3DPOINT_NECK1X,//12 +MOCAPNET_3DPOINT_NECK1Y,//13 +MOCAPNET_3DPOINT_NECK1Z,//14 +MOCAPNET_3DPOINT_HEADX,//15 +MOCAPNET_3DPOINT_HEADY,//16 +MOCAPNET_3DPOINT_HEADZ,//17 +MOCAPNET_3DPOINT___JAWX,//18 +MOCAPNET_3DPOINT___JAWY,//19 +MOCAPNET_3DPOINT___JAWZ,//20 +MOCAPNET_3DPOINT_JAWX,//21 +MOCAPNET_3DPOINT_JAWY,//22 +MOCAPNET_3DPOINT_JAWZ,//23 +MOCAPNET_3DPOINT_SPECIAL04X,//24 +MOCAPNET_3DPOINT_SPECIAL04Y,//25 +MOCAPNET_3DPOINT_SPECIAL04Z,//26 +MOCAPNET_3DPOINT_ORIS02X,//27 +MOCAPNET_3DPOINT_ORIS02Y,//28 +MOCAPNET_3DPOINT_ORIS02Z,//29 +MOCAPNET_3DPOINT_ORIS01X,//30 +MOCAPNET_3DPOINT_ORIS01Y,//31 +MOCAPNET_3DPOINT_ORIS01Z,//32 +MOCAPNET_3DPOINT_ENDSITE_ORIS01X,//33 +MOCAPNET_3DPOINT_ENDSITE_ORIS01Y,//34 +MOCAPNET_3DPOINT_ENDSITE_ORIS01Z,//35 +MOCAPNET_3DPOINT_ORIS06_LX,//36 +MOCAPNET_3DPOINT_ORIS06_LY,//37 +MOCAPNET_3DPOINT_ORIS06_LZ,//38 +MOCAPNET_3DPOINT_ORIS07_LX,//39 +MOCAPNET_3DPOINT_ORIS07_LY,//40 +MOCAPNET_3DPOINT_ORIS07_LZ,//41 +MOCAPNET_3DPOINT_ENDSITE_ORIS07_LX,//42 +MOCAPNET_3DPOINT_ENDSITE_ORIS07_LY,//43 +MOCAPNET_3DPOINT_ENDSITE_ORIS07_LZ,//44 +MOCAPNET_3DPOINT_ORIS06_RX,//45 +MOCAPNET_3DPOINT_ORIS06_RY,//46 +MOCAPNET_3DPOINT_ORIS06_RZ,//47 +MOCAPNET_3DPOINT_ORIS07_RX,//48 +MOCAPNET_3DPOINT_ORIS07_RY,//49 +MOCAPNET_3DPOINT_ORIS07_RZ,//50 +MOCAPNET_3DPOINT_ENDSITE_ORIS07_RX,//51 +MOCAPNET_3DPOINT_ENDSITE_ORIS07_RY,//52 +MOCAPNET_3DPOINT_ENDSITE_ORIS07_RZ,//53 +MOCAPNET_3DPOINT_TONGUE00X,//54 +MOCAPNET_3DPOINT_TONGUE00Y,//55 +MOCAPNET_3DPOINT_TONGUE00Z,//56 +MOCAPNET_3DPOINT_TONGUE01X,//57 +MOCAPNET_3DPOINT_TONGUE01Y,//58 +MOCAPNET_3DPOINT_TONGUE01Z,//59 +MOCAPNET_3DPOINT_TONGUE02X,//60 +MOCAPNET_3DPOINT_TONGUE02Y,//61 +MOCAPNET_3DPOINT_TONGUE02Z,//62 +MOCAPNET_3DPOINT_TONGUE03X,//63 +MOCAPNET_3DPOINT_TONGUE03Y,//64 +MOCAPNET_3DPOINT_TONGUE03Z,//65 +MOCAPNET_3DPOINT___TONGUE04X,//66 +MOCAPNET_3DPOINT___TONGUE04Y,//67 +MOCAPNET_3DPOINT___TONGUE04Z,//68 +MOCAPNET_3DPOINT_TONGUE04X,//69 +MOCAPNET_3DPOINT_TONGUE04Y,//70 +MOCAPNET_3DPOINT_TONGUE04Z,//71 +MOCAPNET_3DPOINT_ENDSITE_TONGUE04X,//72 +MOCAPNET_3DPOINT_ENDSITE_TONGUE04Y,//73 +MOCAPNET_3DPOINT_ENDSITE_TONGUE04Z,//74 +MOCAPNET_3DPOINT_TONGUE07_LX,//75 +MOCAPNET_3DPOINT_TONGUE07_LY,//76 +MOCAPNET_3DPOINT_TONGUE07_LZ,//77 +MOCAPNET_3DPOINT_ENDSITE_TONGUE07_LX,//78 +MOCAPNET_3DPOINT_ENDSITE_TONGUE07_LY,//79 +MOCAPNET_3DPOINT_ENDSITE_TONGUE07_LZ,//80 +MOCAPNET_3DPOINT_TONGUE07_RX,//81 +MOCAPNET_3DPOINT_TONGUE07_RY,//82 +MOCAPNET_3DPOINT_TONGUE07_RZ,//83 +MOCAPNET_3DPOINT_ENDSITE_TONGUE07_RX,//84 +MOCAPNET_3DPOINT_ENDSITE_TONGUE07_RY,//85 +MOCAPNET_3DPOINT_ENDSITE_TONGUE07_RZ,//86 +MOCAPNET_3DPOINT_TONGUE06_LX,//87 +MOCAPNET_3DPOINT_TONGUE06_LY,//88 +MOCAPNET_3DPOINT_TONGUE06_LZ,//89 +MOCAPNET_3DPOINT_ENDSITE_TONGUE06_LX,//90 +MOCAPNET_3DPOINT_ENDSITE_TONGUE06_LY,//91 +MOCAPNET_3DPOINT_ENDSITE_TONGUE06_LZ,//92 +MOCAPNET_3DPOINT_TONGUE06_RX,//93 +MOCAPNET_3DPOINT_TONGUE06_RY,//94 +MOCAPNET_3DPOINT_TONGUE06_RZ,//95 +MOCAPNET_3DPOINT_ENDSITE_TONGUE06_RX,//96 +MOCAPNET_3DPOINT_ENDSITE_TONGUE06_RY,//97 +MOCAPNET_3DPOINT_ENDSITE_TONGUE06_RZ,//98 +MOCAPNET_3DPOINT_TONGUE05_LX,//99 +MOCAPNET_3DPOINT_TONGUE05_LY,//100 +MOCAPNET_3DPOINT_TONGUE05_LZ,//101 +MOCAPNET_3DPOINT_ENDSITE_TONGUE05_LX,//102 +MOCAPNET_3DPOINT_ENDSITE_TONGUE05_LY,//103 +MOCAPNET_3DPOINT_ENDSITE_TONGUE05_LZ,//104 +MOCAPNET_3DPOINT_TONGUE05_RX,//105 +MOCAPNET_3DPOINT_TONGUE05_RY,//106 +MOCAPNET_3DPOINT_TONGUE05_RZ,//107 +MOCAPNET_3DPOINT_ENDSITE_TONGUE05_RX,//108 +MOCAPNET_3DPOINT_ENDSITE_TONGUE05_RY,//109 +MOCAPNET_3DPOINT_ENDSITE_TONGUE05_RZ,//110 +MOCAPNET_3DPOINT___LEVATOR02_LX,//111 +MOCAPNET_3DPOINT___LEVATOR02_LY,//112 +MOCAPNET_3DPOINT___LEVATOR02_LZ,//113 +MOCAPNET_3DPOINT_LEVATOR02_LX,//114 +MOCAPNET_3DPOINT_LEVATOR02_LY,//115 +MOCAPNET_3DPOINT_LEVATOR02_LZ,//116 +MOCAPNET_3DPOINT_LEVATOR03_LX,//117 +MOCAPNET_3DPOINT_LEVATOR03_LY,//118 +MOCAPNET_3DPOINT_LEVATOR03_LZ,//119 +MOCAPNET_3DPOINT_LEVATOR04_LX,//120 +MOCAPNET_3DPOINT_LEVATOR04_LY,//121 +MOCAPNET_3DPOINT_LEVATOR04_LZ,//122 +MOCAPNET_3DPOINT_LEVATOR05_LX,//123 +MOCAPNET_3DPOINT_LEVATOR05_LY,//124 +MOCAPNET_3DPOINT_LEVATOR05_LZ,//125 +MOCAPNET_3DPOINT_ENDSITE_LEVATOR05_LX,//126 +MOCAPNET_3DPOINT_ENDSITE_LEVATOR05_LY,//127 +MOCAPNET_3DPOINT_ENDSITE_LEVATOR05_LZ,//128 +MOCAPNET_3DPOINT___LEVATOR02_RX,//129 +MOCAPNET_3DPOINT___LEVATOR02_RY,//130 +MOCAPNET_3DPOINT___LEVATOR02_RZ,//131 +MOCAPNET_3DPOINT_LEVATOR02_RX,//132 +MOCAPNET_3DPOINT_LEVATOR02_RY,//133 +MOCAPNET_3DPOINT_LEVATOR02_RZ,//134 +MOCAPNET_3DPOINT_LEVATOR03_RX,//135 +MOCAPNET_3DPOINT_LEVATOR03_RY,//136 +MOCAPNET_3DPOINT_LEVATOR03_RZ,//137 +MOCAPNET_3DPOINT_LEVATOR04_RX,//138 +MOCAPNET_3DPOINT_LEVATOR04_RY,//139 +MOCAPNET_3DPOINT_LEVATOR04_RZ,//140 +MOCAPNET_3DPOINT_LEVATOR05_RX,//141 +MOCAPNET_3DPOINT_LEVATOR05_RY,//142 +MOCAPNET_3DPOINT_LEVATOR05_RZ,//143 +MOCAPNET_3DPOINT_ENDSITE_LEVATOR05_RX,//144 +MOCAPNET_3DPOINT_ENDSITE_LEVATOR05_RY,//145 +MOCAPNET_3DPOINT_ENDSITE_LEVATOR05_RZ,//146 +MOCAPNET_3DPOINT___SPECIAL01X,//147 +MOCAPNET_3DPOINT___SPECIAL01Y,//148 +MOCAPNET_3DPOINT___SPECIAL01Z,//149 +MOCAPNET_3DPOINT_SPECIAL01X,//150 +MOCAPNET_3DPOINT_SPECIAL01Y,//151 +MOCAPNET_3DPOINT_SPECIAL01Z,//152 +MOCAPNET_3DPOINT_ORIS04_LX,//153 +MOCAPNET_3DPOINT_ORIS04_LY,//154 +MOCAPNET_3DPOINT_ORIS04_LZ,//155 +MOCAPNET_3DPOINT_ORIS03_LX,//156 +MOCAPNET_3DPOINT_ORIS03_LY,//157 +MOCAPNET_3DPOINT_ORIS03_LZ,//158 +MOCAPNET_3DPOINT_ENDSITE_ORIS03_LX,//159 +MOCAPNET_3DPOINT_ENDSITE_ORIS03_LY,//160 +MOCAPNET_3DPOINT_ENDSITE_ORIS03_LZ,//161 +MOCAPNET_3DPOINT_ORIS04_RX,//162 +MOCAPNET_3DPOINT_ORIS04_RY,//163 +MOCAPNET_3DPOINT_ORIS04_RZ,//164 +MOCAPNET_3DPOINT_ORIS03_RX,//165 +MOCAPNET_3DPOINT_ORIS03_RY,//166 +MOCAPNET_3DPOINT_ORIS03_RZ,//167 +MOCAPNET_3DPOINT_ENDSITE_ORIS03_RX,//168 +MOCAPNET_3DPOINT_ENDSITE_ORIS03_RY,//169 +MOCAPNET_3DPOINT_ENDSITE_ORIS03_RZ,//170 +MOCAPNET_3DPOINT_ORIS06X,//171 +MOCAPNET_3DPOINT_ORIS06Y,//172 +MOCAPNET_3DPOINT_ORIS06Z,//173 +MOCAPNET_3DPOINT_ORIS05X,//174 +MOCAPNET_3DPOINT_ORIS05Y,//175 +MOCAPNET_3DPOINT_ORIS05Z,//176 +MOCAPNET_3DPOINT_ENDSITE_ORIS05X,//177 +MOCAPNET_3DPOINT_ENDSITE_ORIS05Y,//178 +MOCAPNET_3DPOINT_ENDSITE_ORIS05Z,//179 +MOCAPNET_3DPOINT___SPECIAL03X,//180 +MOCAPNET_3DPOINT___SPECIAL03Y,//181 +MOCAPNET_3DPOINT___SPECIAL03Z,//182 +MOCAPNET_3DPOINT_SPECIAL03X,//183 +MOCAPNET_3DPOINT_SPECIAL03Y,//184 +MOCAPNET_3DPOINT_SPECIAL03Z,//185 +MOCAPNET_3DPOINT___LEVATOR06_LX,//186 +MOCAPNET_3DPOINT___LEVATOR06_LY,//187 +MOCAPNET_3DPOINT___LEVATOR06_LZ,//188 +MOCAPNET_3DPOINT_LEVATOR06_LX,//189 +MOCAPNET_3DPOINT_LEVATOR06_LY,//190 +MOCAPNET_3DPOINT_LEVATOR06_LZ,//191 +MOCAPNET_3DPOINT_ENDSITE_LEVATOR06_LX,//192 +MOCAPNET_3DPOINT_ENDSITE_LEVATOR06_LY,//193 +MOCAPNET_3DPOINT_ENDSITE_LEVATOR06_LZ,//194 +MOCAPNET_3DPOINT___LEVATOR06_RX,//195 +MOCAPNET_3DPOINT___LEVATOR06_RY,//196 +MOCAPNET_3DPOINT___LEVATOR06_RZ,//197 +MOCAPNET_3DPOINT_LEVATOR06_RX,//198 +MOCAPNET_3DPOINT_LEVATOR06_RY,//199 +MOCAPNET_3DPOINT_LEVATOR06_RZ,//200 +MOCAPNET_3DPOINT_ENDSITE_LEVATOR06_RX,//201 +MOCAPNET_3DPOINT_ENDSITE_LEVATOR06_RY,//202 +MOCAPNET_3DPOINT_ENDSITE_LEVATOR06_RZ,//203 +MOCAPNET_3DPOINT_SPECIAL06_LX,//204 +MOCAPNET_3DPOINT_SPECIAL06_LY,//205 +MOCAPNET_3DPOINT_SPECIAL06_LZ,//206 +MOCAPNET_3DPOINT_SPECIAL05_LX,//207 +MOCAPNET_3DPOINT_SPECIAL05_LY,//208 +MOCAPNET_3DPOINT_SPECIAL05_LZ,//209 +MOCAPNET_3DPOINT_EYE_LX,//210 +MOCAPNET_3DPOINT_EYE_LY,//211 +MOCAPNET_3DPOINT_EYE_LZ,//212 +MOCAPNET_3DPOINT_ENDSITE_EYE_LX,//213 +MOCAPNET_3DPOINT_ENDSITE_EYE_LY,//214 +MOCAPNET_3DPOINT_ENDSITE_EYE_LZ,//215 +MOCAPNET_3DPOINT_ORBICULARIS03_LX,//216 +MOCAPNET_3DPOINT_ORBICULARIS03_LY,//217 +MOCAPNET_3DPOINT_ORBICULARIS03_LZ,//218 +MOCAPNET_3DPOINT_ENDSITE_ORBICULARIS03_LX,//219 +MOCAPNET_3DPOINT_ENDSITE_ORBICULARIS03_LY,//220 +MOCAPNET_3DPOINT_ENDSITE_ORBICULARIS03_LZ,//221 +MOCAPNET_3DPOINT_ORBICULARIS04_LX,//222 +MOCAPNET_3DPOINT_ORBICULARIS04_LY,//223 +MOCAPNET_3DPOINT_ORBICULARIS04_LZ,//224 +MOCAPNET_3DPOINT_ENDSITE_ORBICULARIS04_LX,//225 +MOCAPNET_3DPOINT_ENDSITE_ORBICULARIS04_LY,//226 +MOCAPNET_3DPOINT_ENDSITE_ORBICULARIS04_LZ,//227 +MOCAPNET_3DPOINT_SPECIAL06_RX,//228 +MOCAPNET_3DPOINT_SPECIAL06_RY,//229 +MOCAPNET_3DPOINT_SPECIAL06_RZ,//230 +MOCAPNET_3DPOINT_SPECIAL05_RX,//231 +MOCAPNET_3DPOINT_SPECIAL05_RY,//232 +MOCAPNET_3DPOINT_SPECIAL05_RZ,//233 +MOCAPNET_3DPOINT_EYE_RX,//234 +MOCAPNET_3DPOINT_EYE_RY,//235 +MOCAPNET_3DPOINT_EYE_RZ,//236 +MOCAPNET_3DPOINT_ENDSITE_EYE_RX,//237 +MOCAPNET_3DPOINT_ENDSITE_EYE_RY,//238 +MOCAPNET_3DPOINT_ENDSITE_EYE_RZ,//239 +MOCAPNET_3DPOINT_ORBICULARIS03_RX,//240 +MOCAPNET_3DPOINT_ORBICULARIS03_RY,//241 +MOCAPNET_3DPOINT_ORBICULARIS03_RZ,//242 +MOCAPNET_3DPOINT_ENDSITE_ORBICULARIS03_RX,//243 +MOCAPNET_3DPOINT_ENDSITE_ORBICULARIS03_RY,//244 +MOCAPNET_3DPOINT_ENDSITE_ORBICULARIS03_RZ,//245 +MOCAPNET_3DPOINT_ORBICULARIS04_RX,//246 +MOCAPNET_3DPOINT_ORBICULARIS04_RY,//247 +MOCAPNET_3DPOINT_ORBICULARIS04_RZ,//248 +MOCAPNET_3DPOINT_ENDSITE_ORBICULARIS04_RX,//249 +MOCAPNET_3DPOINT_ENDSITE_ORBICULARIS04_RY,//250 +MOCAPNET_3DPOINT_ENDSITE_ORBICULARIS04_RZ,//251 +MOCAPNET_3DPOINT___TEMPORALIS01_LX,//252 +MOCAPNET_3DPOINT___TEMPORALIS01_LY,//253 +MOCAPNET_3DPOINT___TEMPORALIS01_LZ,//254 +MOCAPNET_3DPOINT_TEMPORALIS01_LX,//255 +MOCAPNET_3DPOINT_TEMPORALIS01_LY,//256 +MOCAPNET_3DPOINT_TEMPORALIS01_LZ,//257 +MOCAPNET_3DPOINT_OCULI02_LX,//258 +MOCAPNET_3DPOINT_OCULI02_LY,//259 +MOCAPNET_3DPOINT_OCULI02_LZ,//260 +MOCAPNET_3DPOINT_OCULI01_LX,//261 +MOCAPNET_3DPOINT_OCULI01_LY,//262 +MOCAPNET_3DPOINT_OCULI01_LZ,//263 +MOCAPNET_3DPOINT_ENDSITE_OCULI01_LX,//264 +MOCAPNET_3DPOINT_ENDSITE_OCULI01_LY,//265 +MOCAPNET_3DPOINT_ENDSITE_OCULI01_LZ,//266 +MOCAPNET_3DPOINT___TEMPORALIS01_RX,//267 +MOCAPNET_3DPOINT___TEMPORALIS01_RY,//268 +MOCAPNET_3DPOINT___TEMPORALIS01_RZ,//269 +MOCAPNET_3DPOINT_TEMPORALIS01_RX,//270 +MOCAPNET_3DPOINT_TEMPORALIS01_RY,//271 +MOCAPNET_3DPOINT_TEMPORALIS01_RZ,//272 +MOCAPNET_3DPOINT_OCULI02_RX,//273 +MOCAPNET_3DPOINT_OCULI02_RY,//274 +MOCAPNET_3DPOINT_OCULI02_RZ,//275 +MOCAPNET_3DPOINT_OCULI01_RX,//276 +MOCAPNET_3DPOINT_OCULI01_RY,//277 +MOCAPNET_3DPOINT_OCULI01_RZ,//278 +MOCAPNET_3DPOINT_ENDSITE_OCULI01_RX,//279 +MOCAPNET_3DPOINT_ENDSITE_OCULI01_RY,//280 +MOCAPNET_3DPOINT_ENDSITE_OCULI01_RZ,//281 +MOCAPNET_3DPOINT___TEMPORALIS02_LX,//282 +MOCAPNET_3DPOINT___TEMPORALIS02_LY,//283 +MOCAPNET_3DPOINT___TEMPORALIS02_LZ,//284 +MOCAPNET_3DPOINT_TEMPORALIS02_LX,//285 +MOCAPNET_3DPOINT_TEMPORALIS02_LY,//286 +MOCAPNET_3DPOINT_TEMPORALIS02_LZ,//287 +MOCAPNET_3DPOINT_RISORIUS02_LX,//288 +MOCAPNET_3DPOINT_RISORIUS02_LY,//289 +MOCAPNET_3DPOINT_RISORIUS02_LZ,//290 +MOCAPNET_3DPOINT_RISORIUS03_LX,//291 +MOCAPNET_3DPOINT_RISORIUS03_LY,//292 +MOCAPNET_3DPOINT_RISORIUS03_LZ,//293 +MOCAPNET_3DPOINT_ENDSITE_RISORIUS03_LX,//294 +MOCAPNET_3DPOINT_ENDSITE_RISORIUS03_LY,//295 +MOCAPNET_3DPOINT_ENDSITE_RISORIUS03_LZ,//296 +MOCAPNET_3DPOINT___TEMPORALIS02_RX,//297 +MOCAPNET_3DPOINT___TEMPORALIS02_RY,//298 +MOCAPNET_3DPOINT___TEMPORALIS02_RZ,//299 +MOCAPNET_3DPOINT_TEMPORALIS02_RX,//300 +MOCAPNET_3DPOINT_TEMPORALIS02_RY,//301 +MOCAPNET_3DPOINT_TEMPORALIS02_RZ,//302 +MOCAPNET_3DPOINT_RISORIUS02_RX,//303 +MOCAPNET_3DPOINT_RISORIUS02_RY,//304 +MOCAPNET_3DPOINT_RISORIUS02_RZ,//305 +MOCAPNET_3DPOINT_RISORIUS03_RX,//306 +MOCAPNET_3DPOINT_RISORIUS03_RY,//307 +MOCAPNET_3DPOINT_RISORIUS03_RZ,//308 +MOCAPNET_3DPOINT_ENDSITE_RISORIUS03_RX,//309 +MOCAPNET_3DPOINT_ENDSITE_RISORIUS03_RY,//310 +MOCAPNET_3DPOINT_ENDSITE_RISORIUS03_RZ,//311 +MOCAPNET_3DPOINT_RCOLLARX,//312 +MOCAPNET_3DPOINT_RCOLLARY,//313 +MOCAPNET_3DPOINT_RCOLLARZ,//314 +MOCAPNET_3DPOINT_RSHOULDERX,//315 +MOCAPNET_3DPOINT_RSHOULDERY,//316 +MOCAPNET_3DPOINT_RSHOULDERZ,//317 +MOCAPNET_3DPOINT_RELBOWX,//318 +MOCAPNET_3DPOINT_RELBOWY,//319 +MOCAPNET_3DPOINT_RELBOWZ,//320 +MOCAPNET_3DPOINT_RHANDX,//321 +MOCAPNET_3DPOINT_RHANDY,//322 +MOCAPNET_3DPOINT_RHANDZ,//323 +MOCAPNET_3DPOINT_METACARPAL1_RX,//324 +MOCAPNET_3DPOINT_METACARPAL1_RY,//325 +MOCAPNET_3DPOINT_METACARPAL1_RZ,//326 +MOCAPNET_3DPOINT_FINGER2_1_RX,//327 +MOCAPNET_3DPOINT_FINGER2_1_RY,//328 +MOCAPNET_3DPOINT_FINGER2_1_RZ,//329 +MOCAPNET_3DPOINT_FINGER2_2_RX,//330 +MOCAPNET_3DPOINT_FINGER2_2_RY,//331 +MOCAPNET_3DPOINT_FINGER2_2_RZ,//332 +MOCAPNET_3DPOINT_FINGER2_3_RX,//333 +MOCAPNET_3DPOINT_FINGER2_3_RY,//334 +MOCAPNET_3DPOINT_FINGER2_3_RZ,//335 +MOCAPNET_3DPOINT_ENDSITE_FINGER2_3_RX,//336 +MOCAPNET_3DPOINT_ENDSITE_FINGER2_3_RY,//337 +MOCAPNET_3DPOINT_ENDSITE_FINGER2_3_RZ,//338 +MOCAPNET_3DPOINT_METACARPAL2_RX,//339 +MOCAPNET_3DPOINT_METACARPAL2_RY,//340 +MOCAPNET_3DPOINT_METACARPAL2_RZ,//341 +MOCAPNET_3DPOINT_FINGER3_1_RX,//342 +MOCAPNET_3DPOINT_FINGER3_1_RY,//343 +MOCAPNET_3DPOINT_FINGER3_1_RZ,//344 +MOCAPNET_3DPOINT_FINGER3_2_RX,//345 +MOCAPNET_3DPOINT_FINGER3_2_RY,//346 +MOCAPNET_3DPOINT_FINGER3_2_RZ,//347 +MOCAPNET_3DPOINT_FINGER3_3_RX,//348 +MOCAPNET_3DPOINT_FINGER3_3_RY,//349 +MOCAPNET_3DPOINT_FINGER3_3_RZ,//350 +MOCAPNET_3DPOINT_ENDSITE_FINGER3_3_RX,//351 +MOCAPNET_3DPOINT_ENDSITE_FINGER3_3_RY,//352 +MOCAPNET_3DPOINT_ENDSITE_FINGER3_3_RZ,//353 +MOCAPNET_3DPOINT___METACARPAL3_RX,//354 +MOCAPNET_3DPOINT___METACARPAL3_RY,//355 +MOCAPNET_3DPOINT___METACARPAL3_RZ,//356 +MOCAPNET_3DPOINT_METACARPAL3_RX,//357 +MOCAPNET_3DPOINT_METACARPAL3_RY,//358 +MOCAPNET_3DPOINT_METACARPAL3_RZ,//359 +MOCAPNET_3DPOINT_FINGER4_1_RX,//360 +MOCAPNET_3DPOINT_FINGER4_1_RY,//361 +MOCAPNET_3DPOINT_FINGER4_1_RZ,//362 +MOCAPNET_3DPOINT_FINGER4_2_RX,//363 +MOCAPNET_3DPOINT_FINGER4_2_RY,//364 +MOCAPNET_3DPOINT_FINGER4_2_RZ,//365 +MOCAPNET_3DPOINT_FINGER4_3_RX,//366 +MOCAPNET_3DPOINT_FINGER4_3_RY,//367 +MOCAPNET_3DPOINT_FINGER4_3_RZ,//368 +MOCAPNET_3DPOINT_ENDSITE_FINGER4_3_RX,//369 +MOCAPNET_3DPOINT_ENDSITE_FINGER4_3_RY,//370 +MOCAPNET_3DPOINT_ENDSITE_FINGER4_3_RZ,//371 +MOCAPNET_3DPOINT___METACARPAL4_RX,//372 +MOCAPNET_3DPOINT___METACARPAL4_RY,//373 +MOCAPNET_3DPOINT___METACARPAL4_RZ,//374 +MOCAPNET_3DPOINT_METACARPAL4_RX,//375 +MOCAPNET_3DPOINT_METACARPAL4_RY,//376 +MOCAPNET_3DPOINT_METACARPAL4_RZ,//377 +MOCAPNET_3DPOINT_FINGER5_1_RX,//378 +MOCAPNET_3DPOINT_FINGER5_1_RY,//379 +MOCAPNET_3DPOINT_FINGER5_1_RZ,//380 +MOCAPNET_3DPOINT_FINGER5_2_RX,//381 +MOCAPNET_3DPOINT_FINGER5_2_RY,//382 +MOCAPNET_3DPOINT_FINGER5_2_RZ,//383 +MOCAPNET_3DPOINT_FINGER5_3_RX,//384 +MOCAPNET_3DPOINT_FINGER5_3_RY,//385 +MOCAPNET_3DPOINT_FINGER5_3_RZ,//386 +MOCAPNET_3DPOINT_ENDSITE_FINGER5_3_RX,//387 +MOCAPNET_3DPOINT_ENDSITE_FINGER5_3_RY,//388 +MOCAPNET_3DPOINT_ENDSITE_FINGER5_3_RZ,//389 +MOCAPNET_3DPOINT_RTHUMBBASEX,//390 +MOCAPNET_3DPOINT_RTHUMBBASEY,//391 +MOCAPNET_3DPOINT_RTHUMBBASEZ,//392 +MOCAPNET_3DPOINT_RTHUMBX,//393 +MOCAPNET_3DPOINT_RTHUMBY,//394 +MOCAPNET_3DPOINT_RTHUMBZ,//395 +MOCAPNET_3DPOINT_FINGER1_2_RX,//396 +MOCAPNET_3DPOINT_FINGER1_2_RY,//397 +MOCAPNET_3DPOINT_FINGER1_2_RZ,//398 +MOCAPNET_3DPOINT_FINGER1_3_RX,//399 +MOCAPNET_3DPOINT_FINGER1_3_RY,//400 +MOCAPNET_3DPOINT_FINGER1_3_RZ,//401 +MOCAPNET_3DPOINT_ENDSITE_FINGER1_3_RX,//402 +MOCAPNET_3DPOINT_ENDSITE_FINGER1_3_RY,//403 +MOCAPNET_3DPOINT_ENDSITE_FINGER1_3_RZ,//404 +MOCAPNET_3DPOINT_LCOLLARX,//405 +MOCAPNET_3DPOINT_LCOLLARY,//406 +MOCAPNET_3DPOINT_LCOLLARZ,//407 +MOCAPNET_3DPOINT_LSHOULDERX,//408 +MOCAPNET_3DPOINT_LSHOULDERY,//409 +MOCAPNET_3DPOINT_LSHOULDERZ,//410 +MOCAPNET_3DPOINT_LELBOWX,//411 +MOCAPNET_3DPOINT_LELBOWY,//412 +MOCAPNET_3DPOINT_LELBOWZ,//413 +MOCAPNET_3DPOINT_LHANDX,//414 +MOCAPNET_3DPOINT_LHANDY,//415 +MOCAPNET_3DPOINT_LHANDZ,//416 +MOCAPNET_3DPOINT_METACARPAL1_LX,//417 +MOCAPNET_3DPOINT_METACARPAL1_LY,//418 +MOCAPNET_3DPOINT_METACARPAL1_LZ,//419 +MOCAPNET_3DPOINT_FINGER2_1_LX,//420 +MOCAPNET_3DPOINT_FINGER2_1_LY,//421 +MOCAPNET_3DPOINT_FINGER2_1_LZ,//422 +MOCAPNET_3DPOINT_FINGER2_2_LX,//423 +MOCAPNET_3DPOINT_FINGER2_2_LY,//424 +MOCAPNET_3DPOINT_FINGER2_2_LZ,//425 +MOCAPNET_3DPOINT_FINGER2_3_LX,//426 +MOCAPNET_3DPOINT_FINGER2_3_LY,//427 +MOCAPNET_3DPOINT_FINGER2_3_LZ,//428 +MOCAPNET_3DPOINT_ENDSITE_FINGER2_3_LX,//429 +MOCAPNET_3DPOINT_ENDSITE_FINGER2_3_LY,//430 +MOCAPNET_3DPOINT_ENDSITE_FINGER2_3_LZ,//431 +MOCAPNET_3DPOINT_METACARPAL2_LX,//432 +MOCAPNET_3DPOINT_METACARPAL2_LY,//433 +MOCAPNET_3DPOINT_METACARPAL2_LZ,//434 +MOCAPNET_3DPOINT_FINGER3_1_LX,//435 +MOCAPNET_3DPOINT_FINGER3_1_LY,//436 +MOCAPNET_3DPOINT_FINGER3_1_LZ,//437 +MOCAPNET_3DPOINT_FINGER3_2_LX,//438 +MOCAPNET_3DPOINT_FINGER3_2_LY,//439 +MOCAPNET_3DPOINT_FINGER3_2_LZ,//440 +MOCAPNET_3DPOINT_FINGER3_3_LX,//441 +MOCAPNET_3DPOINT_FINGER3_3_LY,//442 +MOCAPNET_3DPOINT_FINGER3_3_LZ,//443 +MOCAPNET_3DPOINT_ENDSITE_FINGER3_3_LX,//444 +MOCAPNET_3DPOINT_ENDSITE_FINGER3_3_LY,//445 +MOCAPNET_3DPOINT_ENDSITE_FINGER3_3_LZ,//446 +MOCAPNET_3DPOINT___METACARPAL3_LX,//447 +MOCAPNET_3DPOINT___METACARPAL3_LY,//448 +MOCAPNET_3DPOINT___METACARPAL3_LZ,//449 +MOCAPNET_3DPOINT_METACARPAL3_LX,//450 +MOCAPNET_3DPOINT_METACARPAL3_LY,//451 +MOCAPNET_3DPOINT_METACARPAL3_LZ,//452 +MOCAPNET_3DPOINT_FINGER4_1_LX,//453 +MOCAPNET_3DPOINT_FINGER4_1_LY,//454 +MOCAPNET_3DPOINT_FINGER4_1_LZ,//455 +MOCAPNET_3DPOINT_FINGER4_2_LX,//456 +MOCAPNET_3DPOINT_FINGER4_2_LY,//457 +MOCAPNET_3DPOINT_FINGER4_2_LZ,//458 +MOCAPNET_3DPOINT_FINGER4_3_LX,//459 +MOCAPNET_3DPOINT_FINGER4_3_LY,//460 +MOCAPNET_3DPOINT_FINGER4_3_LZ,//461 +MOCAPNET_3DPOINT_ENDSITE_FINGER4_3_LX,//462 +MOCAPNET_3DPOINT_ENDSITE_FINGER4_3_LY,//463 +MOCAPNET_3DPOINT_ENDSITE_FINGER4_3_LZ,//464 +MOCAPNET_3DPOINT___METACARPAL4_LX,//465 +MOCAPNET_3DPOINT___METACARPAL4_LY,//466 +MOCAPNET_3DPOINT___METACARPAL4_LZ,//467 +MOCAPNET_3DPOINT_METACARPAL4_LX,//468 +MOCAPNET_3DPOINT_METACARPAL4_LY,//469 +MOCAPNET_3DPOINT_METACARPAL4_LZ,//470 +MOCAPNET_3DPOINT_FINGER5_1_LX,//471 +MOCAPNET_3DPOINT_FINGER5_1_LY,//472 +MOCAPNET_3DPOINT_FINGER5_1_LZ,//473 +MOCAPNET_3DPOINT_FINGER5_2_LX,//474 +MOCAPNET_3DPOINT_FINGER5_2_LY,//475 +MOCAPNET_3DPOINT_FINGER5_2_LZ,//476 +MOCAPNET_3DPOINT_FINGER5_3_LX,//477 +MOCAPNET_3DPOINT_FINGER5_3_LY,//478 +MOCAPNET_3DPOINT_FINGER5_3_LZ,//479 +MOCAPNET_3DPOINT_ENDSITE_FINGER5_3_LX,//480 +MOCAPNET_3DPOINT_ENDSITE_FINGER5_3_LY,//481 +MOCAPNET_3DPOINT_ENDSITE_FINGER5_3_LZ,//482 +MOCAPNET_3DPOINT_LTHUMBBASEX,//483 +MOCAPNET_3DPOINT_LTHUMBBASEY,//484 +MOCAPNET_3DPOINT_LTHUMBBASEZ,//485 +MOCAPNET_3DPOINT_LTHUMBX,//486 +MOCAPNET_3DPOINT_LTHUMBY,//487 +MOCAPNET_3DPOINT_LTHUMBZ,//488 +MOCAPNET_3DPOINT_FINGER1_2_LX,//489 +MOCAPNET_3DPOINT_FINGER1_2_LY,//490 +MOCAPNET_3DPOINT_FINGER1_2_LZ,//491 +MOCAPNET_3DPOINT_FINGER1_3_LX,//492 +MOCAPNET_3DPOINT_FINGER1_3_LY,//493 +MOCAPNET_3DPOINT_FINGER1_3_LZ,//494 +MOCAPNET_3DPOINT_ENDSITE_FINGER1_3_LX,//495 +MOCAPNET_3DPOINT_ENDSITE_FINGER1_3_LY,//496 +MOCAPNET_3DPOINT_ENDSITE_FINGER1_3_LZ,//497 +MOCAPNET_3DPOINT_RBUTTOCKX,//498 +MOCAPNET_3DPOINT_RBUTTOCKY,//499 +MOCAPNET_3DPOINT_RBUTTOCKZ,//500 +MOCAPNET_3DPOINT_RHIPX,//501 +MOCAPNET_3DPOINT_RHIPY,//502 +MOCAPNET_3DPOINT_RHIPZ,//503 +MOCAPNET_3DPOINT_RKNEEX,//504 +MOCAPNET_3DPOINT_RKNEEY,//505 +MOCAPNET_3DPOINT_RKNEEZ,//506 +MOCAPNET_3DPOINT_RFOOTX,//507 +MOCAPNET_3DPOINT_RFOOTY,//508 +MOCAPNET_3DPOINT_RFOOTZ,//509 +MOCAPNET_3DPOINT_TOE1_1_RX,//510 +MOCAPNET_3DPOINT_TOE1_1_RY,//511 +MOCAPNET_3DPOINT_TOE1_1_RZ,//512 +MOCAPNET_3DPOINT_TOE1_2_RX,//513 +MOCAPNET_3DPOINT_TOE1_2_RY,//514 +MOCAPNET_3DPOINT_TOE1_2_RZ,//515 +MOCAPNET_3DPOINT_ENDSITE_TOE1_2_RX,//516 +MOCAPNET_3DPOINT_ENDSITE_TOE1_2_RY,//517 +MOCAPNET_3DPOINT_ENDSITE_TOE1_2_RZ,//518 +MOCAPNET_3DPOINT_TOE2_1_RX,//519 +MOCAPNET_3DPOINT_TOE2_1_RY,//520 +MOCAPNET_3DPOINT_TOE2_1_RZ,//521 +MOCAPNET_3DPOINT_TOE2_2_RX,//522 +MOCAPNET_3DPOINT_TOE2_2_RY,//523 +MOCAPNET_3DPOINT_TOE2_2_RZ,//524 +MOCAPNET_3DPOINT_TOE2_3_RX,//525 +MOCAPNET_3DPOINT_TOE2_3_RY,//526 +MOCAPNET_3DPOINT_TOE2_3_RZ,//527 +MOCAPNET_3DPOINT_ENDSITE_TOE2_3_RX,//528 +MOCAPNET_3DPOINT_ENDSITE_TOE2_3_RY,//529 +MOCAPNET_3DPOINT_ENDSITE_TOE2_3_RZ,//530 +MOCAPNET_3DPOINT_TOE3_1_RX,//531 +MOCAPNET_3DPOINT_TOE3_1_RY,//532 +MOCAPNET_3DPOINT_TOE3_1_RZ,//533 +MOCAPNET_3DPOINT_TOE3_2_RX,//534 +MOCAPNET_3DPOINT_TOE3_2_RY,//535 +MOCAPNET_3DPOINT_TOE3_2_RZ,//536 +MOCAPNET_3DPOINT_TOE3_3_RX,//537 +MOCAPNET_3DPOINT_TOE3_3_RY,//538 +MOCAPNET_3DPOINT_TOE3_3_RZ,//539 +MOCAPNET_3DPOINT_ENDSITE_TOE3_3_RX,//540 +MOCAPNET_3DPOINT_ENDSITE_TOE3_3_RY,//541 +MOCAPNET_3DPOINT_ENDSITE_TOE3_3_RZ,//542 +MOCAPNET_3DPOINT_TOE4_1_RX,//543 +MOCAPNET_3DPOINT_TOE4_1_RY,//544 +MOCAPNET_3DPOINT_TOE4_1_RZ,//545 +MOCAPNET_3DPOINT_TOE4_2_RX,//546 +MOCAPNET_3DPOINT_TOE4_2_RY,//547 +MOCAPNET_3DPOINT_TOE4_2_RZ,//548 +MOCAPNET_3DPOINT_TOE4_3_RX,//549 +MOCAPNET_3DPOINT_TOE4_3_RY,//550 +MOCAPNET_3DPOINT_TOE4_3_RZ,//551 +MOCAPNET_3DPOINT_ENDSITE_TOE4_3_RX,//552 +MOCAPNET_3DPOINT_ENDSITE_TOE4_3_RY,//553 +MOCAPNET_3DPOINT_ENDSITE_TOE4_3_RZ,//554 +MOCAPNET_3DPOINT_TOE5_1_RX,//555 +MOCAPNET_3DPOINT_TOE5_1_RY,//556 +MOCAPNET_3DPOINT_TOE5_1_RZ,//557 +MOCAPNET_3DPOINT_TOE5_2_RX,//558 +MOCAPNET_3DPOINT_TOE5_2_RY,//559 +MOCAPNET_3DPOINT_TOE5_2_RZ,//560 +MOCAPNET_3DPOINT_TOE5_3_RX,//561 +MOCAPNET_3DPOINT_TOE5_3_RY,//562 +MOCAPNET_3DPOINT_TOE5_3_RZ,//563 +MOCAPNET_3DPOINT_ENDSITE_TOE5_3_RX,//564 +MOCAPNET_3DPOINT_ENDSITE_TOE5_3_RY,//565 +MOCAPNET_3DPOINT_ENDSITE_TOE5_3_RZ,//566 +MOCAPNET_3DPOINT_LBUTTOCKX,//567 +MOCAPNET_3DPOINT_LBUTTOCKY,//568 +MOCAPNET_3DPOINT_LBUTTOCKZ,//569 +MOCAPNET_3DPOINT_LHIPX,//570 +MOCAPNET_3DPOINT_LHIPY,//571 +MOCAPNET_3DPOINT_LHIPZ,//572 +MOCAPNET_3DPOINT_LKNEEX,//573 +MOCAPNET_3DPOINT_LKNEEY,//574 +MOCAPNET_3DPOINT_LKNEEZ,//575 +MOCAPNET_3DPOINT_LFOOTX,//576 +MOCAPNET_3DPOINT_LFOOTY,//577 +MOCAPNET_3DPOINT_LFOOTZ,//578 +MOCAPNET_3DPOINT_TOE1_1_LX,//579 +MOCAPNET_3DPOINT_TOE1_1_LY,//580 +MOCAPNET_3DPOINT_TOE1_1_LZ,//581 +MOCAPNET_3DPOINT_TOE1_2_LX,//582 +MOCAPNET_3DPOINT_TOE1_2_LY,//583 +MOCAPNET_3DPOINT_TOE1_2_LZ,//584 +MOCAPNET_3DPOINT_ENDSITE_TOE1_2_LX,//585 +MOCAPNET_3DPOINT_ENDSITE_TOE1_2_LY,//586 +MOCAPNET_3DPOINT_ENDSITE_TOE1_2_LZ,//587 +MOCAPNET_3DPOINT_TOE2_1_LX,//588 +MOCAPNET_3DPOINT_TOE2_1_LY,//589 +MOCAPNET_3DPOINT_TOE2_1_LZ,//590 +MOCAPNET_3DPOINT_TOE2_2_LX,//591 +MOCAPNET_3DPOINT_TOE2_2_LY,//592 +MOCAPNET_3DPOINT_TOE2_2_LZ,//593 +MOCAPNET_3DPOINT_TOE2_3_LX,//594 +MOCAPNET_3DPOINT_TOE2_3_LY,//595 +MOCAPNET_3DPOINT_TOE2_3_LZ,//596 +MOCAPNET_3DPOINT_ENDSITE_TOE2_3_LX,//597 +MOCAPNET_3DPOINT_ENDSITE_TOE2_3_LY,//598 +MOCAPNET_3DPOINT_ENDSITE_TOE2_3_LZ,//599 +MOCAPNET_3DPOINT_TOE3_1_LX,//600 +MOCAPNET_3DPOINT_TOE3_1_LY,//601 +MOCAPNET_3DPOINT_TOE3_1_LZ,//602 +MOCAPNET_3DPOINT_TOE3_2_LX,//603 +MOCAPNET_3DPOINT_TOE3_2_LY,//604 +MOCAPNET_3DPOINT_TOE3_2_LZ,//605 +MOCAPNET_3DPOINT_TOE3_3_LX,//606 +MOCAPNET_3DPOINT_TOE3_3_LY,//607 +MOCAPNET_3DPOINT_TOE3_3_LZ,//608 +MOCAPNET_3DPOINT_ENDSITE_TOE3_3_LX,//609 +MOCAPNET_3DPOINT_ENDSITE_TOE3_3_LY,//610 +MOCAPNET_3DPOINT_ENDSITE_TOE3_3_LZ,//611 +MOCAPNET_3DPOINT_TOE4_1_LX,//612 +MOCAPNET_3DPOINT_TOE4_1_LY,//613 +MOCAPNET_3DPOINT_TOE4_1_LZ,//614 +MOCAPNET_3DPOINT_TOE4_2_LX,//615 +MOCAPNET_3DPOINT_TOE4_2_LY,//616 +MOCAPNET_3DPOINT_TOE4_2_LZ,//617 +MOCAPNET_3DPOINT_TOE4_3_LX,//618 +MOCAPNET_3DPOINT_TOE4_3_LY,//619 +MOCAPNET_3DPOINT_TOE4_3_LZ,//620 +MOCAPNET_3DPOINT_ENDSITE_TOE4_3_LX,//621 +MOCAPNET_3DPOINT_ENDSITE_TOE4_3_LY,//622 +MOCAPNET_3DPOINT_ENDSITE_TOE4_3_LZ,//623 +MOCAPNET_3DPOINT_TOE5_1_LX,//624 +MOCAPNET_3DPOINT_TOE5_1_LY,//625 +MOCAPNET_3DPOINT_TOE5_1_LZ,//626 +MOCAPNET_3DPOINT_TOE5_2_LX,//627 +MOCAPNET_3DPOINT_TOE5_2_LY,//628 +MOCAPNET_3DPOINT_TOE5_2_LZ,//629 +MOCAPNET_3DPOINT_TOE5_3_LX,//630 +MOCAPNET_3DPOINT_TOE5_3_LY,//631 +MOCAPNET_3DPOINT_TOE5_3_LZ,//632 +MOCAPNET_3DPOINT_ENDSITE_TOE5_3_LX,//633 +MOCAPNET_3DPOINT_ENDSITE_TOE5_3_LY,//634 +MOCAPNET_3DPOINT_ENDSITE_TOE5_3_LZ,//635 +//------------------------------------------------------------------- +MOCAPNET_3DPOINT_NUMBER +}; + + + + +/** + * @brief An array with BVH string labels + */ +static const char * MocapNET3DPositionalOutputArrayNames[] = +{ +"hip_Xposition", // 0 +"hip_Yposition", // 1 +"hip_Zposition", // 2 +"abdomen_Xposition", // 3 +"abdomen_Yposition", // 4 +"abdomen_Zposition", // 5 +"chest_Xposition", // 6 +"chest_Yposition", // 7 +"chest_Zposition", // 8 +"neck_Xposition", // 9 +"neck_Yposition", // 10 +"neck_Zposition", // 11 +"neck1_Xposition", // 12 +"neck1_Yposition", // 13 +"neck1_Zposition", // 14 +"head_Xposition", // 15 +"head_Yposition", // 16 +"head_Zposition", // 17 +"__jaw_Xposition", // 18 +"__jaw_Yposition", // 19 +"__jaw_Zposition", // 20 +"jaw_Xposition", // 21 +"jaw_Yposition", // 22 +"jaw_Zposition", // 23 +"special04_Xposition", // 24 +"special04_Yposition", // 25 +"special04_Zposition", // 26 +"oris02_Xposition", // 27 +"oris02_Yposition", // 28 +"oris02_Zposition", // 29 +"oris01_Xposition", // 30 +"oris01_Yposition", // 31 +"oris01_Zposition", // 32 +"endsite_oris01_Xposition", // 33 +"endsite_oris01_Yposition", // 34 +"endsite_oris01_Zposition", // 35 +"oris06.l_Xposition", // 36 +"oris06.l_Yposition", // 37 +"oris06.l_Zposition", // 38 +"oris07.l_Xposition", // 39 +"oris07.l_Yposition", // 40 +"oris07.l_Zposition", // 41 +"endsite_oris07.l_Xposition", // 42 +"endsite_oris07.l_Yposition", // 43 +"endsite_oris07.l_Zposition", // 44 +"oris06.r_Xposition", // 45 +"oris06.r_Yposition", // 46 +"oris06.r_Zposition", // 47 +"oris07.r_Xposition", // 48 +"oris07.r_Yposition", // 49 +"oris07.r_Zposition", // 50 +"endsite_oris07.r_Xposition", // 51 +"endsite_oris07.r_Yposition", // 52 +"endsite_oris07.r_Zposition", // 53 +"tongue00_Xposition", // 54 +"tongue00_Yposition", // 55 +"tongue00_Zposition", // 56 +"tongue01_Xposition", // 57 +"tongue01_Yposition", // 58 +"tongue01_Zposition", // 59 +"tongue02_Xposition", // 60 +"tongue02_Yposition", // 61 +"tongue02_Zposition", // 62 +"tongue03_Xposition", // 63 +"tongue03_Yposition", // 64 +"tongue03_Zposition", // 65 +"__tongue04_Xposition", // 66 +"__tongue04_Yposition", // 67 +"__tongue04_Zposition", // 68 +"tongue04_Xposition", // 69 +"tongue04_Yposition", // 70 +"tongue04_Zposition", // 71 +"endsite_tongue04_Xposition", // 72 +"endsite_tongue04_Yposition", // 73 +"endsite_tongue04_Zposition", // 74 +"tongue07.l_Xposition", // 75 +"tongue07.l_Yposition", // 76 +"tongue07.l_Zposition", // 77 +"endsite_tongue07.l_Xposition", // 78 +"endsite_tongue07.l_Yposition", // 79 +"endsite_tongue07.l_Zposition", // 80 +"tongue07.r_Xposition", // 81 +"tongue07.r_Yposition", // 82 +"tongue07.r_Zposition", // 83 +"endsite_tongue07.r_Xposition", // 84 +"endsite_tongue07.r_Yposition", // 85 +"endsite_tongue07.r_Zposition", // 86 +"tongue06.l_Xposition", // 87 +"tongue06.l_Yposition", // 88 +"tongue06.l_Zposition", // 89 +"endsite_tongue06.l_Xposition", // 90 +"endsite_tongue06.l_Yposition", // 91 +"endsite_tongue06.l_Zposition", // 92 +"tongue06.r_Xposition", // 93 +"tongue06.r_Yposition", // 94 +"tongue06.r_Zposition", // 95 +"endsite_tongue06.r_Xposition", // 96 +"endsite_tongue06.r_Yposition", // 97 +"endsite_tongue06.r_Zposition", // 98 +"tongue05.l_Xposition", // 99 +"tongue05.l_Yposition", // 100 +"tongue05.l_Zposition", // 101 +"endsite_tongue05.l_Xposition", // 102 +"endsite_tongue05.l_Yposition", // 103 +"endsite_tongue05.l_Zposition", // 104 +"tongue05.r_Xposition", // 105 +"tongue05.r_Yposition", // 106 +"tongue05.r_Zposition", // 107 +"endsite_tongue05.r_Xposition", // 108 +"endsite_tongue05.r_Yposition", // 109 +"endsite_tongue05.r_Zposition", // 110 +"__levator02.l_Xposition", // 111 +"__levator02.l_Yposition", // 112 +"__levator02.l_Zposition", // 113 +"levator02.l_Xposition", // 114 +"levator02.l_Yposition", // 115 +"levator02.l_Zposition", // 116 +"levator03.l_Xposition", // 117 +"levator03.l_Yposition", // 118 +"levator03.l_Zposition", // 119 +"levator04.l_Xposition", // 120 +"levator04.l_Yposition", // 121 +"levator04.l_Zposition", // 122 +"levator05.l_Xposition", // 123 +"levator05.l_Yposition", // 124 +"levator05.l_Zposition", // 125 +"endsite_levator05.l_Xposition", // 126 +"endsite_levator05.l_Yposition", // 127 +"endsite_levator05.l_Zposition", // 128 +"__levator02.r_Xposition", // 129 +"__levator02.r_Yposition", // 130 +"__levator02.r_Zposition", // 131 +"levator02.r_Xposition", // 132 +"levator02.r_Yposition", // 133 +"levator02.r_Zposition", // 134 +"levator03.r_Xposition", // 135 +"levator03.r_Yposition", // 136 +"levator03.r_Zposition", // 137 +"levator04.r_Xposition", // 138 +"levator04.r_Yposition", // 139 +"levator04.r_Zposition", // 140 +"levator05.r_Xposition", // 141 +"levator05.r_Yposition", // 142 +"levator05.r_Zposition", // 143 +"endsite_levator05.r_Xposition", // 144 +"endsite_levator05.r_Yposition", // 145 +"endsite_levator05.r_Zposition", // 146 +"__special01_Xposition", // 147 +"__special01_Yposition", // 148 +"__special01_Zposition", // 149 +"special01_Xposition", // 150 +"special01_Yposition", // 151 +"special01_Zposition", // 152 +"oris04.l_Xposition", // 153 +"oris04.l_Yposition", // 154 +"oris04.l_Zposition", // 155 +"oris03.l_Xposition", // 156 +"oris03.l_Yposition", // 157 +"oris03.l_Zposition", // 158 +"endsite_oris03.l_Xposition", // 159 +"endsite_oris03.l_Yposition", // 160 +"endsite_oris03.l_Zposition", // 161 +"oris04.r_Xposition", // 162 +"oris04.r_Yposition", // 163 +"oris04.r_Zposition", // 164 +"oris03.r_Xposition", // 165 +"oris03.r_Yposition", // 166 +"oris03.r_Zposition", // 167 +"endsite_oris03.r_Xposition", // 168 +"endsite_oris03.r_Yposition", // 169 +"endsite_oris03.r_Zposition", // 170 +"oris06_Xposition", // 171 +"oris06_Yposition", // 172 +"oris06_Zposition", // 173 +"oris05_Xposition", // 174 +"oris05_Yposition", // 175 +"oris05_Zposition", // 176 +"endsite_oris05_Xposition", // 177 +"endsite_oris05_Yposition", // 178 +"endsite_oris05_Zposition", // 179 +"__special03_Xposition", // 180 +"__special03_Yposition", // 181 +"__special03_Zposition", // 182 +"special03_Xposition", // 183 +"special03_Yposition", // 184 +"special03_Zposition", // 185 +"__levator06.l_Xposition", // 186 +"__levator06.l_Yposition", // 187 +"__levator06.l_Zposition", // 188 +"levator06.l_Xposition", // 189 +"levator06.l_Yposition", // 190 +"levator06.l_Zposition", // 191 +"endsite_levator06.l_Xposition", // 192 +"endsite_levator06.l_Yposition", // 193 +"endsite_levator06.l_Zposition", // 194 +"__levator06.r_Xposition", // 195 +"__levator06.r_Yposition", // 196 +"__levator06.r_Zposition", // 197 +"levator06.r_Xposition", // 198 +"levator06.r_Yposition", // 199 +"levator06.r_Zposition", // 200 +"endsite_levator06.r_Xposition", // 201 +"endsite_levator06.r_Yposition", // 202 +"endsite_levator06.r_Zposition", // 203 +"special06.l_Xposition", // 204 +"special06.l_Yposition", // 205 +"special06.l_Zposition", // 206 +"special05.l_Xposition", // 207 +"special05.l_Yposition", // 208 +"special05.l_Zposition", // 209 +"eye.l_Xposition", // 210 +"eye.l_Yposition", // 211 +"eye.l_Zposition", // 212 +"endsite_eye.l_Xposition", // 213 +"endsite_eye.l_Yposition", // 214 +"endsite_eye.l_Zposition", // 215 +"orbicularis03.l_Xposition", // 216 +"orbicularis03.l_Yposition", // 217 +"orbicularis03.l_Zposition", // 218 +"endsite_orbicularis03.l_Xposition", // 219 +"endsite_orbicularis03.l_Yposition", // 220 +"endsite_orbicularis03.l_Zposition", // 221 +"orbicularis04.l_Xposition", // 222 +"orbicularis04.l_Yposition", // 223 +"orbicularis04.l_Zposition", // 224 +"endsite_orbicularis04.l_Xposition", // 225 +"endsite_orbicularis04.l_Yposition", // 226 +"endsite_orbicularis04.l_Zposition", // 227 +"special06.r_Xposition", // 228 +"special06.r_Yposition", // 229 +"special06.r_Zposition", // 230 +"special05.r_Xposition", // 231 +"special05.r_Yposition", // 232 +"special05.r_Zposition", // 233 +"eye.r_Xposition", // 234 +"eye.r_Yposition", // 235 +"eye.r_Zposition", // 236 +"endsite_eye.r_Xposition", // 237 +"endsite_eye.r_Yposition", // 238 +"endsite_eye.r_Zposition", // 239 +"orbicularis03.r_Xposition", // 240 +"orbicularis03.r_Yposition", // 241 +"orbicularis03.r_Zposition", // 242 +"endsite_orbicularis03.r_Xposition", // 243 +"endsite_orbicularis03.r_Yposition", // 244 +"endsite_orbicularis03.r_Zposition", // 245 +"orbicularis04.r_Xposition", // 246 +"orbicularis04.r_Yposition", // 247 +"orbicularis04.r_Zposition", // 248 +"endsite_orbicularis04.r_Xposition", // 249 +"endsite_orbicularis04.r_Yposition", // 250 +"endsite_orbicularis04.r_Zposition", // 251 +"__temporalis01.l_Xposition", // 252 +"__temporalis01.l_Yposition", // 253 +"__temporalis01.l_Zposition", // 254 +"temporalis01.l_Xposition", // 255 +"temporalis01.l_Yposition", // 256 +"temporalis01.l_Zposition", // 257 +"oculi02.l_Xposition", // 258 +"oculi02.l_Yposition", // 259 +"oculi02.l_Zposition", // 260 +"oculi01.l_Xposition", // 261 +"oculi01.l_Yposition", // 262 +"oculi01.l_Zposition", // 263 +"endsite_oculi01.l_Xposition", // 264 +"endsite_oculi01.l_Yposition", // 265 +"endsite_oculi01.l_Zposition", // 266 +"__temporalis01.r_Xposition", // 267 +"__temporalis01.r_Yposition", // 268 +"__temporalis01.r_Zposition", // 269 +"temporalis01.r_Xposition", // 270 +"temporalis01.r_Yposition", // 271 +"temporalis01.r_Zposition", // 272 +"oculi02.r_Xposition", // 273 +"oculi02.r_Yposition", // 274 +"oculi02.r_Zposition", // 275 +"oculi01.r_Xposition", // 276 +"oculi01.r_Yposition", // 277 +"oculi01.r_Zposition", // 278 +"endsite_oculi01.r_Xposition", // 279 +"endsite_oculi01.r_Yposition", // 280 +"endsite_oculi01.r_Zposition", // 281 +"__temporalis02.l_Xposition", // 282 +"__temporalis02.l_Yposition", // 283 +"__temporalis02.l_Zposition", // 284 +"temporalis02.l_Xposition", // 285 +"temporalis02.l_Yposition", // 286 +"temporalis02.l_Zposition", // 287 +"risorius02.l_Xposition", // 288 +"risorius02.l_Yposition", // 289 +"risorius02.l_Zposition", // 290 +"risorius03.l_Xposition", // 291 +"risorius03.l_Yposition", // 292 +"risorius03.l_Zposition", // 293 +"endsite_risorius03.l_Xposition", // 294 +"endsite_risorius03.l_Yposition", // 295 +"endsite_risorius03.l_Zposition", // 296 +"__temporalis02.r_Xposition", // 297 +"__temporalis02.r_Yposition", // 298 +"__temporalis02.r_Zposition", // 299 +"temporalis02.r_Xposition", // 300 +"temporalis02.r_Yposition", // 301 +"temporalis02.r_Zposition", // 302 +"risorius02.r_Xposition", // 303 +"risorius02.r_Yposition", // 304 +"risorius02.r_Zposition", // 305 +"risorius03.r_Xposition", // 306 +"risorius03.r_Yposition", // 307 +"risorius03.r_Zposition", // 308 +"endsite_risorius03.r_Xposition", // 309 +"endsite_risorius03.r_Yposition", // 310 +"endsite_risorius03.r_Zposition", // 311 +"rcollar_Xposition", // 312 +"rcollar_Yposition", // 313 +"rcollar_Zposition", // 314 +"rshoulder_Xposition", // 315 +"rshoulder_Yposition", // 316 +"rshoulder_Zposition", // 317 +"relbow_Xposition", // 318 +"relbow_Yposition", // 319 +"relbow_Zposition", // 320 +"rhand_Xposition", // 321 +"rhand_Yposition", // 322 +"rhand_Zposition", // 323 +"metacarpal1.r_Xposition", // 324 +"metacarpal1.r_Yposition", // 325 +"metacarpal1.r_Zposition", // 326 +"finger2-1.r_Xposition", // 327 +"finger2-1.r_Yposition", // 328 +"finger2-1.r_Zposition", // 329 +"finger2-2.r_Xposition", // 330 +"finger2-2.r_Yposition", // 331 +"finger2-2.r_Zposition", // 332 +"finger2-3.r_Xposition", // 333 +"finger2-3.r_Yposition", // 334 +"finger2-3.r_Zposition", // 335 +"endsite_finger2-3.r_Xposition", // 336 +"endsite_finger2-3.r_Yposition", // 337 +"endsite_finger2-3.r_Zposition", // 338 +"metacarpal2.r_Xposition", // 339 +"metacarpal2.r_Yposition", // 340 +"metacarpal2.r_Zposition", // 341 +"finger3-1.r_Xposition", // 342 +"finger3-1.r_Yposition", // 343 +"finger3-1.r_Zposition", // 344 +"finger3-2.r_Xposition", // 345 +"finger3-2.r_Yposition", // 346 +"finger3-2.r_Zposition", // 347 +"finger3-3.r_Xposition", // 348 +"finger3-3.r_Yposition", // 349 +"finger3-3.r_Zposition", // 350 +"endsite_finger3-3.r_Xposition", // 351 +"endsite_finger3-3.r_Yposition", // 352 +"endsite_finger3-3.r_Zposition", // 353 +"__metacarpal3.r_Xposition", // 354 +"__metacarpal3.r_Yposition", // 355 +"__metacarpal3.r_Zposition", // 356 +"metacarpal3.r_Xposition", // 357 +"metacarpal3.r_Yposition", // 358 +"metacarpal3.r_Zposition", // 359 +"finger4-1.r_Xposition", // 360 +"finger4-1.r_Yposition", // 361 +"finger4-1.r_Zposition", // 362 +"finger4-2.r_Xposition", // 363 +"finger4-2.r_Yposition", // 364 +"finger4-2.r_Zposition", // 365 +"finger4-3.r_Xposition", // 366 +"finger4-3.r_Yposition", // 367 +"finger4-3.r_Zposition", // 368 +"endsite_finger4-3.r_Xposition", // 369 +"endsite_finger4-3.r_Yposition", // 370 +"endsite_finger4-3.r_Zposition", // 371 +"__metacarpal4.r_Xposition", // 372 +"__metacarpal4.r_Yposition", // 373 +"__metacarpal4.r_Zposition", // 374 +"metacarpal4.r_Xposition", // 375 +"metacarpal4.r_Yposition", // 376 +"metacarpal4.r_Zposition", // 377 +"finger5-1.r_Xposition", // 378 +"finger5-1.r_Yposition", // 379 +"finger5-1.r_Zposition", // 380 +"finger5-2.r_Xposition", // 381 +"finger5-2.r_Yposition", // 382 +"finger5-2.r_Zposition", // 383 +"finger5-3.r_Xposition", // 384 +"finger5-3.r_Yposition", // 385 +"finger5-3.r_Zposition", // 386 +"endsite_finger5-3.r_Xposition", // 387 +"endsite_finger5-3.r_Yposition", // 388 +"endsite_finger5-3.r_Zposition", // 389 +"rthumbBase_Xposition", // 390 +"rthumbBase_Yposition", // 391 +"rthumbBase_Zposition", // 392 +"rthumb_Xposition", // 393 +"rthumb_Yposition", // 394 +"rthumb_Zposition", // 395 +"finger1-2.r_Xposition", // 396 +"finger1-2.r_Yposition", // 397 +"finger1-2.r_Zposition", // 398 +"finger1-3.r_Xposition", // 399 +"finger1-3.r_Yposition", // 400 +"finger1-3.r_Zposition", // 401 +"endsite_finger1-3.r_Xposition", // 402 +"endsite_finger1-3.r_Yposition", // 403 +"endsite_finger1-3.r_Zposition", // 404 +"lcollar_Xposition", // 405 +"lcollar_Yposition", // 406 +"lcollar_Zposition", // 407 +"lshoulder_Xposition", // 408 +"lshoulder_Yposition", // 409 +"lshoulder_Zposition", // 410 +"lelbow_Xposition", // 411 +"lelbow_Yposition", // 412 +"lelbow_Zposition", // 413 +"lhand_Xposition", // 414 +"lhand_Yposition", // 415 +"lhand_Zposition", // 416 +"metacarpal1.l_Xposition", // 417 +"metacarpal1.l_Yposition", // 418 +"metacarpal1.l_Zposition", // 419 +"finger2-1.l_Xposition", // 420 +"finger2-1.l_Yposition", // 421 +"finger2-1.l_Zposition", // 422 +"finger2-2.l_Xposition", // 423 +"finger2-2.l_Yposition", // 424 +"finger2-2.l_Zposition", // 425 +"finger2-3.l_Xposition", // 426 +"finger2-3.l_Yposition", // 427 +"finger2-3.l_Zposition", // 428 +"endsite_finger2-3.l_Xposition", // 429 +"endsite_finger2-3.l_Yposition", // 430 +"endsite_finger2-3.l_Zposition", // 431 +"metacarpal2.l_Xposition", // 432 +"metacarpal2.l_Yposition", // 433 +"metacarpal2.l_Zposition", // 434 +"finger3-1.l_Xposition", // 435 +"finger3-1.l_Yposition", // 436 +"finger3-1.l_Zposition", // 437 +"finger3-2.l_Xposition", // 438 +"finger3-2.l_Yposition", // 439 +"finger3-2.l_Zposition", // 440 +"finger3-3.l_Xposition", // 441 +"finger3-3.l_Yposition", // 442 +"finger3-3.l_Zposition", // 443 +"endsite_finger3-3.l_Xposition", // 444 +"endsite_finger3-3.l_Yposition", // 445 +"endsite_finger3-3.l_Zposition", // 446 +"__metacarpal3.l_Xposition", // 447 +"__metacarpal3.l_Yposition", // 448 +"__metacarpal3.l_Zposition", // 449 +"metacarpal3.l_Xposition", // 450 +"metacarpal3.l_Yposition", // 451 +"metacarpal3.l_Zposition", // 452 +"finger4-1.l_Xposition", // 453 +"finger4-1.l_Yposition", // 454 +"finger4-1.l_Zposition", // 455 +"finger4-2.l_Xposition", // 456 +"finger4-2.l_Yposition", // 457 +"finger4-2.l_Zposition", // 458 +"finger4-3.l_Xposition", // 459 +"finger4-3.l_Yposition", // 460 +"finger4-3.l_Zposition", // 461 +"endsite_finger4-3.l_Xposition", // 462 +"endsite_finger4-3.l_Yposition", // 463 +"endsite_finger4-3.l_Zposition", // 464 +"__metacarpal4.l_Xposition", // 465 +"__metacarpal4.l_Yposition", // 466 +"__metacarpal4.l_Zposition", // 467 +"metacarpal4.l_Xposition", // 468 +"metacarpal4.l_Yposition", // 469 +"metacarpal4.l_Zposition", // 470 +"finger5-1.l_Xposition", // 471 +"finger5-1.l_Yposition", // 472 +"finger5-1.l_Zposition", // 473 +"finger5-2.l_Xposition", // 474 +"finger5-2.l_Yposition", // 475 +"finger5-2.l_Zposition", // 476 +"finger5-3.l_Xposition", // 477 +"finger5-3.l_Yposition", // 478 +"finger5-3.l_Zposition", // 479 +"endsite_finger5-3.l_Xposition", // 480 +"endsite_finger5-3.l_Yposition", // 481 +"endsite_finger5-3.l_Zposition", // 482 +"lthumbBase_Xposition", // 483 +"lthumbBase_Yposition", // 484 +"lthumbBase_Zposition", // 485 +"lthumb_Xposition", // 486 +"lthumb_Yposition", // 487 +"lthumb_Zposition", // 488 +"finger1-2.l_Xposition", // 489 +"finger1-2.l_Yposition", // 490 +"finger1-2.l_Zposition", // 491 +"finger1-3.l_Xposition", // 492 +"finger1-3.l_Yposition", // 493 +"finger1-3.l_Zposition", // 494 +"endsite_finger1-3.l_Xposition", // 495 +"endsite_finger1-3.l_Yposition", // 496 +"endsite_finger1-3.l_Zposition", // 497 +"rbuttock_Xposition", // 498 +"rbuttock_Yposition", // 499 +"rbuttock_Zposition", // 500 +"rhip_Xposition", // 501 +"rhip_Yposition", // 502 +"rhip_Zposition", // 503 +"rknee_Xposition", // 504 +"rknee_Yposition", // 505 +"rknee_Zposition", // 506 +"rfoot_Xposition", // 507 +"rfoot_Yposition", // 508 +"rfoot_Zposition", // 509 +"toe1-1.r_Xposition", // 510 +"toe1-1.r_Yposition", // 511 +"toe1-1.r_Zposition", // 512 +"toe1-2.r_Xposition", // 513 +"toe1-2.r_Yposition", // 514 +"toe1-2.r_Zposition", // 515 +"endsite_toe1-2.r_Xposition", // 516 +"endsite_toe1-2.r_Yposition", // 517 +"endsite_toe1-2.r_Zposition", // 518 +"toe2-1.r_Xposition", // 519 +"toe2-1.r_Yposition", // 520 +"toe2-1.r_Zposition", // 521 +"toe2-2.r_Xposition", // 522 +"toe2-2.r_Yposition", // 523 +"toe2-2.r_Zposition", // 524 +"toe2-3.r_Xposition", // 525 +"toe2-3.r_Yposition", // 526 +"toe2-3.r_Zposition", // 527 +"endsite_toe2-3.r_Xposition", // 528 +"endsite_toe2-3.r_Yposition", // 529 +"endsite_toe2-3.r_Zposition", // 530 +"toe3-1.r_Xposition", // 531 +"toe3-1.r_Yposition", // 532 +"toe3-1.r_Zposition", // 533 +"toe3-2.r_Xposition", // 534 +"toe3-2.r_Yposition", // 535 +"toe3-2.r_Zposition", // 536 +"toe3-3.r_Xposition", // 537 +"toe3-3.r_Yposition", // 538 +"toe3-3.r_Zposition", // 539 +"endsite_toe3-3.r_Xposition", // 540 +"endsite_toe3-3.r_Yposition", // 541 +"endsite_toe3-3.r_Zposition", // 542 +"toe4-1.r_Xposition", // 543 +"toe4-1.r_Yposition", // 544 +"toe4-1.r_Zposition", // 545 +"toe4-2.r_Xposition", // 546 +"toe4-2.r_Yposition", // 547 +"toe4-2.r_Zposition", // 548 +"toe4-3.r_Xposition", // 549 +"toe4-3.r_Yposition", // 550 +"toe4-3.r_Zposition", // 551 +"endsite_toe4-3.r_Xposition", // 552 +"endsite_toe4-3.r_Yposition", // 553 +"endsite_toe4-3.r_Zposition", // 554 +"toe5-1.r_Xposition", // 555 +"toe5-1.r_Yposition", // 556 +"toe5-1.r_Zposition", // 557 +"toe5-2.r_Xposition", // 558 +"toe5-2.r_Yposition", // 559 +"toe5-2.r_Zposition", // 560 +"toe5-3.r_Xposition", // 561 +"toe5-3.r_Yposition", // 562 +"toe5-3.r_Zposition", // 563 +"endsite_toe5-3.r_Xposition", // 564 +"endsite_toe5-3.r_Yposition", // 565 +"endsite_toe5-3.r_Zposition", // 566 +"lbuttock_Xposition", // 567 +"lbuttock_Yposition", // 568 +"lbuttock_Zposition", // 569 +"lhip_Xposition", // 570 +"lhip_Yposition", // 571 +"lhip_Zposition", // 572 +"lknee_Xposition", // 573 +"lknee_Yposition", // 574 +"lknee_Zposition", // 575 +"lfoot_Xposition", // 576 +"lfoot_Yposition", // 577 +"lfoot_Zposition", // 578 +"toe1-1.l_Xposition", // 579 +"toe1-1.l_Yposition", // 580 +"toe1-1.l_Zposition", // 581 +"toe1-2.l_Xposition", // 582 +"toe1-2.l_Yposition", // 583 +"toe1-2.l_Zposition", // 584 +"endsite_toe1-2.l_Xposition", // 585 +"endsite_toe1-2.l_Yposition", // 586 +"endsite_toe1-2.l_Zposition", // 587 +"toe2-1.l_Xposition", // 588 +"toe2-1.l_Yposition", // 589 +"toe2-1.l_Zposition", // 590 +"toe2-2.l_Xposition", // 591 +"toe2-2.l_Yposition", // 592 +"toe2-2.l_Zposition", // 593 +"toe2-3.l_Xposition", // 594 +"toe2-3.l_Yposition", // 595 +"toe2-3.l_Zposition", // 596 +"endsite_toe2-3.l_Xposition", // 597 +"endsite_toe2-3.l_Yposition", // 598 +"endsite_toe2-3.l_Zposition", // 599 +"toe3-1.l_Xposition", // 600 +"toe3-1.l_Yposition", // 601 +"toe3-1.l_Zposition", // 602 +"toe3-2.l_Xposition", // 603 +"toe3-2.l_Yposition", // 604 +"toe3-2.l_Zposition", // 605 +"toe3-3.l_Xposition", // 606 +"toe3-3.l_Yposition", // 607 +"toe3-3.l_Zposition", // 608 +"endsite_toe3-3.l_Xposition", // 609 +"endsite_toe3-3.l_Yposition", // 610 +"endsite_toe3-3.l_Zposition", // 611 +"toe4-1.l_Xposition", // 612 +"toe4-1.l_Yposition", // 613 +"toe4-1.l_Zposition", // 614 +"toe4-2.l_Xposition", // 615 +"toe4-2.l_Yposition", // 616 +"toe4-2.l_Zposition", // 617 +"toe4-3.l_Xposition", // 618 +"toe4-3.l_Yposition", // 619 +"toe4-3.l_Zposition", // 620 +"endsite_toe4-3.l_Xposition", // 621 +"endsite_toe4-3.l_Yposition", // 622 +"endsite_toe4-3.l_Zposition", // 623 +"toe5-1.l_Xposition", // 624 +"toe5-1.l_Yposition", // 625 +"toe5-1.l_Zposition", // 626 +"toe5-2.l_Xposition", // 627 +"toe5-2.l_Yposition", // 628 +"toe5-2.l_Zposition", // 629 +"toe5-3.l_Xposition", // 630 +"toe5-3.l_Yposition", // 631 +"toe5-3.l_Zposition", // 632 +"endsite_toe5-3.l_Xposition", // 633 +"endsite_toe5-3.l_Yposition", // 634 +"endsite_toe5-3.l_Zposition"// 635 +}; + +/** + * @brief This is a structure to encode model limits, not currently used + */ +struct MocapNETModelLimits +{ + int numberOfLimits; + float minimumYaw1; + float maximumYaw1; + float minimumYaw2; + float maximumYaw2; + int isFlipped; +}; + +/** + * @brief This is a MocapNET orientation. + */ +enum MOCAPNET_Orientation +{ + MOCAPNET_ORIENTATION_NONE=0, + MOCAPNET_ORIENTATION_FRONT, + MOCAPNET_ORIENTATION_BACK, + MOCAPNET_ORIENTATION_LEFT, + MOCAPNET_ORIENTATION_RIGHT, + //----------------------------- + MOCAPNET_ORIENTATION_NUMBER +}; + +/** + * @brief This is an array of names for all uncompressed inputs expected from MocapNET. + * Please notice that these 171 values correspond to triplets of 57 x,y,v ( v for visibility ) information for each joint. + */ +static const char * MocapNETOrientationNames[] = +{ + "None", + "Front", + "Back", + "Left", + "Right" +}; + + +/** + * @brief Each part of our 3D pose output is solved by a dedicated ensemble, this structure organizes this data + */ +struct MocapNET2SolutionPart +{ + int test; +}; + + +#if USE_BVH + #include "../../../dependencies/RGBDAcquisition/tools/PThreadWorkerPool/pthreadWorkerPool.h" +#endif + + +/** + * @brief MocapNET consists of separate classes/ensembles that are invoked for particular orientations. + * This structure holds the required tensorflow instances to make MocapNET work. + */ +struct MocapNET4 +{ + int test; +}; + +/** + * @brief Load a MocapNET from .pb files on disk + * @ingroup mocapnet + * @param Pointer to a struct MocapNET that will hold the tensorflow instances on load. + * @param Description of instance + * @retval 1 = Success loading the files , 0 = Failure + */ +int loadMocapNET4( + struct MocapNET4 * mnet, + const char * description + ); + + +/** + * @brief run MocapNET on an input vector that has the correct formatting. If getting data from an external source + * the prepareMocapNETInputFromUncompressedInput function could be used to prepare the input for this function. + * @param Pointer to a valid and populated MocapNET instance + * @param Vector of input values according to MocapNETUncompressedAndCompressedArrayNames + * @retval 1=Success,0=Failure + */ +std::vector runMocapNET4( + struct MocapNET4 * mnet, + struct skeletonSerialized * input, + int doLowerbody, + int doHands, + int doFace, + int doGestureDetection, + unsigned int useInverseKinematics, + int doOutputFiltering + ); + +/** + * @brief Deallocate tensorflow instances and free memory + * @param Pointer to a valid and populated MocapNET instance + * @retval 1=Success,0=Failure + */ +int unloadMocapNET4(struct MocapNET4 * mnet); + diff --git a/src/MocapNET4/MocapNETLib4/tools.h b/src/MocapNET4/MocapNETLib4/tools.h new file mode 100644 index 0000000..68be579 --- /dev/null +++ b/src/MocapNET4/MocapNETLib4/tools.h @@ -0,0 +1,86 @@ +/** @file tools.h + * @brief Various Tools! + * @author Ammar Qammaz (AmmarkoV) + */ + +#ifndef MNET4_TOOLS_H_INCLUDED +#define MNET4_TOOLS_H_INCLUDED + + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define NORMAL "\033[0m" +#define BLACK "\033[30m" /* Black */ +#define RED "\033[31m" /* Red */ +#define GREEN "\033[32m" /* Green */ +#define YELLOW "\033[33m" /* Yellow */ + + +#include +#include + + +static char * readFileToMemory(const char * filename,unsigned int *length) +{ + if (filename==0) + { + fprintf(stderr,RED "No Path Given to readFileToMemory\n" NORMAL); + return 0; + } + + *length = 0; + FILE * pFile = fopen ( filename , "rb" ); + + if (pFile==0) + { + fprintf(stderr,RED "readFileToMemory failed\n" NORMAL); + fprintf(stderr,RED "Could not read file %s \n" NORMAL,filename); + return 0; + } + + // obtain file size: + fseek (pFile , 0 , SEEK_END); + unsigned long lSize = ftell (pFile); + rewind (pFile); + + // allocate memory to contain the whole file: + unsigned long bufferSize = sizeof(char)*(lSize+1); + char * buffer = (char*) malloc (bufferSize); + if (buffer == 0 ) + { + fprintf(stderr,RED "Could not allocate enough memory for file %s \n" NORMAL,filename); + fclose(pFile); + return 0; + } + + // copy the file into the buffer: + size_t result = fread (buffer,1,lSize,pFile); + if (result != lSize) + { + free(buffer); + fprintf(stderr,RED "Could not read the whole file onto memory %s \n" NORMAL,filename); + fclose(pFile); + return 0; + } + + /* the whole file is now loaded in the memory buffer. */ + + // terminate + fclose (pFile); + + buffer[lSize]=0; //Null Terminate Buffer! + *length = (unsigned int) lSize; + return buffer; +} + + +#ifdef __cplusplus +} +#endif + + + +#endif \ No newline at end of file From cd9d222bb2f83990189213d2d0261453dd553313 Mon Sep 17 00:00:00 2001 From: Ammar Qammaz Date: Mon, 17 Jul 2023 18:18:04 +0300 Subject: [PATCH 002/154] this is the current state of the implementation for MNET4 --- src/python/mnet4/BVH/BVHConverter.cbp | 229 +++ src/python/mnet4/BVH/BVHTester.cbp | 218 +++ src/python/mnet4/BVH/BVHToCSV.py | 86 ++ src/python/mnet4/BVH/CMakeLists.txt | 79 + src/python/mnet4/BVH/bvhConverter.c | 814 ++++++++++ src/python/mnet4/BVH/bvhConverter.py | 881 +++++++++++ src/python/mnet4/BVH/bvhLibrary.h | 64 + src/python/mnet4/BVH/bvhLibrary.py | 238 +++ src/python/mnet4/BVH/calibration.py | 139 ++ src/python/mnet4/BVH/gatherFiles.sh | 72 + .../mnet4/BVH/headerWithHeadAndOneMotion.bvh | 1022 ++++++++++++ src/python/mnet4/BVH/libBVHConverter.so | Bin 0 -> 438208 bytes src/python/mnet4/BVH/main.c | 1373 +++++++++++++++++ src/python/mnet4/BVH/makeLibrary.sh | 130 ++ src/python/mnet4/EDM.py | 75 + src/python/mnet4/EigenPoses.py | 68 + src/python/mnet4/MocapNET.py | 953 ++++++++++++ src/python/mnet4/MocapNETONNX.py | 385 +++++ src/python/mnet4/MocapNETTFLite.py | 326 ++++ src/python/mnet4/MocapNETTensorflow.py | 437 ++++++ src/python/mnet4/MocapNETVisualization.py | 808 ++++++++++ src/python/mnet4/NSDM.py | 521 +++++++ src/python/mnet4/PoseNET.py | 891 +++++++++++ src/python/mnet4/PoseNETServer.py | 232 +++ src/python/mnet4/align2DPoints.py | 119 ++ src/python/mnet4/align3DPoints.py | 280 ++++ src/python/mnet4/csvNET.py | 420 +++++ src/python/mnet4/dataDecomposition.py | 783 ++++++++++ .../dataset/headerWithHeadAndOneMotion.bvh | 1022 ++++++++++++ src/python/mnet4/espStream.py | 75 + src/python/mnet4/evaluateMocapNET.py | 674 ++++++++ src/python/mnet4/folderStream.py | 116 ++ src/python/mnet4/getModelFromDatabase.py | 483 ++++++ src/python/mnet4/holisticPartNames.py | 990 ++++++++++++ src/python/mnet4/jointMap.py | 81 + .../mnet4/mediapipeHolisticWebcamMocapNET.py | 547 +++++++ src/python/mnet4/plotCSV.py | 242 +++ .../mnet4/principleComponentAnalysis.py | 1068 +++++++++++++ .../mnet4/principleComponentAnalysisTool.py | 287 ++++ src/python/mnet4/readCSV.py | 1302 ++++++++++++++++ src/python/mnet4/setup.sh | 44 + .../mnet4/sobolRandomDatasetGenerator.py | 542 +++++++ src/python/mnet4/tools.py | 585 +++++++ src/python/mnet4/writeCSV.py | 33 + 44 files changed, 19734 insertions(+) create mode 100644 src/python/mnet4/BVH/BVHConverter.cbp create mode 100644 src/python/mnet4/BVH/BVHTester.cbp create mode 100644 src/python/mnet4/BVH/BVHToCSV.py create mode 100644 src/python/mnet4/BVH/CMakeLists.txt create mode 100644 src/python/mnet4/BVH/bvhConverter.c create mode 100644 src/python/mnet4/BVH/bvhConverter.py create mode 100644 src/python/mnet4/BVH/bvhLibrary.h create mode 100644 src/python/mnet4/BVH/bvhLibrary.py create mode 100644 src/python/mnet4/BVH/calibration.py create mode 100755 src/python/mnet4/BVH/gatherFiles.sh create mode 100644 src/python/mnet4/BVH/headerWithHeadAndOneMotion.bvh create mode 100755 src/python/mnet4/BVH/libBVHConverter.so create mode 100644 src/python/mnet4/BVH/main.c create mode 100755 src/python/mnet4/BVH/makeLibrary.sh create mode 100755 src/python/mnet4/EDM.py create mode 100755 src/python/mnet4/EigenPoses.py create mode 100755 src/python/mnet4/MocapNET.py create mode 100755 src/python/mnet4/MocapNETONNX.py create mode 100755 src/python/mnet4/MocapNETTFLite.py create mode 100755 src/python/mnet4/MocapNETTensorflow.py create mode 100755 src/python/mnet4/MocapNETVisualization.py create mode 100755 src/python/mnet4/NSDM.py create mode 100755 src/python/mnet4/PoseNET.py create mode 100755 src/python/mnet4/PoseNETServer.py create mode 100755 src/python/mnet4/align2DPoints.py create mode 100755 src/python/mnet4/align3DPoints.py create mode 100644 src/python/mnet4/csvNET.py create mode 100755 src/python/mnet4/dataDecomposition.py create mode 100644 src/python/mnet4/dataset/headerWithHeadAndOneMotion.bvh create mode 100644 src/python/mnet4/espStream.py create mode 100755 src/python/mnet4/evaluateMocapNET.py create mode 100644 src/python/mnet4/folderStream.py create mode 100755 src/python/mnet4/getModelFromDatabase.py create mode 100755 src/python/mnet4/holisticPartNames.py create mode 100755 src/python/mnet4/jointMap.py create mode 100755 src/python/mnet4/mediapipeHolisticWebcamMocapNET.py create mode 100755 src/python/mnet4/plotCSV.py create mode 100755 src/python/mnet4/principleComponentAnalysis.py create mode 100755 src/python/mnet4/principleComponentAnalysisTool.py create mode 100755 src/python/mnet4/readCSV.py create mode 100644 src/python/mnet4/setup.sh create mode 100644 src/python/mnet4/sobolRandomDatasetGenerator.py create mode 100755 src/python/mnet4/tools.py create mode 100755 src/python/mnet4/writeCSV.py diff --git a/src/python/mnet4/BVH/BVHConverter.cbp b/src/python/mnet4/BVH/BVHConverter.cbp new file mode 100644 index 0000000..e69cce6 --- /dev/null +++ b/src/python/mnet4/BVH/BVHConverter.cbp @@ -0,0 +1,229 @@ + + + + + + diff --git a/src/python/mnet4/BVH/BVHTester.cbp b/src/python/mnet4/BVH/BVHTester.cbp new file mode 100644 index 0000000..78fda98 --- /dev/null +++ b/src/python/mnet4/BVH/BVHTester.cbp @@ -0,0 +1,218 @@ + + + + + + diff --git a/src/python/mnet4/BVH/BVHToCSV.py b/src/python/mnet4/BVH/BVHToCSV.py new file mode 100644 index 0000000..eca45fa --- /dev/null +++ b/src/python/mnet4/BVH/BVHToCSV.py @@ -0,0 +1,86 @@ +def bvh_to_csv(bvh_file_path_in,csv_file_path_out): + with open(bvh_file_path_in, 'r') as file: + lines = file.readlines() + + motion_start_index = None + motionIDAssignments = list() + + # Parse Hierarchy + for i, line in enumerate(lines): + if 'MOTION' in line: + motion_start_index = i + 2 # Skip the "Frames:" line + break + + line = line.strip() + + if 'ROOT' in line: + root_name = line.split(" ")[1].strip() + current_joint = root_name + + elif 'JOINT' in line: + joint_name = line.split(" ")[1].strip() + current_joint = joint_name + + #elif 'End Site' in line: + # joint_name = 'End Site' + # current_joint = "EndSite_%s" % current_joint + + elif 'CHANNELS' in line: + splitLine = line.split(" ") + numberOfchannels = int(splitLine[1]) + #print(current_joint," has ",numberOfchannels," channels ") + for mID in range(0,numberOfchannels): + motionIDAssignments.append("%s_%s" % (current_joint,splitLine[2+mID])) + + + #Debug + #print("Hierarchy To Motion IDS Mapping :") + #for i in range(0,len(motionIDAssignments)): + # print(i, " - ",motionIDAssignments[i]) + numberOfMotionIDs = len(motionIDAssignments) + + f = open(csv_file_path_out,'w') + #------------------------------------------------------------------------ + for column in range(numberOfMotionIDs): + if (column>0): + f.write(',') + f.write("%s"%(motionIDAssignments[column])) + f.write('\n') + #------------------------------------------------------------------------ + + # Parse Motion + if motion_start_index: + for i in range(motion_start_index, len(lines)): + if ("Frame" not in lines[i]): #skip Frames: / Frame Time: lines + + #Got a fresh motion line + motionData = lines[i].split(" ") + #Make sure it is consistent with our hierarchy length + if (len(motionData)!=numberOfMotionIDs): + raise ValueError('Line %u: Mismatched number of motion values (%u) compared to our hierarchy (%u)' % (i,len(motionData),len(motionIDAssignments)) ) + #Dump it to CSV + #---------------------------------------------- + for column in range(numberOfMotionIDs): + if (column>0): + f.write(',') + f.write("%f"%(float(motionData[column]))) + f.write('\n') + #---------------------------------------------- + f.close() + + +if __name__ == '__main__': + import sys + if (len(sys.argv)>1): + for i in range(1,len(sys.argv)): + baseFilename = sys.argv[i] + if (".bvh" in baseFilename): + print("Will convert ",baseFilename) + targetFilename = "%s.csv" % (baseFilename.rsplit(".")[0]) + print("To ",targetFilename) + bvh_to_csv(baseFilename,targetFilename) + else: + print("Will NOT convert ",baseFilename," it does not have a .bvh extension") + else: + raise ValueError('Please call the utility with a single path of BVH file\n') + diff --git a/src/python/mnet4/BVH/CMakeLists.txt b/src/python/mnet4/BVH/CMakeLists.txt new file mode 100644 index 0000000..07ccb99 --- /dev/null +++ b/src/python/mnet4/BVH/CMakeLists.txt @@ -0,0 +1,79 @@ +project( BVHTester ) +cmake_minimum_required( VERSION 2.8.7 ) +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/modules ${CMAKE_MODULE_PATH}) + + + +IF( ENABLE_JPG ) + MESSAGE("JPGs will be included in this codec build") + set(JPG_Libs jpeg ) + set(JPG_Parts jpgInput.c jpgInput.h jpgExifexternal.c jpgExifexternal.h jpgExiforient_embed.c jpgExiforient_embed.h ) + set(JPG_Includes ${CMAKE_SOURCE_DIR}/3dparty/OpenNI2/Include/ ) + add_definitions(-DUSE_JPG_FILES) + add_definitions(-DENABLE_JPG) +ENDIF( ENABLE_JPG ) + + +IF( ENABLE_PNG ) + MESSAGE("PNGs will be included in this codec build") + set(PNG_Libs png ) + set(PNG_Parts pngInput.c pngInput.h) + set(PNG_Includes ${CMAKE_SOURCE_DIR}/3dparty/OpenNI2/Include/ ) + add_definitions(-DUSE_PNG_FILES) + add_definitions(-DENABLE_PNG) +ENDIF( ENABLE_PNG ) + +IF( ENABLE_SHADERS ) + MESSAGE("Shaders will be included in this codec build") + set(GLEW_Libs GLEW ) #sudo apt-get install libglew-dev + set(GLEW_Parts ) + set(GLEW_Includes ) + add_definitions(-DUSE_GLEW) +ENDIF( ENABLE_SHADERS ) + +add_executable( + BVHTester + main.c + ../../Library/MotionCaptureLoader/bvh_loader.c + ../../Library/MotionCaptureLoader/calculate/bvh_transform.c + ../../Library/MotionCaptureLoader/calculate/bvh_project.c + ../../Library/MotionCaptureLoader/calculate/bvh_to_tri_pose.c + ../../Library/MotionCaptureLoader/calculate/smoothing.h + ../../Library/MotionCaptureLoader/calculate/smoothing.c + ../../Library/MotionCaptureLoader/import/fromBVH.c + ../../Library/MotionCaptureLoader/export/bvh_export.c + ../../Library/MotionCaptureLoader/export/bvh_to_c.c + ../../Library/MotionCaptureLoader/export/bvh_to_bvh.c + ../../Library/MotionCaptureLoader/export/bvh_to_svg.c + ../../Library/MotionCaptureLoader/export/bvh_to_csv.c + ../../Library/MotionCaptureLoader/export/bvh_to_json.c + ../../Library/MotionCaptureLoader/export/bvh_to_trajectoryParserTRI.c + ../../Library/MotionCaptureLoader/export/bvh_to_trajectoryParserPrimitives.c + ../../Library/MotionCaptureLoader/edit/bvh_randomize.c + ../../Library/MotionCaptureLoader/edit/bvh_rename.c + ../../Library/MotionCaptureLoader/edit/bvh_remapangles.c + ../../Library/MotionCaptureLoader/edit/bvh_interpolate.c + ../../Library/MotionCaptureLoader/edit/bvh_merge.c + ../../Library/MotionCaptureLoader/edit/bvh_filter.c + ../../Library/MotionCaptureLoader/edit/bvh_cut_paste.c + ../../Library/MotionCaptureLoader/ik/bvh_inverseKinematics.c + ../../Library/MotionCaptureLoader/ik/hardcodedProblems_inverseKinematics.c + #../../Library/MotionCaptureLoader/ik/levmar.c + ../../Library/MotionCaptureLoader/metrics/bvh_measure.c + ../../Library/MotionCaptureLoader/tests/test.c + ../../Library/TrajectoryParser/InputParser_C.c + ../../../../../tools/AmMatrix/matrix4x4Tools.c + ../../../../../tools/AmMatrix/matrixMultiplicationOptimization.c + ../../../../../tools/AmMatrix/matrixOpenGL.c + ../../../../../tools/AmMatrix/quaternions.c + ../../../../../tools/AmMatrix/simpleRenderer.c + ) + +target_link_libraries(BVHTester rt m pthread ) + + +set_target_properties(BVHTester PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}" + ) diff --git a/src/python/mnet4/BVH/bvhConverter.c b/src/python/mnet4/BVH/bvhConverter.c new file mode 100644 index 0000000..d431d17 --- /dev/null +++ b/src/python/mnet4/BVH/bvhConverter.c @@ -0,0 +1,814 @@ +/** @file main.c + * @brief A library that can parse BVH files and perform various processing options as a commandline tool + * X86 compilation: gcc -o -L/usr/X11/lib main main.c + * X64 compilation: gcc -o -L/usr/X11/lib64 main main.c + * @author Ammar Qammaz (AmmarkoV) + */ + +#include +#include +#include +#include +#include + +#include "../../Library/TrajectoryParser/TrajectoryParserDataStructures.h" +#include "../../Library/MotionCaptureLoader/bvh_loader.h" +#include "../../Library/MotionCaptureLoader/calculate/bvh_to_tri_pose.h" +#include "../../Library/MotionCaptureLoader/calculate/smoothing.h" + +#include "../../Library/MotionCaptureLoader/export/bvh_to_trajectoryParserTRI.h" +#include "../../Library/MotionCaptureLoader/export/bvh_to_trajectoryParserPrimitives.h" +#include "../../Library/MotionCaptureLoader/export/bvh_export.h" +#include "../../Library/MotionCaptureLoader/export/bvh_to_bvh.h" +#include "../../Library/MotionCaptureLoader/export/bvh_to_csv.h" +#include "../../Library/MotionCaptureLoader/export/bvh_to_c.h" + +#include "../../Library/MotionCaptureLoader/edit/bvh_cut_paste.h" +#include "../../Library/MotionCaptureLoader/edit/bvh_randomize.h" +#include "../../Library/MotionCaptureLoader/edit/bvh_filter.h" +#include "../../Library/MotionCaptureLoader/edit/bvh_rename.h" +#include "../../Library/MotionCaptureLoader/edit/bvh_merge.h" +#include "../../Library/MotionCaptureLoader/edit/bvh_remapangles.h" +#include "../../Library/MotionCaptureLoader/edit/bvh_interpolate.h" + +#include "../../Library/MotionCaptureLoader/ik/bvh_inverseKinematics.h" +#include "../../Library/MotionCaptureLoader/ik/hardcodedProblems_inverseKinematics.h" + +#include "../../Library/MotionCaptureLoader/metrics/bvh_measure.h" +#include "../../Library/MotionCaptureLoader/tests/test.h" + +#include "../../../../../tools/AmMatrix/matrix4x4Tools.h" +#include "../../../../../tools/AmMatrix/matrixOpenGL.h" + + +#define NORMAL "\033[0m" +#define BLACK "\033[30m" /* Black */ +#define RED "\033[31m" /* Red */ +#define GREEN "\033[32m" /* Green */ +#define YELLOW "\033[33m" /* Yellow */ +#define BLUE "\033[34m" /* Blue */ +#define MAGENTA "\033[35m" /* Magenta */ +#define CYAN "\033[36m" /* Cyan */ +#define WHITE "\033[37m" /* White */ + +void haltOnError(unsigned int haltingSwitch,const char * message) +{ + fprintf(stderr,RED "=======================================\n"); + fprintf(stderr,"=======================================\n"); + fprintf(stderr,"Encountered error during procedure %s \n",message); + fprintf(stderr,"=======================================\n"); + fprintf(stderr,"=======================================\n" NORMAL); + + if (haltingSwitch) + { + fprintf(stderr,RED "Halting because of --haltonerror switch\n" NORMAL); + exit(1); + } +} + +void incorrectArguments() +{ + fprintf(stderr,RED "Incorrect number of arguments.. \n" NORMAL); + exit(1); +} + +//------------------------------------------------------------------ +//------------------------------------------------------------------ +//------------------------------------------------------------------ +//------------------------------------------------------------------ +struct BVH_MotionCapture bvhAtomicMotion = {0}; +struct BVH_Transform bvhTransformAtomic = {0}; +struct simpleRenderer rendererAtomic = {0}; +struct BVH_RendererConfiguration renderingAtomicConfiguration = {0}; +struct ikProblem * atomicFaceProblem = 0; +struct ikProblem * atomicBodyProblem = 0; +struct ikProblem * atomicLHandProblem = 0; +struct ikProblem * atomicRHandProblem = 0; +struct MotionBuffer * atomicPenultimateSolution=0; +struct MotionBuffer * atomicPreviousSolution=0; +struct MotionBuffer * atomicSolution=0; +struct ButterWorthArray * atomicSmoothingFilter = 0; +//------------------------------------------------------------------ +//------------------------------------------------------------------ +//------------------------------------------------------------------ +//------------------------------------------------------------------ +int bvhConverter_loadAtomic(const char *path) +{ + float scaleWorld=1.0; + int immediatelyHaltOnError = 1; + fprintf(stderr,"Attempting to load %s\n",path); + if (!bvh_loadBVH(path, &bvhAtomicMotion, scaleWorld)) + { + haltOnError(immediatelyHaltOnError,"Error loading bvh file.."); + } + //Change joint names.. + bvh_renameJointsForCompatibility(&bvhAtomicMotion); + + + // Emulate GoPro Hero4 @ FullHD mode by default.. + // https://gopro.com/help/articles/Question_Answer/HERO4-Field-of-View-FOV-Information + renderingAtomicConfiguration.near = 1.0; + renderingAtomicConfiguration.far = 10000.0; + renderingAtomicConfiguration.width = 1920; + renderingAtomicConfiguration.height = 1080; + renderingAtomicConfiguration.cX = (float)renderingAtomicConfiguration.width/2; + renderingAtomicConfiguration.cY = (float)renderingAtomicConfiguration.height/2; + renderingAtomicConfiguration.fX = 582.18394; + renderingAtomicConfiguration.fY = 582.52915; + //---------------------------------------------- + simpleRendererDefaults( + &rendererAtomic, + renderingAtomicConfiguration.width, + renderingAtomicConfiguration.height, + renderingAtomicConfiguration.fX, + renderingAtomicConfiguration.fY + ); + //---------------------------------------------- + simpleRendererInitialize(&rendererAtomic); + //---------------------------------------------- + return bvhAtomicMotion.jointHierarchySize; +} + + +int bvhConverter_unloadAtomic() +{ + /* TODO: unload all this..! + struct BVH_MotionCapture bvhAtomicMotion={0}; +struct BVH_Transform bvhTransformAtomic={0}; +struct simpleRenderer rendererAtomic={0}; +struct BVH_RendererConfiguration renderingAtomicConfiguration={0}; +struct ikProblem * atomicFaceProblem = 0; +struct ikProblem * atomicBodyProblem = 0; +struct ikProblem * atomicLHandProblem = 0; +struct ikProblem * atomicRHandProblem = 0; +struct MotionBuffer * atomicPreviousSolution=0; +struct MotionBuffer * atomicSolution=0; +*/ + fprintf(stderr,"bvhConverter_unloadAtomic not implemented yet..\n"); + return 0; +} + +int bvhConverter_rendererConfigurationAtomic(const char ** labels,const float * values,int numberOfElements) +{ + // Emulate GoPro Hero4 @ FullHD mode by default.. + // https://gopro.com/help/articles/Question_Answer/HERO4-Field-of-View-FOV-Information + renderingAtomicConfiguration.near = 1.0; + renderingAtomicConfiguration.far = 10000.0; + renderingAtomicConfiguration.width = 1920; + renderingAtomicConfiguration.height = 1080; + renderingAtomicConfiguration.cX = (float)renderingAtomicConfiguration.width/2; + renderingAtomicConfiguration.cY = (float)renderingAtomicConfiguration.height/2; + renderingAtomicConfiguration.fX = 582.18394; + renderingAtomicConfiguration.fY = 582.52915; + + fprintf(stderr,"bvhConverter_rendererConfigurationAtomic received %u elements\n",numberOfElements); + for (int i=0; i%0.2f\n",i,labels[i],values[i]); + if (strcmp(labels[i],"near")==0) { renderingAtomicConfiguration.near = values[i]; } else + if (strcmp(labels[i],"far")==0) { renderingAtomicConfiguration.far = values[i]; } else + if (strcmp(labels[i],"width")==0) { renderingAtomicConfiguration.width = (unsigned int) values[i]; } else + if (strcmp(labels[i],"height")==0) { renderingAtomicConfiguration.height = (unsigned int) values[i]; } else + if (strcmp(labels[i],"cX")==0) { renderingAtomicConfiguration.cX = values[i]; } else + if (strcmp(labels[i],"cY")==0) { renderingAtomicConfiguration.cY = values[i]; } else + if (strcmp(labels[i],"fX")==0) { renderingAtomicConfiguration.fX = values[i]; } else + if (strcmp(labels[i],"fY")==0) { renderingAtomicConfiguration.fY = values[i]; } else + { + fprintf(stderr,RED"bvhConverter_rendererConfigurationAtomic: Unknown command %u - %s->%0.2f\n" NORMAL,i,labels[i],values[i]); + } + } + + simpleRendererDefaults( + &rendererAtomic, + renderingAtomicConfiguration.width, + renderingAtomicConfiguration.height, + renderingAtomicConfiguration.fX, + renderingAtomicConfiguration.fY + ); + simpleRendererInitialize(&rendererAtomic); + return 1; +} + +int bvhConverter_processFrame(int frameID) +{ + int occlusions=1; + return performPointProjectionsForFrame( + &bvhAtomicMotion, + &bvhTransformAtomic, + frameID, + &rendererAtomic, + occlusions, + renderingAtomicConfiguration.isDefined + ); +} + + +int bvhConverter_scale(float scaleRatio) +{ + fprintf(stderr,"Offset scaling ratio = %0.2f \n",scaleRatio); + return bvh_scaleAllOffsets( + &bvhAtomicMotion, + scaleRatio + ); +} + +int bvhConverter_getNumberOfMotionValuesPerFrame() +{ + return bvhAtomicMotion.numberOfValuesPerFrame; +} + +int bvhConverter_getNumberOfJoints() +{ + return bvhAtomicMotion.jointHierarchySize; +} + +int bvhConverter_writeBVH(char * filename,int writeHierarchy,int writeMotion) +{ + return dumpBVHToBVH( + filename, + &bvhAtomicMotion, + writeHierarchy, + writeMotion + ); +} + +int bvhConverter_getMotionValueOfFrame(int fID,int mID) +{ + return bvh_getMotionValueOfFrame(&bvhAtomicMotion,fID,mID); +} + +int bvhConverter_setMotionValueOfFrame(int fID,int mID,float value) +{ + float localValue = value; + return bvh_setMotionValueOfFrame(&bvhAtomicMotion,fID,mID,&localValue); +} + +int bvhConverter_getJointNameJointID(const char * jointName) +{ + //fprintf(stderr,"Asked to resolve %s\n",jointName); + BVHJointID jID=0; + if ( + bvh_getJointIDFromJointNameNocase( + &bvhAtomicMotion, + jointName, + &jID + ) + ) + { + return jID; + } + fprintf(stderr,RED "BVH library could not resolve joint \"%s\" \n" NORMAL,jointName); + return -1; +} + +const char * bvhConverter_getJointNameFromJointID(int jointID) +{ + if (jointID=bvhAtomicMotion.numberOfFrames) { return 0; } + //------------------------------------------------------------ + //fprintf(stderr,"bvhConverter_modifyAtomic received element %s with value %0.2f for frame %u\n",label,value,frameID); + //------------------------------------------------------------ + int everythingOk = 1; + char jointName[513]={0}; + snprintf(jointName,512,"%s",label); + char * delimeter = strchr(jointName,'_'); + if (delimeter==0) + { + fprintf(stderr,"bvhConverter_modifyAtomic received element %s with value %0.2f for frame %u ",label,value,frameID); + fprintf(stderr,"it doesn't have a degree of freedom associated so ignoring it.."); + return 0; + } + *delimeter = 0; + char * dof = delimeter+1; + //======================================================= + lowercase(jointName); + lowercase(dof); + //======================================================= + if (strstr(jointName,"endsite_")!=0) + { + fprintf(stderr,RED "Endsites can't be modified..!\n" NORMAL); + return 0; + } + + if (strcmp(jointName,"neck01")==0) + { + snprintf(jointName,512,"neck1"); //Fix ? + } + if (strcmp(jointName,"lthumbbase")==0) + { + snprintf(jointName,512,"__lthumb"); //Fix ? + } + if (strcmp(jointName,"rthumbbase")==0) + { + snprintf(jointName,512,"__rthumb"); //Fix ? + } + + //fprintf(stderr," %u - %s->%0.2f ",i,label,value); + //fprintf(stderr," Joint:%s Control:%s\n",jointName,dof); + //======================================================= + //int jointID = bvhConverter_getJointNameJointID(jointName); + BVHJointID jointID=0; + if ( + bvh_getJointIDFromJointNameNocase( + &bvhAtomicMotion, + jointName, + &jointID + ) + ) + { + // The next line is a debug message that spams a *lot*! + //fprintf(stderr,"Joint ID %u / %s|%s => %0.2f \n",jointID,bvhAtomicMotion.jointHierarchy[jointID].jointName,dof,value); + //============================================================================================================== + if (strcmp(dof,"xposition")==0) { bvh_setJointPositionXAtFrame(&bvhAtomicMotion,jointID,frameID,value); } else + if (strcmp(dof,"yposition")==0) { bvh_setJointPositionYAtFrame(&bvhAtomicMotion,jointID,frameID,value); } else + if (strcmp(dof,"zposition")==0) { bvh_setJointPositionZAtFrame(&bvhAtomicMotion,jointID,frameID,value); } else + if (strcmp(dof,"xrotation")==0) { bvh_setJointRotationXAtFrame(&bvhAtomicMotion,jointID,frameID,value); } else + if (strcmp(dof,"yrotation")==0) { bvh_setJointRotationYAtFrame(&bvhAtomicMotion,jointID,frameID,value); } else + if (strcmp(dof,"zrotation")==0) { bvh_setJointRotationZAtFrame(&bvhAtomicMotion,jointID,frameID,value); } else + if (strcmp(dof,"wrotation")==0) { bvh_setJointRotationWAtFrame(&bvhAtomicMotion,jointID,frameID,value); } else + { + fprintf(stderr,RED "\n\n\nBVH library could not perform modification \"%s\" for joint \"%s\" \n\n\n" NORMAL,dof,jointName); + everythingOk=0; + } + //============================================================================================================== + } else + { + fprintf(stderr,RED "\nBVH library modification could not resolve joint \"%s\" \n" NORMAL,jointName); + everythingOk=0; + } + return everythingOk; +} + + + +int bvhConverter_modifyAtomic(const char ** labels,const float * values,int numberOfElements,int frameID) +{ + //fprintf(stderr,"bvhConverter_modifyAtomic received %u elements\n",numberOfElements); + int everythingOk = 1; + for (int i=0; i 300 so 10 is a good limit + ikConfig.spring = 20; + ikConfig.dumpScreenshots = 0; // Dont thrash disk + ikConfig.verbose = 0; //Dont spam console + ikConfig.tryMaintainingLocalOptima=1; //Less Jittery but can be stuck at local optima + ikConfig.dontUseSolutionHistory=0; + ikConfig.useLangevinDynamics = langevinDynamics; + ikConfig.ikVersion = IK_VERSION; + //------------------------------------ + + int multiThreading = 0; + + //====================================================================================================== + //====================================================================================================== + //====================================================================================================== + if ( (strcmp(bodyPart,"body")==0) || (strcmp(bodyPart,"lhand")==0) || (strcmp(bodyPart,"rhand")==0) ) + { + //Keep history..! + copyMotionBuffer(atomicPenultimateSolution,atomicPreviousSolution); + copyMotionBuffer(atomicPreviousSolution,atomicSolution); + bvh_copyMotionFrameToMotionBuffer( + &bvhAtomicMotion, + atomicSolution, + frameID + ); + + char jointName[512]={0}; + struct BVH_Transform bvhTargetTransform={0}; + int occlusions=1; + performPointProjectionsForFrame( + &bvhAtomicMotion, + &bvhTargetTransform, + frameID, + &rendererAtomic, + occlusions, + renderingAtomicConfiguration.isDefined + ); + + for (int i=0; i %s with %0.2f \n",i,labels[i],values[i] ); + snprintf(jointName,512,"%s",labels[i]); + char * delimeter = strchr(jointName,'_'); + + if (delimeter!=0) + { + *delimeter = 0; + char * coord = jointName; + char * dof = delimeter+1; + //======================================================= + lowercase(coord); + lowercase(dof); + + if ( (coord[0]=='2') && (coord[1]=='d') && ( (coord[2]=='x') || (coord[2]=='y') ) ) + { + BVHJointID jID=0; + if ( bvh_getJointIDFromJointNameNocase(&bvhAtomicMotion,dof,&jID) ) + { + if (coord[2]=='x') + { + //fprintf(stderr,GREEN "%s/%s \n" NORMAL,coord,dof); + bvhTargetTransform.joint[jID].pos2D[0] = (float) values[i]*renderingAtomicConfiguration.width; + } else + if (coord[2]=='y') + { + //fprintf(stderr,GREEN "%s/%s \n" NORMAL,coord,dof); + bvhTargetTransform.joint[jID].pos2D[1] = (float) values[i]*renderingAtomicConfiguration.height; + } + } else + { + //fprintf(stderr,RED "IK: Could not resolve Joint %s for Number %u => %s with %0.2f \n" NORMAL,dof,i,labels[i],values[i] ); + } + }//2DX/Y + + } //Tag has an _ and we process it + + }//Loop over received elements + + if ( approximateBodyFromMotionBufferUsingInverseKinematics( + &bvhAtomicMotion, + &rendererAtomic, + selectedProblem, + &ikConfig, + //---------------- + atomicPenultimateSolution, + atomicPreviousSolution, + atomicSolution, + 0, //No ground truth.. + //---------------- + &bvhTargetTransform, + //---------------- + multiThreading,// 0=single thread, 1=multi thread + //---------------- + &initialMAEInPixels, + &finalMAEInPixels, + &initialMAEInMM, + &finalMAEInMM + ) + ) + { + + /* No longer automatically do this to avoid multiple smoothings per frame + if ( (fSampling>0.0) && (fCutoff>0.0) ) + { //Only perform smoothing if sampling/cutoff is set.. + for (int mID=0; mIDbufferSize; mID++) + { + atomicSolution->motion[mID] = butterWorth_filterArrayElement(atomicSmoothingFilter,mID,atomicSolution->motion[mID]); + } + }*/ + + + + if(!bvh_copyMotionBufferToMotionFrame( + &bvhAtomicMotion, + frameID, + atomicSolution + ) + ) + { + fprintf(stderr,RED "Failed bvh_copyMotionBufferToMotionFrame\n" NORMAL); + } + + //Perform and update projections for new results..! + bvhConverter_processFrame(frameID); + } else + { + fprintf(stderr,RED "Failed approximateBodyFromMotionBufferUsingInverseKinematics\n" NORMAL); + } + } + + return finalMAEInPixels; +} + + +int bvhConverter_smooth(int frameID,float fSampling,float fCutoff) +{ + if (atomicSolution==0) { fprintf(stderr,RED "bvhConverter_smooth has no solution to work with..\n" NORMAL); return 0; } + if (atomicSmoothingFilter==0) { fprintf(stderr,RED "bvhConverter_smooth has no initialized filter to work with..\n" NORMAL); return 0; } + + if ( (fSampling>0.0) && (fCutoff>0.0) ) + { //Only perform smoothing if sampling/cutoff is set.. + fprintf(stderr,GREEN "bvhConverter_smooth going through motions\n" NORMAL); + for (int mID=0; mIDbufferSize; mID++) + { + atomicSolution->motion[mID] = butterWorth_filterArrayElement(atomicSmoothingFilter,mID,atomicSolution->motion[mID]); + } + + + + fprintf(stderr,GREEN "copyback..\n" NORMAL); + if(!bvh_copyMotionBufferToMotionFrame( + &bvhAtomicMotion, + frameID, + atomicSolution + ) + ) + { + fprintf(stderr,RED "Failed bvh_copyMotionBufferToMotionFrame\n" NORMAL); + } + + //Perform and update projections for new results..! + bvhConverter_processFrame(frameID); + return 1; + } + return 0; +} + + + +int bvhConverter(int argc,const char **argv) +{ + fprintf(stderr,RED "BVHConverter.c main is a stub please use the python code\n" NORMAL); + return 0; +} + +int main(int argc,const char **argv) +{ + fprintf(stderr,RED "BVHConverter.c main is a stub please use the python code\n" NORMAL); + return 0; +} + diff --git a/src/python/mnet4/BVH/bvhConverter.py b/src/python/mnet4/BVH/bvhConverter.py new file mode 100644 index 0000000..0cda3c6 --- /dev/null +++ b/src/python/mnet4/BVH/bvhConverter.py @@ -0,0 +1,881 @@ +#!/usr/bin/python3 + +import ctypes +import os +import sys +from ctypes import * +from os.path import exists + +class bcolors: + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKGREEN = '\033[92m' + WARNING = '\033[93m' + FAIL = '\033[91m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + + +#This mimics the calibration files like ; +# https://github.com/AmmarkoV/RGBDAcquisition/blob/master/tools/Calibration/calibration.c +def readCalibrationFromFile(filename): + calib = dict() + if filename is None: + return calib + + fp = None + try: + fp = open(filename, "r") + except IOError: + return calib + + # Our state + # ---------------------------- + i = 0 + category = 0 + line_length = 0 + lines_at_current_category = 0 + # ---------------------------- + + + for line in fp: + #-------------------------------------- + line = line.rstrip("\r\n") + line_length = len(line) + #-------------------------------------- + if line_length > 0: + if line[line_length - 1] == '\n': + line = line[:-1] + if line[line_length - 1] == '\r': + line = line[:-1] + #-------------------------------------- + if line_length > 1: + if line[line_length - 2] == '\n': + line = line[:-2] + if line[line_length - 2] == '\r': + line = line[:-2] + #-------------------------------------- + if line[0] == '%': + lines_at_current_category = 0 + #-------------------------------------- + # ---------------------------- ---------------------------- ---------------------------- + if line == "%I": + category = 1 + calib["intrinsic"] = list() + elif line == "%D": + category = 2 + elif line == "%T": + category = 3 + calib["extrinsicTranslation"] = list() + elif line == "%R": + category = 4 + calib["extrinsicRotationRodriguez"] = list() + elif line == "%NF": + category = 5 + elif line == "%UNIT": + category = 6 + elif line == "%RT4*4": + category = 7 + calib["extrinsic"] = list() + elif line == "%Width": + category = 8 + elif line == "%Height": + category = 9 + else: + # ---------------------------- ---------------------------- ---------------------------- + if category == 1: + calib["intrinsicParametersSet"] = 1 + lines_at_current_category = min(lines_at_current_category, 9) + calib["intrinsic"].append(float(line)) + lines_at_current_category += 1 + if (lines_at_current_category==9): + category = 0 + elif category == 2: + if lines_at_current_category == 0: + calib["k1"] = float(line) + elif lines_at_current_category == 1: + calib["k2"] = float(line) + elif lines_at_current_category == 2: + calib["p1"] = float(line) + elif lines_at_current_category == 3: + calib["p2"] = float(line) + elif lines_at_current_category == 4: + calib["k3"] = float(line) + lines_at_current_category += 1 + if (lines_at_current_category==4): + category = 0 + elif category == 3: + calib["extrinsicParametersSet"] = 1 + lines_at_current_category = min(lines_at_current_category, 3) + calib["extrinsicTranslation"].append(float(line)) + lines_at_current_category += 1 + if (lines_at_current_category==3): + category = 0 + elif category == 4: + lines_at_current_category = min(lines_at_current_category, 3) + calib["extrinsicRotationRodriguez"].append(float(line)) + lines_at_current_category += 1 + if (lines_at_current_category==3): + category = 0 + elif category == 5: + calib["nearPlane"] = float(line) + category = 0 + elif category == 6: + calib["farPlane"] = float(line) + category = 0 + elif category == 7: + lines_at_current_category = min(lines_at_current_category, 16) + calib["extrinsic"].append(float(line)) + lines_at_current_category += 1 + category = 0 + elif category == 8: + calib["width"] = int(line) + category = 0 + elif category == 9: + calib["height"] = int(line) + category = 0 + # ---------------------------- ---------------------------- ---------------------------- + + fp.close() + + try: + calib["fX"] = calib["intrinsic"][0] + calib["fY"] = calib["intrinsic"][4] + calib["cX"] = calib["intrinsic"][2] + calib["cY"] = calib["intrinsic"][5] + except: + print("No intrinsic matrix declared in ", filename) + print("Cannot populate fX, fY, cX, cY") + + + print("New calibration loaded : ",calib) + + return calib + + + +def loadLibrary(filename,relativePath="",forceUpdate=False): +#-------------------------------------------------------- + if (relativePath!=""): + filename=relativePath+"/"+filename + + if (forceUpdate) or (not exists(filename)): + print("Could not find BVH Library (",filename,"), compiling a fresh one..!") + print("Current directory was (",os.getcwd(),") ") + directory=os.path.dirname(os.path.abspath(filename)) + creationScript = directory+"/makeLibrary.sh" + os.system(creationScript) + #Magic JIT Just in time compilation, java has nothing on this :P + if not exists(filename): + directory=os.path.dirname(os.path.abspath(filename)) + print("Could not make BVH Library, terminating") + print("Directory we tried was : ",directory) + sys.exit(0) + libBVH = CDLL(filename) + #call C function to check connection + libBVH.connect() + libBVH.bvhConverter.restype = c_int + libBVH.bvhConverter.argtypes = c_int,POINTER(c_char_p) + return libBVH +#-------------------------------------------------------- + + +def splitDictionaryInLabelsAndFloats(arguments): + #First prepare the labels of the joints we want to transmit + #-------------------------------------------------- + labels = list(arguments.keys()) + labelsBytes = [] + for i in range(len(labels)): + #Potential renaming.. + #--------------------------------------------- + #if ("endsite_" in labels[i]): + # if ("eye" in labels[i]): + # datasplit = labels[i].split("endsite_",1) + # newLabel="%s%s" % (datasplit[0],datasplit[1]) + # print(labels[i]," renamed to -> ",newLabel) + #--------------------------------------------- + labelsBytes.append(bytes(labels[i], 'utf-8')) + labelsCStr = (ctypes.c_char_p * len(labelsBytes))() + labelsCStr[:] = labelsBytes + #-------------------------------------------------- + + #Then prepare the array of floats we want to transmit + #-------------------------------------------------- + values = list(arguments.values()) + valuesF = list() + for v in values: + try: + valuesF.append(float(v)) + except: + print("Argument ",v,"cannot be casted to float..") + valuesF.append(0.0) + valuesArray = (ctypes.c_float * len(valuesF))() + valuesArray[:] = valuesF + #-------------------------------------------------- + + argc=len(labelsBytes) + + return labelsCStr,valuesArray,argc +#-------------------------------------------------------- + + +class BVH(): + def __init__( + self, + bvhPath:str, + libraryPath:str = "./libBVHConverter.so", + cameraCalibrationFile = "", + forceLibUpdate=False + ): + print("Initializing BVH file ",bvhPath," from ",libraryPath) + self.libBVH = loadLibrary(libraryPath,forceUpdate = forceLibUpdate) + self.numberOfJoints = 0 + self.lastMAEErrorInPixels = 0.0 + self.traceStages = False #If set to true each call will be emitted in stdout to speed-up debugging + self.calib = dict() + #----------------------------------- + if (cameraCalibrationFile!=""): + if not exists(cameraCalibrationFile): + print("Could not find renderer configuration file ",cameraCalibrationFile) + raise FileNotFoundError + self.configureRendererFromFile(cameraCalibrationFile) + #----------------------------------- + if not exists(bvhPath): + print("Could not find BVH file ",bvhPath) + raise FileNotFoundError + self.loadBVHFile(bvhPath) + #-------------------------------------------------------- + def stage(self,message): + if (self.traceStages): + print(bcolors.WARNING,message,bcolors.ENDC) + #-------------------------------------------------------- + def loadBVHFile(self,bvhPath): + self.stage("loadBVHFile") + # create byte objects from the strings + arg1 = bvhPath.encode('utf-8') + # send strings to c function + self.libBVH.bvhConverter_loadAtomic.argtypes = [ctypes.c_char_p] + self.libBVH.bvhConverter_loadAtomic.restype = ctypes.c_int + self.numberOfJoints = self.libBVH.bvhConverter_loadAtomic(arg1) + if (self.numberOfJoints==0): + print("Failed to load BVH file ",bvhPath) + return self.numberOfJoints + #-------------------------------------------------------- + def scale(self, scaleRatio:float): + self.stage("scale") + self.libBVH.bvhConverter_scale.argtypes = [ctypes.c_float] + self.libBVH.bvhConverter_scale.restype = ctypes.c_int + return str(self.libBVH.bvhConverter_scale(scaleRatio)); + #-------------------------------------------------------- + def getJointName(self, jointID:int): + self.stage("getJointName") + self.libBVH.bvhConverter_getJointNameFromJointID.argtypes = [ctypes.c_int] + self.libBVH.bvhConverter_getJointNameFromJointID.restype = ctypes.c_char_p + return str(self.libBVH.bvhConverter_getJointNameFromJointID(jointID).decode('UTF-8')); + #-------------------------------------------------------- + def isJointEndSite(self, jointID:int): + self.stage("isJointEndSite") + self.libBVH.bvhConverter_isJointEndSite.argtypes = [ctypes.c_int] + self.libBVH.bvhConverter_isJointEndSite.restype = ctypes.c_int + retval = self.libBVH.bvhConverter_isJointEndSite(jointID) + return retval + #-------------------------------------------------------- + def getJointParent(self, jointID:int): + self.stage("getJointParent") + self.libBVH.bvhConverter_getJointParent.argtypes = [ctypes.c_int] + self.libBVH.bvhConverter_getJointParent.restype = ctypes.c_int + jointID = self.libBVH.bvhConverter_getJointParent(jointID) + return jointID + #-------------------------------------------------------- + def getJointParentList(self): + self.stage("getJointParentList") + jointList = list() + for jointID in range(0,self.numberOfJoints): + jointList.append(int(self.getJointParent(jointID))) + return jointList + #-------------------------------------------------------- + def getMotionValueOfFrame(self, frameID:int, jointID:int): + self.stage("getMotionValueOfFrame") + self.libBVH.bvhConverter_getMotionValueOfFrame.argtypes = [ctypes.c_int,ctypes.c_int] + self.libBVH.bvhConverter_getMotionValueOfFrame.restype = ctypes.c_float + value = self.libBVH.bvhConverter_getMotionValueOfFrame(frameID,jointID) + return value + #-------------------------------------------------------- + def getAllMotionValuesOfFrame(self, frameID:int): + allMIDs=list() + for mID in range(0,self.getNumberOfMotionValuesPerFrame()): + allMIDs.append(self.getMotionValueOfFrame(frameID,mID)) + return allMIDs + #-------------------------------------------------------- + def saveBVHFileFromList(self, filename:str, allMotionData:list): + self.stage("saveBVHFileFromList") + arg1 = filename.encode('utf-8') + #int bvhConverter_writeBVH(char * filename,int writeHierarchy,int writeMotion) + self.libBVH.bvhConverter_writeBVH.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.c_int] + self.libBVH.bvhConverter_writeBVH.restype = ctypes.c_int + success = self.libBVH.bvhConverter_writeBVH(arg1,1,0) #Just write the Hierarchy part of the BVH file + + if (success): + numberOfFrames = len(allMotionData) + f = open(filename, 'a') + f.write("MOTION\n"); + f.write("Frames: %u\n"%numberOfFrames); + f.write("Frame Time: %0.8f\n"%(float(1/24)) ); + for fID in range(0,numberOfFrames): + i=0 + for mID in allMotionData[fID]: + if (i>0): + f.write(' ') + if (mID==0.0): + f.write("0") + else: + f.write("%0.4f" % mID) + i=i+1 + f.write('\n') + f.close() + + #-------------------------------------------- + os.system("sed -i 's/rcollar/rCollar/g' out.bvh") + os.system("sed -i 's/rshoulder/rShldr/g' out.bvh") + os.system("sed -i 's/relbow/rForeArm/g' out.bvh") + os.system("sed -i 's/rhand/rHand/g' out.bvh") + #-------------------------------------------- + os.system("sed -i 's/lcollar/lCollar/g' out.bvh") + os.system("sed -i 's/lshoulder/lShldr/g' out.bvh") + os.system("sed -i 's/lelbow/lForeArm/g' out.bvh") + os.system("sed -i 's/lhand/lHand/g' out.bvh") + #-------------------------------------------- + os.system("sed -i 's/rhip/rThigh/g' out.bvh") + os.system("sed -i 's/rknee/rShin/g' out.bvh") + os.system("sed -i 's/rfoot/rFoot/g' out.bvh") + #------------------------------------------------------ + os.system("sed -i 's/lhip/lThigh/g' out.bvh") + os.system("sed -i 's/lknee/lShin/g' out.bvh") + os.system("sed -i 's/lfoot/lFoot/g' out.bvh") + + + return success + #-------------------------------------------------------- + def setMotionValueOfFrame(self, frameID:int, jointID:int, value:float): + self.stage("setMotionValueOfFrame") + self.libBVH.bvhConverter_setMotionValueOfFrame.argtypes = [ctypes.c_int,ctypes.c_int,ctypes.c_float] + self.libBVH.bvhConverter_setMotionValueOfFrame.restype = ctypes.c_int + success = self.libBVH.bvhConverter_setMotionValueOfFrame(frameID,jointID,value) + return success + #-------------------------------------------------------- + def getNumberOfMotionValuesPerFrame(self): + self.stage("getNumberOfMotionValuesPerFrame") + self.libBVH.bvhConverter_getNumberOfMotionValuesPerFrame.argtypes = [] + self.libBVH.bvhConverter_getNumberOfMotionValuesPerFrame.restype = ctypes.c_int + jointID = self.libBVH.bvhConverter_getNumberOfMotionValuesPerFrame() + return jointID + #-------------------------------------------------------- + def getNumberOfJoints(self): + self.stage("getNumberOfJoints") + self.libBVH.bvhConverter_getNumberOfJoints.argtypes = [] + self.libBVH.bvhConverter_getNumberOfJoints.restype = ctypes.c_int + jointID = self.libBVH.bvhConverter_getNumberOfJoints() + return jointID + #-------------------------------------------------------- + def getJointID(self, jointName:str): + self.stage("getJointID") + arg1 = jointName.encode('utf-8') + self.libBVH.bvhConverter_getJointNameJointID.argtypes = [ctypes.c_char_p] + self.libBVH.bvhConverter_getJointNameJointID.restype = ctypes.c_int + jointID = self.libBVH.bvhConverter_getJointNameJointID(arg1) + return jointID + #-------------------------------------------------------- + def getJointList(self): + self.stage("getJointList") + jointList = list() + for jointID in range(0,self.numberOfJoints): + jointList.append(self.getJointName(jointID)) + return jointList + #-------------------------------------------------------- + def getJointRotationsForFrame(self, jointID:int, frameID:int): + self.stage("getJointRotationsForFrame") + if (self.isJointEndSite(jointID)==1): + xRot=0.0 + yRot=0.0 + zRot=0.0 + else: + #-------------------------------------------------------- + self.libBVH.bvhConverter_getBVHJointRotationXForFrame.argtypes = [ctypes.c_int, ctypes.c_int] + self.libBVH.bvhConverter_getBVHJointRotationXForFrame.restype = ctypes.c_float + xRot = self.libBVH.bvhConverter_getBVHJointRotationXForFrame(frameID,jointID) + #-------------------------------------------------------- + self.libBVH.bvhConverter_getBVHJointRotationYForFrame.argtypes = [ctypes.c_int, ctypes.c_int] + self.libBVH.bvhConverter_getBVHJointRotationYForFrame.restype = ctypes.c_float + yRot = self.libBVH.bvhConverter_getBVHJointRotationYForFrame(frameID,jointID) + #-------------------------------------------------------- + self.libBVH.bvhConverter_getBVHJointRotationZForFrame.argtypes = [ctypes.c_int, ctypes.c_int] + self.libBVH.bvhConverter_getBVHJointRotationZForFrame.restype = ctypes.c_float + zRot = self.libBVH.bvhConverter_getBVHJointRotationZForFrame(frameID,jointID) + #-------------------------------------------------------- + return xRot,yRot,zRot + #-------------------------------------------------------- + def getJoint3D(self, jointID:int): + self.stage("getJoint3D") + self.libBVH.bvhConverter_get3DX.argtypes = [ctypes.c_int] + self.libBVH.bvhConverter_get3DX.restype = ctypes.c_float + x3D = self.libBVH.bvhConverter_get3DX(jointID) + + self.libBVH.bvhConverter_get3DY.argtypes = [ctypes.c_int] + self.libBVH.bvhConverter_get3DY.restype = ctypes.c_float + y3D = self.libBVH.bvhConverter_get3DY(jointID) + + self.libBVH.bvhConverter_get3DZ.argtypes = [ctypes.c_int] + self.libBVH.bvhConverter_get3DZ.restype = ctypes.c_float + z3D = self.libBVH.bvhConverter_get3DZ(jointID) + + return x3D,y3D,z3D + #-------------------------------------------------------- + def getJoint2D(self, jointID:int): + self.stage("getJoint2D") + self.libBVH.bvhConverter_get2DX.argtypes = [ctypes.c_int] + self.libBVH.bvhConverter_get2DX.restype = ctypes.c_float + x2D = self.libBVH.bvhConverter_get2DX(jointID) + + self.libBVH.bvhConverter_get2DY.argtypes = [ctypes.c_int] + self.libBVH.bvhConverter_get2DY.restype = ctypes.c_float + y2D = self.libBVH.bvhConverter_get2DY(jointID) + + #Flip X + if (x2D!=0.0) or (y2D!=0.0): + x2D = 1.0 - x2D + + return x2D,y2D + #-------------------------------------------------------- + def getJoint3DUsingJointName(self, jointName:str): + return self.getJoint3D(self.getJointID(jointName)) + #-------------------------------------------------------- + def getJoint2DUsingJointName(self, jointName:str): + return self.getJoint2D(self.getJointID(jointName)) + #-------------------------------------------------------- + def processFrame(self, frameID:int): + self.stage("processFrame") + self.libBVH.bvhConverter_processFrame.argtypes = [ctypes.c_int] + self.libBVH.bvhConverter_processFrame.restype = ctypes.c_int + success = self.libBVH.bvhConverter_processFrame(frameID) + return success + #-------------------------------------------------------- + def modify(self,arguments:dict,frameID=0): + self.stage("modify") + #print("BVH modify called with : ",arguments) + if (not arguments): + print("BVH modify called without arguments") + return 0 + #Arguments is a dict with a lot of key/value pairs we want to transmit to the C code + labelsCStr,valuesArray,argc = splitDictionaryInLabelsAndFloats(arguments) + self.libBVH.bvhConverter_modifyAtomic.argtypes = [ctypes.POINTER(ctypes.c_char_p), ctypes.POINTER(ctypes.c_float), ctypes.c_int, ctypes.c_int] + success = self.libBVH.bvhConverter_modifyAtomic(labelsCStr,valuesArray,argc,frameID) + return success + #-------------------------------------------------------- + def configureRenderer(self,arguments:dict): + #Arguments is a dict with a lot of key/value pairs we want to transmit to the C code + labelsCStr,valuesArray,argc = splitDictionaryInLabelsAndFloats(arguments) + self.libBVH.bvhConverter_rendererConfigurationAtomic.argtypes = [ctypes.POINTER(ctypes.c_char_p), ctypes.POINTER(ctypes.c_float), ctypes.c_int] + self.libBVH.bvhConverter_rendererConfigurationAtomic(labelsCStr,valuesArray,argc) + #-------------------------------------------------------- + def configureRendererFromFile(self,cameraCalibrationFile:str): + #from calibration import readCalibrationFromFile + self.calib = readCalibrationFromFile(cameraCalibrationFile) + if (self.calib): + print("We found a calibration in file ",cameraCalibrationFile) + print("calib : ",self.calib) + self.configureRenderer(self.calib) + #-------------------------------------------------------- + def get2DAnd3DAndBVHDictsForFrame(self,frameID=0): + self.stage("get2DAnd3DAndBVHDictsForFrame ") + #Arguments is a dict with a lot of key/value pairs we want to transmit to the C code + self.processFrame(frameID=frameID) + + #Our output + #--------------- + data2D = dict() + data3D = dict() + dataBVH = dict() + #--------------- + + for jointID in range(0,self.numberOfJoints): + #------------------------------------------------------- + #print("joint ID = ",jointID) + #------------------------------------------- + jointName = self.getJointName(jointID).lower() + #------------------------------------------- + #print("Getting 3D") + x3D,y3D,z3D = self.getJoint3D(jointID) + data3D["3DX_"+jointName]=float(x3D) + data3D["3DY_"+jointName]=float(y3D) + data3D["3DZ_"+jointName]=float(z3D) + #------------------------------------------- + #print("Getting 2D") + x2D,y2D = self.getJoint2D(jointID) + data2D["2DX_"+jointName]=float(x2D) + data2D["2DY_"+jointName]=float(y2D) + #------------------------------------------- + #print("Getting Joint Rotations") + if (self.isJointEndSite(jointID)==0): #Do not try to recover rotations for EndSites (they dont have rotations) + xRot,yRot,zRot = self.getJointRotationsForFrame(jointID,frameID) + if (jointID==0): + dataBVH[jointName+"_Xposition"]=float(x3D) + dataBVH[jointName+"_Yposition"]=float(y3D) + dataBVH[jointName+"_Zposition"]=float(z3D) + dataBVH[jointName+"_Xrotation"]=float(xRot) + dataBVH[jointName+"_Yrotation"]=float(yRot) + dataBVH[jointName+"_Zrotation"]=float(zRot) + #------------------------------------------------------- + return data2D,data3D,dataBVH + #-------------------------------------------------------- + def fineTuneToMatch(self,bodyPart:str,target:dict,frameID=0,iterations=20,epochs=30,lr=0.01,fSampling=30.0,fCutoff=5.0,langevinDynamics=0.0): + self.stage("fineTuneToMatch ") + bodyPartCStr = bytes(bodyPart, 'utf-8') + + #Arguments is a dict with a lot of key/value pairs we want to transmit to the C code + labelsCStr,valuesArray,argc = splitDictionaryInLabelsAndFloats(target) + self.libBVH.bvhConverter_IKFineTune.argtypes = [ctypes.c_char_p, ctypes.POINTER(ctypes.c_char_p), ctypes.POINTER(ctypes.c_float), ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_float, ctypes.c_float, ctypes.c_float,ctypes.c_float] + self.libBVH.bvhConverter_IKFineTune.restype = ctypes.c_float + accuracy2D = self.libBVH.bvhConverter_IKFineTune(bodyPartCStr,labelsCStr,valuesArray,argc,frameID,iterations,epochs,lr,fSampling,fCutoff,langevinDynamics) + print("HCD results for ",iterations," iterations ~> %0.2f pixels!" % accuracy2D) + self.lastMAEErrorInPixels = accuracy2D + return self.get2DAnd3DAndBVHDictsForFrame(frameID=frameID) + + #return dict() + #-------------------------------------------------------- + + def smooth(self,frameID=0,fSampling=30.0,fCutoff=5.0): + self.stage("smooth ") + #This call assumes that is called after subsequent(?) calls to fineTuneToMatch that have transmitted the BVH state..! + self.libBVH.bvhConverter_smooth.argtypes = [ctypes.c_int, ctypes.c_float, ctypes.c_float] + self.libBVH.bvhConverter_smooth.restype = ctypes.c_int + result = self.libBVH.bvhConverter_smooth(frameID,fSampling,fCutoff) + return result==1 + + #return dict() + #-------------------------------------------------------- + + + +if __name__== "__main__": + bvhFile = BVH(bvhPath="./headerWithHeadAndOneMotion.bvh",forceLibUpdate=True) + + print("File has ",bvhFile.numberOfJoints," joints") + + print(" Joint List : ",bvhFile.getJointList()) + print(" Joint Parent List : ",bvhFile.getJointParentList()) + + modifications = dict() + modifications["hip_Xposition"]=100.0 + modifications["hip_Yposition"]=100.0 + modifications["hip_Zposition"]=-400.0 + modifications["hip_Xrotation"]=1.0 + modifications["hip_Yrotation"]=2.0 + modifications["hip_Zrotation"]=4.0 + bvhFile.modify(modifications) + jointName = "neck" + print("Joint ID for ",jointName," is ",bvhFile.getJointID(jointName)) + + frameID=0 + + for i in range(0,10): + modifications["hip_Xposition"]=100.0 + i * 10.0 + bvhFile.modify(modifications) + bvhFile.processFrame(frameID) + x3D,y3D,z3D = bvhFile.getJoint3DUsingJointName(jointName) + print(" I=",i," Joint=",jointName," 3D values for frame ",frameID," are ",x3D,",",y3D,",",z3D," ") + + x2D,y2D = bvhFile.getJoint2DUsingJointName(jointName) + print(" Joint ",jointName," 2D values for frame ",frameID," are ",x2D,",",y2D) + + target2D = dict() + target2D["2dx_head"]=0.4722689390182495 + target2D["2dy_head"]=0.1971915066242218 + target2D["visible_head"]=0.9999899864196777 + target2D["2dx_head_leye_0"]=0.46880775690078735 + target2D["2dy_head_leye_0"]=0.1777663230895996 + target2D["visible_head_leye_0"]=0.0 + target2D["2dx_endsite_eye.l"]=0.4609638452529907 + target2D["2dy_endsite_eye.l"]=0.18265053629875183 + target2D["visible_endsite_eye.l"]=0.9999715089797974 + target2D["2dx_head_leye_3"]=0.4599621295928955 + target2D["2dy_head_leye_3"]=0.17712406814098358 + target2D["visible_head_leye_3"]=0.0 + target2D["2dx_head_reye_3"]=0.4780046343803406 + target2D["2dy_head_reye_3"]=0.17705687880516052 + target2D["visible_head_reye_3"]=0.0 + target2D["2dx_endsite_eye.r"]=0.47662675380706787 + target2D["2dy_endsite_eye.r"]=0.17989128828048706 + target2D["visible_endsite_eye.r"]=0.9999359846115112 + target2D["2dx_head_reye_0"]=0.4844837188720703 + target2D["2dy_head_reye_0"]=0.17520418763160706 + target2D["visible_head_reye_0"]=0.0 + target2D["2dx_lear"]=0.4520418643951416 + target2D["2dy_lear"]=0.1892796754837036 + target2D["visible_lear"]=0.9999462366104126 + target2D["2dx_rear"]=0.47904515266418457 + target2D["2dy_rear"]=0.18252810835838318 + target2D["visible_rear"]=0.999884843826294 + target2D["2dx_head_outmouth_0"]=0.4792625308036804 + target2D["2dy_head_outmouth_0"]=0.20948739349842072 + target2D["visible_head_outmouth_0"]=0.0 + target2D["2dx_head_outmouth_6"]=0.4670618176460266 + target2D["2dy_head_outmouth_6"]=0.2107415348291397 + target2D["visible_head_outmouth_6"]=0.0 + target2D["2dx_lshoulder"]=0.43696290254592896 + target2D["2dy_lshoulder"]=0.2820419669151306 + target2D["visible_lshoulder"]=0.9999864101409912 + target2D["2dx_rshoulder"]=0.5070507228374481 + target2D["2dy_rshoulder"]=0.2563856244087219 + target2D["visible_rshoulder"]=0.9998794794082642 + target2D["2dx_lelbow"]=0.4353405833244324 + target2D["2dy_lelbow"]=0.3725816607475281 + target2D["visible_lelbow"]=0.9570146799087524 + target2D["2dx_relbow"]=0.5561909377574921 + target2D["2dy_relbow"]=0.323696106672287 + target2D["visible_relbow"]=0.974626362323761 + target2D["2dx_lhand"]=0.41237425804138184 + target2D["2dy_lhand"]=0.4415815770626068 + target2D["visible_lhand"]=0.9751281142234802 + target2D["2dx_rhand"]=0.6100260317325592 + target2D["2dy_rhand"]=0.36322692036628723 + target2D["visible_rhand"]=0.9656738638877869 + target2D["2dx_left_hand_pinky_4"]=0.40250515937805176 + target2D["2dy_left_hand_pinky_4"]=0.468822181224823 + target2D["visible_left_hand_pinky_4"]=0.9458165764808655 + target2D["2dx_right_hand_pinky_4"]=0.6301354765892029 + target2D["2dy_right_hand_pinky_4"]=0.36382848024368286 + target2D["visible_right_hand_pinky_4"]=0.9336408376693726 + target2D["2dx_left_hand_index_4"]=0.4018949866294861 + target2D["2dy_left_hand_index_4"]=0.46844327449798584 + target2D["visible_left_hand_index_4"]=0.9523322582244873 + target2D["2dx_right_hand_index_4"]=0.6305072009563446 + target2D["2dy_right_hand_index_4"]=0.361262708902359 + target2D["visible_right_hand_index_4"]=0.9438664317131042 + target2D["2dx_left_hand_thumb_4"]=0.40644168853759766 + target2D["2dy_left_hand_thumb_4"]=0.4600275158882141 + target2D["visible_left_hand_thumb_4"]=0.9461172819137573 + target2D["2dx_right_hand_thumb_4"]=0.6229645609855652 + target2D["2dy_right_hand_thumb_4"]=0.3644818663597107 + target2D["visible_right_hand_thumb_4"]=0.9423035979270935 + target2D["2dx_lhip"]=0.4714365005493164 + target2D["2dy_lhip"]=0.49608662724494934 + target2D["visible_lhip"]=0.9997338652610779 + target2D["2dx_rhip"]=0.5158871114253998 + target2D["2dy_rhip"]=0.48727133870124817 + target2D["visible_rhip"]=0.9996464252471924 + target2D["2dx_lknee"]=0.46627652645111084 + target2D["2dy_lknee"]=0.6712287068367004 + target2D["visible_lknee"]=0.9964113831520081 + target2D["2dx_rknee"]=0.5255001187324524 + target2D["2dy_rknee"]=0.6690568923950195 + target2D["visible_rknee"]=0.997963547706604 + target2D["2dx_lfoot"]=0.45652568340301514 + target2D["2dy_lfoot"]=0.799410879611969 + target2D["visible_lfoot"]=0.9943563342094421 + target2D["2dx_rfoot"]=0.5572476387023926 + target2D["2dy_rfoot"]=0.8126811981201172 + target2D["visible_rfoot"]=0.9981330037117004 + target2D["2dx_lheel"]=0.46518951654434204 + target2D["2dy_lheel"]=0.8143126964569092 + target2D["visible_lheel"]=0.9416220784187317 + target2D["2dx_rheel"]=0.5635095536708832 + target2D["2dy_rheel"]=0.8311944007873535 + target2D["visible_rheel"]=0.9331190586090088 + target2D["2dx_endsite_toe1-2.l"]=0.44505226612091064 + target2D["2dy_endsite_toe1-2.l"]=0.8585301637649536 + target2D["visible_endsite_toe1-2.l"]=0.9915184378623962 + target2D["2dx_endsite_toe1-2.r"]=0.5560439825057983 + target2D["2dy_endsite_toe1-2.r"]=0.8789670467376709 + target2D["visible_endsite_toe1-2.r"]=0.9957401752471924 + target2D["2dx_head_outmouth_3"]=0.4757194519042969 + target2D["2dy_head_outmouth_3"]=0.2063373625278473 + target2D["visible_head_outmouth_3"]=0.0 + target2D["2dx_head_nosebone_3"]=0.47762298583984375 + target2D["2dy_head_nosebone_3"]=0.19625969231128693 + target2D["visible_head_nosebone_3"]=0.0 + target2D["2dx_head_nostrills_2"]=0.4758298993110657 + target2D["2dy_head_nostrills_2"]=0.19925406575202942 + target2D["visible_head_nostrills_2"]=0.0 + target2D["2dx_head_nosebone_2"]=0.47625207901000977 + target2D["2dy_head_nosebone_2"]=0.1822354793548584 + target2D["visible_head_nosebone_2"]=0.0 + target2D["2dx_head_nosebone_1"]=0.47568726539611816 + target2D["2dy_head_nosebone_1"]=0.17861950397491455 + target2D["visible_head_nosebone_1"]=0.0 + target2D["2dx_head_inmouth_2"]=0.47532206773757935 + target2D["2dy_head_inmouth_2"]=0.20903927087783813 + target2D["visible_head_inmouth_2"]=0.0 + target2D["2dx_head_inmouth_6"]=0.4751202464103699 + target2D["2dy_head_inmouth_6"]=0.21265165507793427 + target2D["visible_head_inmouth_6"]=0.0 + target2D["2dx_head_outmouth_9"]=0.47498154640197754 + target2D["2dy_head_outmouth_9"]=0.21609455347061157 + target2D["visible_head_outmouth_9"]=0.0 + target2D["2dx_head_outmouth_2"]=0.48239976167678833 + target2D["2dy_head_outmouth_2"]=0.17039550840854645 + target2D["visible_head_outmouth_2"]=0.0 + target2D["2dx_head_outmouth_1"]=0.47901231050491333 + target2D["2dy_head_outmouth_1"]=0.2075507491827011 + target2D["visible_head_outmouth_1"]=0.0 + target2D["2dx_head_inmouth_1"]=0.47740042209625244 + target2D["2dy_head_inmouth_1"]=0.20893418788909912 + target2D["visible_head_inmouth_1"]=0.0 + target2D["2dx_head_reyebrow_4"]=0.4785383939743042 + target2D["2dy_head_reyebrow_4"]=0.1684335619211197 + target2D["visible_head_reyebrow_4"]=0.0 + target2D["2dx_head_reyebrow_1"]=0.4863806962966919 + target2D["2dy_head_reyebrow_1"]=0.16322830319404602 + target2D["visible_head_reyebrow_1"]=0.0 + target2D["2dx_head_reyebrow_3"]=0.48255395889282227 + target2D["2dy_head_reyebrow_3"]=0.16279345750808716 + target2D["visible_head_reyebrow_3"]=0.0 + target2D["2dx_head_reyebrow_0"]=0.4868544340133667 + target2D["2dy_head_reyebrow_0"]=0.16618424654006958 + target2D["visible_head_reyebrow_0"]=0.0 + target2D["2dx_head_inmouth_0"]=0.4783351421356201 + target2D["2dy_head_inmouth_0"]=0.20919831097126007 + target2D["visible_head_inmouth_0"]=0.0 + target2D["2dx_head_outmouth_10"]=0.47662580013275146 + target2D["2dy_head_outmouth_10"]=0.2154039442539215 + target2D["visible_head_outmouth_10"]=0.0 + target2D["2dx_head_outmouth_11"]=0.47859442234039307 + target2D["2dy_head_outmouth_11"]=0.2121586948633194 + target2D["visible_head_outmouth_11"]=0.0 + target2D["2dx_head_nostrills_1"]=0.4774726629257202 + target2D["2dy_head_nostrills_1"]=0.19906578958034515 + target2D["visible_head_nostrills_1"]=0.0 + target2D["2dx_head_nostrills_0"]=0.47901052236557007 + target2D["2dy_head_nostrills_0"]=0.19807150959968567 + target2D["visible_head_nostrills_0"]=0.0 + target2D["2dx_head_reyebrow_2"]=0.4849526286125183 + target2D["2dy_head_reyebrow_2"]=0.1620578020811081 + target2D["visible_head_reyebrow_2"]=0.0 + target2D["2dx_head_rchin_1"]=0.4867928624153137 + target2D["2dy_head_rchin_1"]=0.1828579306602478 + target2D["visible_head_rchin_1"]=0.0 + target2D["2dx_head_rchin_0"]=0.4863375425338745 + target2D["2dy_head_rchin_0"]=0.17663004994392395 + target2D["visible_head_rchin_0"]=0.0 + target2D["2dx_head_reye_4"]=0.4802663326263428 + target2D["2dy_head_reye_4"]=0.1782480776309967 + target2D["visible_head_reye_4"]=0.0 + target2D["2dx_head_rchin_2"]=0.4862361550331116 + target2D["2dy_head_rchin_2"]=0.195520281791687 + target2D["visible_head_rchin_2"]=0.0 + target2D["2dx_head_rchin_7"]=0.4762313961982727 + target2D["2dy_head_rchin_7"]=0.22934123873710632 + target2D["visible_head_rchin_7"]=0.0 + target2D["2dx_head_chin"]=0.473285973072052 + target2D["2dy_head_chin"]=0.2309272140264511 + target2D["visible_head_chin"]=0.0 + target2D["2dx_head_reye_2"]=0.4805048704147339 + target2D["2dy_head_reye_2"]=0.1739354431629181 + target2D["visible_head_reye_2"]=0.0 + target2D["2dx_head_reye_1"]=0.483254075050354 + target2D["2dy_head_reye_1"]=0.17367364466190338 + target2D["visible_head_reye_1"]=0.0 + target2D["2dx_head_nosebone_0"]=0.47514963150024414 + target2D["2dy_head_nosebone_0"]=0.1740616112947464 + target2D["visible_head_nosebone_0"]=0.0 + target2D["2dx_head_rchin_5"]=0.47995781898498535 + target2D["2dy_head_rchin_5"]=0.2206612378358841 + target2D["visible_head_rchin_5"]=0.0 + target2D["2dx_head_rchin_6"]=0.478015661239624 + target2D["2dy_head_rchin_6"]=0.22674590349197388 + target2D["visible_head_rchin_6"]=0.0 + target2D["2dx_head_inmouth_7"]=0.4774059057235718 + target2D["2dy_head_inmouth_7"]=0.21126437187194824 + target2D["visible_head_inmouth_7"]=0.0 + target2D["2dx_head_rchin_3"]=0.48438382148742676 + target2D["2dy_head_rchin_3"]=0.20477566123008728 + target2D["visible_head_rchin_3"]=0.0 + target2D["2dx_head_rchin_4"]=0.4815486669540405 + target2D["2dy_head_rchin_4"]=0.21430769562721252 + target2D["visible_head_rchin_4"]=0.0 + target2D["2dx_head_leye_4"]=0.4646076560020447 + target2D["2dy_head_leye_4"]=0.18126901984214783 + target2D["visible_head_leye_4"]=0.0 + target2D["2dx_head_leye_5"]=0.4679994583129883 + target2D["2dy_head_leye_5"]=0.17986340820789337 + target2D["visible_head_leye_5"]=0.0 + target2D["2dx_head_lchin_0"]=0.45561397075653076 + target2D["2dy_head_lchin_0"]=0.17862515151500702 + target2D["visible_head_lchin_0"]=0.0 + target2D["2dx_head_outmouth_4"]=0.47371816635131836 + target2D["2dy_head_outmouth_4"]=0.2061852067708969 + target2D["visible_head_outmouth_4"]=0.0 + target2D["2dx_head_inmouth_3"]=0.4718068242073059 + target2D["2dy_head_inmouth_3"]=0.20968028903007507 + target2D["visible_head_inmouth_3"]=0.0 + target2D["2dx_head_leyebrow_0"]=0.45764458179473877 + target2D["2dy_head_leyebrow_0"]=0.16966667771339417 + target2D["visible_head_leyebrow_0"]=0.0 + target2D["2dx_head_leyebrow_4"]=0.47118234634399414 + target2D["2dy_head_leyebrow_4"]=0.16888980567455292 + target2D["visible_head_leyebrow_4"]=0.0 + target2D["2dx_head_leyebrow_1"]=0.4586902856826782 + target2D["2dy_head_leyebrow_1"]=0.16491857171058655 + target2D["visible_head_leyebrow_1"]=0.0 + target2D["2dx_head_leyebrow_3"]=0.466333270072937 + target2D["2dy_head_leyebrow_3"]=0.16365274786949158 + target2D["visible_head_leyebrow_3"]=0.0 + target2D["2dx_head_inmouth_4"]=0.46794217824935913 + target2D["2dy_head_inmouth_4"]=0.21027947962284088 + target2D["visible_head_inmouth_4"]=0.0 + target2D["2dx_head_outmouth_8"]=0.4729023575782776 + target2D["2dy_head_outmouth_8"]=0.21605923771858215 + target2D["visible_head_outmouth_8"]=0.0 + target2D["2dx_head_nostrills_3"]=0.4738311171531677 + target2D["2dy_head_nostrills_3"]=0.19950126111507416 + target2D["visible_head_nostrills_3"]=0.0 + target2D["2dx_head_nostrills_4"]=0.47070103883743286 + target2D["2dy_head_nostrills_4"]=0.19887711107730865 + target2D["visible_head_nostrills_4"]=0.0 + target2D["2dx_head_leyebrow_2"]=0.46217411756515503 + target2D["2dy_head_leyebrow_2"]=0.16334131360054016 + target2D["visible_head_leyebrow_2"]=0.0 + target2D["2dx_head_lchin_1"]=0.4578735828399658 + target2D["2dy_head_lchin_1"]=0.18603739142417908 + target2D["visible_head_lchin_1"]=0.0 + target2D["2dx_head_lchin_2"]=0.45422083139419556 + target2D["2dy_head_lchin_2"]=0.19913220405578613 + target2D["visible_head_lchin_2"]=0.0 + target2D["2dx_head_lchin_7"]=0.4697527289390564 + target2D["2dy_head_lchin_7"]=0.2310400754213333 + target2D["visible_head_lchin_7"]=0.0 + target2D["2dx_head_leye_1"]=0.46723294258117676 + target2D["2dy_head_leye_1"]=0.1757526397705078 + target2D["visible_head_leye_1"]=0.0 + target2D["2dx_head_leye_2"]=0.4639958143234253 + target2D["2dy_head_leye_2"]=0.1749143898487091 + target2D["visible_head_leye_2"]=0.0 + target2D["2dx_head_lchin_5"]=0.4636954665184021 + target2D["2dy_head_lchin_5"]=0.22440548241138458 + target2D["visible_head_lchin_5"]=0.0 + target2D["2dx_head_lchin_6"]=0.46653610467910767 + target2D["2dy_head_lchin_6"]=0.22966337203979492 + target2D["visible_head_lchin_6"]=0.0 + target2D["2dx_head_inmouth_5"]=0.4714820384979248 + target2D["2dy_head_inmouth_5"]=0.21212585270404816 + target2D["visible_head_inmouth_5"]=0.0 + target2D["2dx_head_outmouth_7"]=0.4708884358406067 + target2D["2dy_head_outmouth_7"]=0.21508848667144775 + target2D["visible_head_outmouth_7"]=0.0 + target2D["2dx_head_lchin_3"]=0.4568250775337219 + target2D["2dy_head_lchin_3"]=0.20863905549049377 + target2D["visible_head_lchin_3"]=0.0 + target2D["2dx_head_lchin_4"]=0.4621039032936096 + target2D["2dy_head_lchin_4"]=0.217911496758461 + target2D["visible_head_lchin_4"]=0.0 + target2D["2dx_neck"]=0.47200681269168854 + target2D["2dy_neck"]=0.26921379566192627 + target2D["visible_neck"]=0.9999329447746277 + target2D["2dx_hip"]=0.4936618059873581 + target2D["2dy_hip"]=0.49167898297309875 + target2D["visible_hip"]=0.9996901452541351 + + print("fineTuneToMatch") + result = bvhFile.fineTuneToMatch("body",target2D,frameID=0,iterations=10,epochs=30) + #print("Result ",result) + diff --git a/src/python/mnet4/BVH/bvhLibrary.h b/src/python/mnet4/BVH/bvhLibrary.h new file mode 100644 index 0000000..ccf3dcf --- /dev/null +++ b/src/python/mnet4/BVH/bvhLibrary.h @@ -0,0 +1,64 @@ +/** @file bvhLibrary.h + * @brief BVH file parser part of https://github.com/AmmarkoV/RGBDAcquisition/tree/master/opengl_acquisition_shared_library/opengl_depth_and_color_renderer + This is the central header for the BVH library in order to compile it not as an executable file but as a real library! + To enable building as a library please compile the code with -DBVH_USE_AS_A_LIBRARY so that there is no main function included! + Don't forget, to check generated symbols : nm -gD libBVHConverter.so + * @author Ammar Qammaz (AmmarkoV) + */ +#ifndef BVH_STANDALONE_LIBRARY_H_INCLUDED +#define BVH_STANDALONE_LIBRARY_H_INCLUDED + +#ifdef __cplusplus +extern "C" +{ +#endif + + +int bvhConverter_writeBVH(char * filename,int writeHierarchy,int writeMotion); +int bvhConverter_getMotionValueOfFrame(int fID,int mID); +int bvhConverter_setMotionValueOfFrame(int fID,int mID,float value); + +int bvhConverter_loadAtomic(const char *path); +int bvhConverter_unloadAtomic(); + +int bvhConverter_scale(float scaleRatio); + +int bvhConverter_rendererConfigurationAtomic(const char ** labels,const float * values,int numberOfElements); +int bvhConverter_processFrame(int frameID); +int bvhConverter_getJointNameJointID(const char * jointName); + +int bvhConverter_getNumberOfMotionValuesPerFrame(); +int bvhConverter_getNumberOfJoints(); +const char * bvhConverter_getJointNameFromJointID(int jointID); + +int bvhConverter_getJointParent(int jointID); + +float bvhConverter_get3DX(int jointID); +float bvhConverter_get3DY(int jointID); +float bvhConverter_get3DZ(int jointID); + +float bvhConverter_get2DX(int jointID); +float bvhConverter_get2DY(int jointID); + +int bvhConverter_isJointEndSite(int jointID); + +float bvhConverter_getBVHJointRotationXForFrame(int frameID,int jointID); +float bvhConverter_getBVHJointRotationYForFrame(int frameID,int jointID); +float bvhConverter_getBVHJointRotationZForFrame(int frameID,int jointID); + +int bvhConverter_modifySingleAtomic(const char * label,const float value,int frameID); +int bvhConverter_modifyAtomic(const char ** labels,const float * values,int numberOfElements,int frameID); + +int bvhConverter_IKSetup(const char * bodyPart,const char ** labels,const float * values,int numberOfElements,int frameID); +float bvhConverter_IKFineTune(const char * bodyPart,const char ** labels,const float * values,int numberOfElements,int frameID,int iterations,int epochs,float lr,float fSampling,float fCutoff,float langevinDynamics); + + +int bvhConverter_smooth(int frameID,float fSampling,float fCutoff); + +int bvhConverter(int argc,const char **argv); + +#ifdef __cplusplus +} +#endif + +#endif // BVH_STANDALONE_LIBRARY_H_INCLUDED diff --git a/src/python/mnet4/BVH/bvhLibrary.py b/src/python/mnet4/BVH/bvhLibrary.py new file mode 100644 index 0000000..f79a048 --- /dev/null +++ b/src/python/mnet4/BVH/bvhLibrary.py @@ -0,0 +1,238 @@ +#!/usr/bin/python3 + +import ctypes +import os +import sys +from ctypes import * +from os.path import exists + + +#-------------------------------------------------------- +def readCSV(filename): + result=dict() + import csv + with open(filename,newline='') as csvfile: + readerIn = csv.reader(csvfile,delimiter=',',quotechar='"') + for rowIn in readerIn: + numberOfColumns=len(rowIn) + labels = list(rowIn[i] for i in range(0,numberOfColumns) ) + if (labels[0]!=''): + newList = list() + newList.append(labels[1]) + newList.append(labels[2]) + result[labels[0]]=newList + return result +#-------------------------------------------------------- +def gatherAllBVHFiles(directoryPath): + results = list() + for f in os.scandir(directoryPath): + if f.is_file(): + #print("f.path=",f.path) + if (f.path.find(".bvh")!=-1): + #print("Adding BVH file ",f.path) + results.append(f.path) + return results; +#-------------------------------------------------------- +def gatherAllBVHDirectories(directoryPath): + results = list() + for f in os.scandir(directoryPath): + if f.is_dir(): + #print("Adding files in ",f.path) + results = results + gatherAllBVHFiles(f.path) + return results +#-------------------------------------------------------- +def writeListToFile(theList,theFilename): + file = open(theFilename, "w") + for theList in allBVHFiles: + file.write(theList + "\n") + file.close() +#-------------------------------------------------------- +def appendJSONEnding(theFilename): + file = open(theFilename, "a") # append mode + file.write(" ]\n } \n") + file.close() +#-------------------------------------------------------- + + + +def loadLibrary(filename): + if not exists(filename): + print("Could not find BVH Library (",filename,"), compiling a fresh one..!") + print("Current directory was (",os.getcwd(),") ") + directory=os.path.dirname(os.path.abspath(filename)) + os.system(directory+"/makeLibrary.sh") + if not exists(filename): + print("Could not make BVH Library, terminating") + sys.exit(0) + libBVH = CDLL(filename) + #call C function to check connection + libBVH.connect() + libBVH.bvhConverter.restype = c_int + libBVH.bvhConverter.argtypes = c_int,POINTER(c_char_p) + return libBVH +#-------------------------------------------------------- +def bvhConvert(libBVH,arguments): + argumentBytes = [] + for i in range(len(arguments)): + argumentBytes.append(bytes(arguments[i], 'utf-8')) + argv = (ctypes.c_char_p * len(argumentBytes))() + argv[:] = argumentBytes + argc=len(argumentBytes) + libBVH.bvhConverter(argc,argv) +#-------------------------------------------------------- + + + + + + + + + + + +if __name__== "__main__": + #python main : + pythonFlags=list() + #Add any arguments given in the python script directly! + if (len(sys.argv)>1): + print('Supplied argument List:', str(sys.argv)) + for i in range(0, len(sys.argv)): + pythonFlags.append(sys.argv[i]) + if (sys.argv[i]=="--update"): + print('Deleting previous libBVHConverter.so to force update!\n') + os.system("rm libBVHConverter.so") + + + + + libBVH = loadLibrary("./libBVHConverter.so") + + + + datasetDirectory = "/home/ammar/Documents/Programming/DNNTracker/DNNTracker/dataset/MotionCapture" + outputDirectory = os.getcwd() + allBVHFiles = gatherAllBVHDirectories(datasetDirectory) + #Keep list for debug.. + writeListToFile(allBVHFiles,"listOfBVHFiles.txt") + print("Output Directory = ",outputDirectory) + print("Found ",len(allBVHFiles)," BVH files") + + + #Package Annotations as JSON + print("Packaging annotations in JSON format") + annotations=readCSV(datasetDirectory+"/cmu-mocap-annotations.csv") + import json + with open("annotations.json", "w") as outfile: + json.dump(annotations, outfile, indent=4) + + + mode="json" # json or csv + extension="."+mode + bodyPart="upperbody" + os.system("rm 2d_"+bodyPart+extension) + os.system("rm 3d_"+bodyPart+extension) + os.system("rm bvh_"+bodyPart+extension) + + + minRotationLimit="-45" + maxRotationLimit="45" + minORIENTATION="-55" #-45 default , -55 with 10 deg safety + maxORIENTATION="55" # 45 default , 55 with 10 deg safety + minDepth="900" #1000 original + maxDepth="4500" #3000 is too small + + #HIDEBODY="--hide2DLocationOfJoints 0 8 abdomen chest eye.r eye.l toe1-2.r toe5-3.r toe1-2.l toe5-3.l" #We want to contain these joints in the BVH file and solve them, but they do not + #SELECTBODY="--selectJoints 1 23 hip eye.r eye.l abdomen chest neck head rshoulder relbow rhand lshoulder lelbow lhand rhip rknee rfoot lhip lknee lfoot toe1-2.r toe5-3.r toe1-2.l toe5-3.l $HIDEBODY" + #--randomize2D $MIN_DEPTH $MAX_DEPTH $MIN_LIM $FRONT_MIN_ORIENTATION $MIN_LIM $MAX_LIM $FRONT_MAX_ORIENTATION $MAX_LIM" "$SELECTLOWERBODY $RAND_LOWER_BODY + + #$MIN_DEPTH $MAX_DEPTH $MIN_LIM -179.999999 $MIN_LIM $MAX_LIM 180 $MAX_LIM + + numberOfFilesProcessed = 0 + for bvhFile in allBVHFiles: + args=list() + args.append("--printparams") + args.append("--haltonerror") + args.append("--from") + args.append(bvhFile) + #RAND_UPPER_BODY="--perturbJointAngles 2 30.0 rshoulder lshoulder --perturbJointAngles 2 16.0 relbow lelbow --perturbJointAngles 2 10.0 abdomen chest" + #RAND_LOWER_BODY="--perturbJointAngles 2 30.0 rhip lhip --perturbJointAngles 4 10.0 lknee rknee lfoot rfoot --perturbJointAngles 2 10.0 abdomen chest" + args.append("--filtergimballocks"); args.append("4") + #startingFlags.append("--repeat") ; startingFlags.append("1") + + #Hip Position/Rotation Randomization + args.append("--randomize2D") + args.append(minDepth) #Minimum distance from the camera + args.append(maxDepth) #Maximum distance from the camera + args.append(minRotationLimit) # + args.append("-179.99") + args.append(minRotationLimit) + args.append(maxRotationLimit) + args.append("179.99") + args.append(maxRotationLimit) + #------------------------------------- + + + #Upper body joints + #------------------------------------- + args.append("--selectJoints") + args.append("1") + args.append("23") #Number of joints to select + args.append("hip") + args.append("eye.r") + args.append("eye.l") + args.append("abdomen") + args.append("chest") + args.append("neck") + args.append("head") + args.append("rshoulder") + args.append("relbow") + args.append("rhand") + args.append("lshoulder") + args.append("lelbow") + args.append("lhand") + args.append("rhip") + args.append("rknee") + args.append("rfoot") + args.append("lhip") + args.append("lknee") + args.append("lfoot") + args.append("toe1-2.r") + args.append("toe5-3.r") + args.append("toe1-2.l") + args.append("toe5-3.l") + #------------------------------------- + + #Deactivate spare joints + #------------------------------------- + args.append("--hide2DLocationOfJoints") + args.append("0") + args.append("8") #Number of joints to hide + args.append("abdomen") + args.append("chest") + args.append("eye.r") + args.append("eye.l") + args.append("toe1-2.r") + args.append("toe5-3.r") + args.append("toe1-2.l") + args.append("toe5-3.l") + #------------------------------------- + + + args.append("--occlusions") + args.append("--"+mode) + args.append(outputDirectory) + args.append(bodyPart+extension) + args.append("2d+bvh") + args = args + pythonFlags + bvhConvert(libBVH,args) + numberOfFilesProcessed = numberOfFilesProcessed + 1 + if (numberOfFilesProcessed==2): + print("Stopping now at ",numberOfFilesProcessed," limit") + break + + appendJSONEnding("2d_"+bodyPart+extension) + appendJSONEnding("3d_"+bodyPart+extension) + appendJSONEnding("bvh_"+bodyPart+extension) + + #./GroundTruthDumper $VIEW_COMMANDS --haltonerror --from $BVHFILE --filtergimballocks 4 $3 --repeat $ITERATIONS $2 --occlusions --csv $outputDir $1 2d+bvh # --bvh $outputDir/$f-random.bvh diff --git a/src/python/mnet4/BVH/calibration.py b/src/python/mnet4/BVH/calibration.py new file mode 100644 index 0000000..9abe449 --- /dev/null +++ b/src/python/mnet4/BVH/calibration.py @@ -0,0 +1,139 @@ +#This mimics the calibration files like ; +# https://github.com/AmmarkoV/RGBDAcquisition/blob/master/tools/Calibration/calibration.c + +#This mimics the calibration files like ; +# https://github.com/AmmarkoV/RGBDAcquisition/blob/master/tools/Calibration/calibration.c +def readCalibrationFromFile(filename): + calib = dict() + if filename is None: + return calib + + fp = None + try: + fp = open(filename, "r") + except IOError: + return calib + + # Our state + # ---------------------------- + i = 0 + category = 0 + line_length = 0 + lines_at_current_category = 0 + # ---------------------------- + + + for line in fp: + #-------------------------------------- + line = line.rstrip("\r\n") + line_length = len(line) + #-------------------------------------- + if line_length > 0: + if line[line_length - 1] == '\n': + line = line[:-1] + if line[line_length - 1] == '\r': + line = line[:-1] + #-------------------------------------- + if line_length > 1: + if line[line_length - 2] == '\n': + line = line[:-2] + if line[line_length - 2] == '\r': + line = line[:-2] + #-------------------------------------- + if line[0] == '%': + lines_at_current_category = 0 + #-------------------------------------- + # ---------------------------- ---------------------------- ---------------------------- + if line == "%I": + category = 1 + calib["intrinsic"] = list() + elif line == "%D": + category = 2 + elif line == "%T": + category = 3 + calib["extrinsicTranslation"] = list() + elif line == "%R": + category = 4 + calib["extrinsicRotationRodriguez"] = list() + elif line == "%NF": + category = 5 + elif line == "%UNIT": + category = 6 + elif line == "%RT4*4": + category = 7 + calib["extrinsic"] = list() + elif line == "%Width": + category = 8 + elif line == "%Height": + category = 9 + else: + # ---------------------------- ---------------------------- ---------------------------- + if category == 1: + calib["intrinsicParametersSet"] = 1 + lines_at_current_category = min(lines_at_current_category, 9) + calib["intrinsic"].append(float(line)) + lines_at_current_category += 1 + if (lines_at_current_category==9): + category = 0 + elif category == 2: + if lines_at_current_category == 0: + calib["k1"] = float(line) + elif lines_at_current_category == 1: + calib["k2"] = float(line) + elif lines_at_current_category == 2: + calib["p1"] = float(line) + elif lines_at_current_category == 3: + calib["p2"] = float(line) + elif lines_at_current_category == 4: + calib["k3"] = float(line) + lines_at_current_category += 1 + if (lines_at_current_category==4): + category = 0 + elif category == 3: + calib["extrinsicParametersSet"] = 1 + lines_at_current_category = min(lines_at_current_category, 3) + calib["extrinsicTranslation"].append(float(line)) + lines_at_current_category += 1 + if (lines_at_current_category==3): + category = 0 + elif category == 4: + lines_at_current_category = min(lines_at_current_category, 3) + calib["extrinsicRotationRodriguez"].append(float(line)) + lines_at_current_category += 1 + if (lines_at_current_category==3): + category = 0 + elif category == 5: + calib["nearPlane"] = float(line) + category = 0 + elif category == 6: + calib["farPlane"] = float(line) + category = 0 + elif category == 7: + lines_at_current_category = min(lines_at_current_category, 16) + calib["extrinsic"].append(float(line)) + lines_at_current_category += 1 + category = 0 + elif category == 8: + calib["width"] = int(line) + category = 0 + elif category == 9: + calib["height"] = int(line) + category = 0 + # ---------------------------- ---------------------------- ---------------------------- + + fp.close() + + try: + calib["fX"] = calib["intrinsic"][0] + calib["fY"] = calib["intrinsic"][4] + calib["cX"] = calib["intrinsic"][2] + calib["cY"] = calib["intrinsic"][5] + except: + print("No intrinsic matrix declared in ", filename) + print("Cannot populate fX, fY, cX, cY") + + + print("New calibration loaded : ",calib) + + return calib + diff --git a/src/python/mnet4/BVH/gatherFiles.sh b/src/python/mnet4/BVH/gatherFiles.sh new file mode 100755 index 0000000..1c10583 --- /dev/null +++ b/src/python/mnet4/BVH/gatherFiles.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "$DIR" + +REPO="/home/ammar/Documents/Programming/RGBDAcquisition" + + + +ITEM="tools/AmMatrix" +cd "$DIR" +mkdir -p $ITEM +cd $ITEM +cp $REPO/$ITEM/matrixTools* ./ +cp $REPO/$ITEM/matrix3x3Tools* ./ +cp $REPO/$ITEM/matrix4x4Tools* ./ +cp $REPO/$ITEM/matrixCalculations* ./ +cp $REPO/$ITEM/matrixOpenGL* ./ +cp $REPO/$ITEM/quaternions* ./ +cp $REPO/$ITEM/simpleRenderer* ./ +cp $REPO/$ITEM/solveLinearSystemGJ* ./ +cp $REPO/$ITEM/solveHomography* ./ + +ITEM="tools/PThreadWorkerPool" +cd "$DIR" +mkdir -p $ITEM +cd $ITEM +cd .. +cp -R $REPO/$ITEM/ ./ + + +ITEM="opengl_acquisition_shared_library/opengl_depth_and_color_renderer/src/Applications/BVHTester" +cd "$DIR" +mkdir -p $ITEM +cd $ITEM +cp $REPO/$ITEM/bvhConverter.py ./ +cp $REPO/$ITEM/bvhLibrary.py ./ +cp $REPO/$ITEM/bvhLibrary.h ./ +cp $REPO/$ITEM/main.c ./ +cd "$DIR" +cp $REPO/$ITEM/bvhLibrary.py ./ + +#Also copy the two most important files in root +cd "$DIR" +cp $REPO/$ITEM/bvhConverter.py ./ +cp $REPO/$ITEM/bvhLibrary.py ./ + +ITEM="opengl_acquisition_shared_library/opengl_depth_and_color_renderer/src/Library/MotionCaptureLoader" +cd "$DIR" +mkdir -p $ITEM +cd $ITEM +cd .. +cp -R $REPO/$ITEM/ ./ + +ITEM="opengl_acquisition_shared_library/opengl_depth_and_color_renderer/src/Library/ModelLoader" +cd "$DIR" +mkdir -p $ITEM +cd $ITEM +cd .. +cp -R $REPO/$ITEM/ ./ + +ITEM="opengl_acquisition_shared_library/opengl_depth_and_color_renderer/src/Library/TrajectoryParser" +cd "$DIR" +mkdir -p $ITEM +cd $ITEM +cp $REPO/$ITEM/InputParser_C.* ./ +cp $REPO/$ITEM/TrajectoryParserDataStructures.* ./ +cp $REPO/$ITEM/TrajectoryParser* ./ +cp $REPO/$ITEM/hashmap* ./ + + +exit 0 diff --git a/src/python/mnet4/BVH/headerWithHeadAndOneMotion.bvh b/src/python/mnet4/BVH/headerWithHeadAndOneMotion.bvh new file mode 100644 index 0000000..5009ccf --- /dev/null +++ b/src/python/mnet4/BVH/headerWithHeadAndOneMotion.bvh @@ -0,0 +1,1022 @@ +HIERARCHY +ROOT hip +{ + OFFSET 0 0 0 + CHANNELS 6 Xposition Yposition Zposition Zrotation Yrotation Xrotation + JOINT abdomen + { + OFFSET 0 20.6881 -0.73152 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT chest + { + OFFSET 0 11.7043 -0.48768 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT neck + { + OFFSET 0 22.1894 -2.19456 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT neck1 + { + OFFSET 0.000000 5.364170 1.574630 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT head + { + OFFSET 0.000000 5.364141 1.574630 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT __jaw + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT jaw + { + OFFSET 0.000000 -13.499860 2.500710 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT special04 + { + OFFSET -0.000000 -6.835370 4.375500 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oris02 + { + OFFSET 0.000000 1.711150 2.820850 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oris01 + { + OFFSET -0.000000 0.972390 0.845650 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.000000 1.162291 0.607091 + } + } + } + JOINT oris06.l + { + OFFSET 0.000000 1.711150 2.820850 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oris07.l + { + OFFSET 1.168850 0.445180 0.506110 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.450611 1.195178 0.204519 + } + } + } + JOINT oris06.r + { + OFFSET 0.000000 1.711150 2.820850 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oris07.r + { + OFFSET -1.168850 0.445180 0.506110 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.450611 1.195173 0.204519 + } + } + } + } + JOINT tongue00 + { + OFFSET -0.000000 -6.835370 4.375500 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT tongue01 + { + OFFSET 0.000000 3.973650 -3.762340 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT tongue02 + { + OFFSET 0.000000 0.429760 2.924710 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT tongue03 + { + OFFSET 0.000000 0.018530 2.059010 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT __tongue04 + { + OFFSET 0.000000 -0.440240 0.838860 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT tongue04 + { + OFFSET 0.000000 0.000000 0.000000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.000000 -0.440230 0.838860 + } + } + } + JOINT tongue07.l + { + OFFSET 0.000000 -0.440240 0.838860 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 1.160923 -0.331531 0.018227 + } + } + JOINT tongue07.r + { + OFFSET 0.000000 -0.440240 0.838860 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -1.160922 -0.331531 0.018227 + } + } + } + JOINT tongue06.l + { + OFFSET 0.000000 0.018530 2.059010 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 1.644752 -0.526075 -0.203281 + } + } + JOINT tongue06.r + { + OFFSET 0.000000 0.018530 2.059010 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -1.644752 -0.526075 -0.203282 + } + } + } + JOINT tongue05.l + { + OFFSET 0.000000 0.429760 2.924710 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 1.971028 -0.388618 0.239206 + } + } + JOINT tongue05.r + { + OFFSET 0.000000 0.429760 2.924710 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -1.971028 -0.388618 0.239205 + } + } + } + } + } + } + JOINT __levator02.l + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator02.l + { + OFFSET 0.313580 -11.321120 11.599360 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator03.l + { + OFFSET 1.681690 -1.563730 -1.357570 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator04.l + { + OFFSET 0.504730 -1.676760 -0.058160 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator05.l + { + OFFSET 0.145440 -1.643170 -0.225470 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.310116 -0.760198 -0.121474 + } + } + } + } + } + } + JOINT __levator02.r + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator02.r + { + OFFSET -0.313580 -11.321120 11.599360 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator03.r + { + OFFSET -1.681690 -1.563740 -1.357570 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator04.r + { + OFFSET -0.504730 -1.676750 -0.058160 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator05.r + { + OFFSET -0.145440 -1.643170 -0.225470 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.310116 -0.760193 -0.121474 + } + } + } + } + } + } + JOINT __special01 + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT special01 + { + OFFSET -0.000000 -14.026930 -5.716970 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oris04.l + { + OFFSET -0.000000 -0.492640 17.312620 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oris03.l + { + OFFSET 1.215520 -0.627430 -0.393050 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.288776 -0.560899 -0.149645 + } + } + } + JOINT oris04.r + { + OFFSET -0.000000 -0.492640 17.312620 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oris03.r + { + OFFSET -1.215520 -0.627440 -0.393050 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.288776 -0.560894 -0.149645 + } + } + } + JOINT oris06 + { + OFFSET -0.000000 -0.492640 17.312620 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oris05 + { + OFFSET -0.000000 -0.486000 0.000950 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.000000 -0.630493 0.197635 + } + } + } + } + } + JOINT __special03 + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT special03 + { + OFFSET 0.000000 -13.499860 2.500710 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT __levator06.l + { + OFFSET -0.000000 1.035800 10.090229 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator06.l + { + OFFSET 0.522240 -0.615720 0.045900 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 1.107505 -0.131898 -2.142227 + } + } + } + JOINT __levator06.r + { + OFFSET -0.000000 1.035800 10.090229 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator06.r + { + OFFSET -0.522240 -0.615730 0.045900 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -1.107506 -0.131893 -2.142227 + } + } + } + } + } + JOINT special06.l + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT special05.l + { + OFFSET 2.108890 0.153870 5.595070 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT eye.l + { + OFFSET 0.857170 -10.254801 2.414670 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.111609 0.006490 4.051424 + } + } + JOINT orbicularis03.l + { + OFFSET 0.857170 -10.254801 2.414670 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.097789 1.146431 3.788029 + } + } + JOINT orbicularis04.l + { + OFFSET 0.857170 -10.254801 2.414670 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.113609 -1.130505 3.863064 + } + } + } + } + JOINT special06.r + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT special05.r + { + OFFSET -2.108890 0.153870 5.595070 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT eye.r + { + OFFSET -0.857170 -10.254801 2.414670 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.111609 0.006490 4.051424 + } + } + JOINT orbicularis03.r + { + OFFSET -0.857170 -10.254801 2.414670 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.097789 1.146431 3.788029 + } + } + JOINT orbicularis04.r + { + OFFSET -0.857170 -10.254801 2.414670 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.113609 -1.130505 3.863064 + } + } + } + } + JOINT __temporalis01.l + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT temporalis01.l + { + OFFSET 6.332510 -9.444281 6.595120 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oculi02.l + { + OFFSET -0.804920 0.053010 1.621140 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oculi01.l + { + OFFSET -2.161570 1.690970 2.142660 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -1.740622 0.281157 0.647323 + } + } + } + } + } + JOINT __temporalis01.r + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT temporalis01.r + { + OFFSET -6.332510 -9.444281 6.595120 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oculi02.r + { + OFFSET 0.804920 0.053010 1.621140 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oculi01.r + { + OFFSET 2.161570 1.690970 2.142660 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 1.740622 0.281157 0.647323 + } + } + } + } + } + JOINT __temporalis02.l + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT temporalis02.l + { + OFFSET 6.377600 -11.680510 6.235180 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT risorius02.l + { + OFFSET -0.814250 0.451130 1.721730 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT risorius03.l + { + OFFSET -0.649710 -2.514660 0.612550 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.479556 -1.760402 -1.642659 + } + } + } + } + } + JOINT __temporalis02.r + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT temporalis02.r + { + OFFSET -6.377600 -11.680510 6.235180 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT risorius02.r + { + OFFSET 0.814250 0.451130 1.721730 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT risorius03.r + { + OFFSET 0.649710 -2.514660 0.612550 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.479556 -1.760402 -1.642659 + } + } + } + } + } + } + } + } + JOINT rCollar + { + OFFSET -2.68224 19.2634 -4.8768 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT rShldr + { + OFFSET -8.77824 -1.95073 1.46304 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT rForeArm + { + OFFSET -28.1742 -1.7115 0.48768 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT rHand + { + + OFFSET -21.049400 0.002190 -0.634230 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT metacarpal1.r + { + OFFSET -2.815680 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger2-1.r + { + OFFSET -6.292930 0.272380 2.520090 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger2-2.r + { + OFFSET -2.310530 -0.320530 -0.060510 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger2-3.r + { + OFFSET -2.051030 -0.295400 -0.164890 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -2.376838 -0.681367 -0.183877 + } + } + } + } + } + JOINT metacarpal2.r + { + OFFSET -2.815680 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger3-1.r + { + OFFSET -6.313640 0.626130 0.318530 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger3-2.r + { + OFFSET -3.015730 -0.589480 -0.088540 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger3-3.r + { + OFFSET -2.482120 -0.426280 0.076670 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -2.344174 -0.731969 0.003260 + } + } + } + } + } + JOINT __metacarpal3.r + { + OFFSET -2.815680 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT metacarpal3.r + { + OFFSET -0.606080 -0.162120 -1.874870 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger4-1.r + { + OFFSET -5.355730 0.702040 0.402510 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger4-2.r + { + OFFSET -2.643900 -0.485530 -0.117520 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger4-3.r + { + OFFSET -2.215850 -0.353160 0.066220 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -2.350273 -0.621223 -0.046375 + } + } + } + } + } + } + JOINT __metacarpal4.r + { + OFFSET -2.815680 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT metacarpal4.r + { + OFFSET -0.606080 -0.162120 -1.874870 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger5-1.r + { + OFFSET -4.761700 0.175470 -1.109590 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger5-2.r + { + OFFSET -1.916360 -0.173360 -0.146170 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger5-3.r + { + OFFSET -1.411290 -0.108670 -0.020110 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -1.799226 -0.102363 -0.078601 + } + } + } + } + } + } + JOINT __rthumb + { + OFFSET -2.815680 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT rthumb + { + OFFSET -0.283040 -0.142720 1.950690 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger1-2.r + { + OFFSET -0.915590 -2.152150 1.546760 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger1-3.r + { + OFFSET -3.213140 -0.470060 0.247480 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -2.521224 -0.161543 -0.511272 + } + } + } + } + } + } + } + } + } + JOINT lCollar + { + OFFSET 2.68224 19.2634 -4.8768 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT lShldr + { + OFFSET 8.77824 -1.95073 1.46304 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT lForeArm + { + OFFSET 28.1742 -1.7115 0.48768 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT lHand + { + + OFFSET 21.049408 0.002200 -0.634230 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT metacarpal1.l + { + OFFSET 2.815670 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger2-1.l + { + OFFSET 6.292930 0.272390 2.520090 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger2-2.l + { + OFFSET 2.310530 -0.320520 -0.060510 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger2-3.l + { + OFFSET 2.051030 -0.295400 -0.164880 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 2.376823 -0.681367 -0.183876 + } + } + } + } + } + JOINT metacarpal2.l + { + OFFSET 2.815670 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger3-1.l + { + OFFSET 6.313640 0.626120 0.318530 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger3-2.l + { + OFFSET 3.015730 -0.589470 -0.088540 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger3-3.l + { + OFFSET 2.482120 -0.426270 0.076670 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 2.344170 -0.731978 0.003260 + } + } + } + } + } + JOINT __metacarpal3.l + { + OFFSET 2.815670 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT metacarpal3.l + { + OFFSET 0.606080 -0.162120 -1.874870 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger4-1.l + { + OFFSET 5.355730 0.702050 0.402510 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger4-2.l + { + OFFSET 2.643900 -0.485530 -0.117510 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger4-3.l + { + OFFSET 2.215840 -0.353150 0.066210 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 2.350273 -0.621228 -0.046377 + } + } + } + } + } + } + JOINT __metacarpal4.l + { + OFFSET 2.815670 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT metacarpal4.l + { + OFFSET 0.606080 -0.162120 -1.874870 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger5-1.l + { + OFFSET 4.761700 0.175480 -1.109600 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger5-2.l + { + OFFSET 1.916350 -0.173360 -0.146170 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger5-3.l + { + OFFSET 1.411290 -0.108670 -0.020110 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 1.799216 -0.102372 -0.078600 + } + } + } + } + } + } + JOINT __lthumb + { + OFFSET 2.815670 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT lthumb + { + OFFSET 0.283040 -0.142710 1.950690 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger1-2.l + { + OFFSET 0.915930 -2.151960 1.546820 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger1-3.l + { + OFFSET 3.213210 -0.469680 0.247300 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 2.521210 -0.161290 -0.511422 + } + } + } + } + } + } + } + } + } + } + } + JOINT rButtock + { + OFFSET -8.77824 4.35084 1.2192 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT rThigh + { + OFFSET 0 -1.70687 -2.19456 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT rShin + { + OFFSET 0 -36.8199 0.73152 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT rFoot + { + + OFFSET 0.73152 -45.1104 -5.12064 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe1-1.R + { + OFFSET 2.454000 -4.050002 13.194999 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe1-2.R + { + OFFSET -0.214000 -0.646000 2.427000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.401900 -0.827789 2.725930 + } + } + } + JOINT toe2-1.R + { + OFFSET 0.177000 -4.299998 13.329000 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe2-2.R + { + OFFSET -0.177000 -0.323000 2.039000 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe2-3.R + { + OFFSET -0.067000 -0.440998 1.248000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.042990 -0.647306 1.660872 + } + } + } + } + JOINT toe3-1.R + { + OFFSET -1.396000 -4.461999 13.078999 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe3-2.R + { + OFFSET -0.161000 -0.247002 1.809000 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe3-3.R + { + OFFSET -0.033000 -0.441999 1.202000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.032040 -0.433550 1.271800 + } + } + } + } + JOINT toe4-1.R + { + OFFSET -2.888001 -4.480000 12.376999 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe4-2.R + { + OFFSET -0.160000 -0.331998 1.491001 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe4-3.R + { + OFFSET 0.035999 -0.251002 1.138999 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.088911 -0.568814 0.969530 + } + } + } + } + JOINT toe5-1.R + { + OFFSET -4.257999 -4.467001 11.711999 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe5-2.R + { + OFFSET -0.046000 -0.265999 0.982000 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe5-3.R + { + OFFSET 0.086999 -0.372000 0.791000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.044329 -0.555482 1.085780 + } + } + } + } + } + } + } + } + JOINT lButtock + { + OFFSET 8.77824 4.35084 1.2192 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT lThigh + { + OFFSET 0 -1.70687 -2.19456 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT lShin + { + OFFSET 0 -36.8199 0.73152 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT lFoot + { + + OFFSET -0.73152 -45.1104 -5.12064 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe1-1.L + { + OFFSET -2.454000 -4.050002 13.194999 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe1-2.L + { + OFFSET 0.214000 -0.646000 2.427000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.401900 -0.827789 2.725930 + } + } + } + JOINT toe2-1.L + { + OFFSET -0.177000 -4.299998 13.329000 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe2-2.L + { + OFFSET 0.177000 -0.323000 2.039000 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe2-3.L + { + OFFSET 0.067000 -0.440998 1.248000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.042990 -0.647306 1.660872 + } + } + } + } + JOINT toe3-1.L + { + OFFSET 1.396000 -4.461999 13.078999 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe3-2.L + { + OFFSET 0.161000 -0.247002 1.809000 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe3-3.L + { + OFFSET 0.033000 -0.441999 1.202000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.032040 -0.433550 1.271800 + } + } + } + } + JOINT toe4-1.L + { + OFFSET 2.888001 -4.480000 12.376999 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe4-2.L + { + OFFSET 0.160000 -0.331998 1.491001 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe4-3.L + { + OFFSET -0.035999 -0.251002 1.138999 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.088911 -0.568814 0.969530 + } + } + } + } + JOINT toe5-1.L + { + OFFSET 4.257999 -4.467001 11.711999 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe5-2.L + { + OFFSET 0.046000 -0.265999 0.982000 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe5-3.L + { + OFFSET -0.086999 -0.372000 0.791000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.044329 -0.555482 1.085780 + } + } + } + } + } + } + } + } +} +MOTION +Frames: 1 +Frame Time: 0.04 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 diff --git a/src/python/mnet4/BVH/libBVHConverter.so b/src/python/mnet4/BVH/libBVHConverter.so new file mode 100755 index 0000000000000000000000000000000000000000..737442edba7fa8bcf7ba4fa5f731daa67b08fe06 GIT binary patch literal 438208 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�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�P-b3J0(#_h?QA4CCD>LxZ|-ed&WHX(Yxe& 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

gVzhVy56nG6!D_YVWI+&T~S_QmCMi5N; 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�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

bq&*Q!2UmsxNYF@Das$*{%81svC|`N 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 literal 0 HcmV?d00001 diff --git a/src/python/mnet4/BVH/main.c b/src/python/mnet4/BVH/main.c new file mode 100644 index 0000000..1b794cc --- /dev/null +++ b/src/python/mnet4/BVH/main.c @@ -0,0 +1,1373 @@ +/** @file main.c + * @brief A library that can parse BVH files and perform various processing options as a commandline tool + * X86 compilation: gcc -o -L/usr/X11/lib main main.c + * X64 compilation: gcc -o -L/usr/X11/lib64 main main.c + * @author Ammar Qammaz (AmmarkoV) + */ + +#include +#include +#include +#include +#include + +#include "../../Library/TrajectoryParser/TrajectoryParserDataStructures.h" +#include "../../Library/MotionCaptureLoader/bvh_loader.h" +#include "../../Library/MotionCaptureLoader/calculate/bvh_to_tri_pose.h" +#include "../../Library/MotionCaptureLoader/calculate/smoothing.h" + +#include "../../Library/MotionCaptureLoader/export/bvh_to_trajectoryParserTRI.h" +#include "../../Library/MotionCaptureLoader/export/bvh_to_trajectoryParserPrimitives.h" +#include "../../Library/MotionCaptureLoader/export/bvh_export.h" +#include "../../Library/MotionCaptureLoader/export/bvh_to_bvh.h" +#include "../../Library/MotionCaptureLoader/export/bvh_to_csv.h" +#include "../../Library/MotionCaptureLoader/export/bvh_to_c.h" + +#include "../../Library/MotionCaptureLoader/edit/bvh_cut_paste.h" +#include "../../Library/MotionCaptureLoader/edit/bvh_randomize.h" +#include "../../Library/MotionCaptureLoader/edit/bvh_filter.h" +#include "../../Library/MotionCaptureLoader/edit/bvh_rename.h" +#include "../../Library/MotionCaptureLoader/edit/bvh_merge.h" +#include "../../Library/MotionCaptureLoader/edit/bvh_remapangles.h" +#include "../../Library/MotionCaptureLoader/edit/bvh_interpolate.h" + +#include "../../Library/MotionCaptureLoader/ik/bvh_inverseKinematics.h" +#include "../../Library/MotionCaptureLoader/ik/hardcodedProblems_inverseKinematics.h" + +#include "../../Library/MotionCaptureLoader/metrics/bvh_measure.h" +#include "../../Library/MotionCaptureLoader/tests/test.h" + +#include "../../../../../tools/AmMatrix/matrix4x4Tools.h" +#include "../../../../../tools/AmMatrix/matrixOpenGL.h" + + +#define NORMAL "\033[0m" +#define BLACK "\033[30m" /* Black */ +#define RED "\033[31m" /* Red */ +#define GREEN "\033[32m" /* Green */ +#define YELLOW "\033[33m" /* Yellow */ +#define BLUE "\033[34m" /* Blue */ +#define MAGENTA "\033[35m" /* Magenta */ +#define CYAN "\033[36m" /* Cyan */ +#define WHITE "\033[37m" /* White */ + +void haltOnError(unsigned int haltingSwitch,const char * message) +{ + fprintf(stderr,RED "=======================================\n"); + fprintf(stderr,"=======================================\n"); + fprintf(stderr,"Encountered error during procedure %s \n",message); + fprintf(stderr,"=======================================\n"); + fprintf(stderr,"=======================================\n" NORMAL); + + if (haltingSwitch) + { + fprintf(stderr,RED "Halting because of --haltonerror switch\n" NORMAL); + exit(1); + } +} + +void incorrectArguments() +{ + fprintf(stderr,RED "Incorrect number of arguments.. \n" NORMAL); + exit(1); +} + +//----------------------------------------------------------------- +//----------------------------------------------------------------- +//----------------------------------------------------------------- + +void prepare4x4Human36MRotationMatrix(struct Matrix4x4OfFloats * rotationMatrix,float rX,float rY,float rZ) +{ + if ( (rX==0.0) && (rY==0.0) && (rZ==0.0) ) + { + create4x4FIdentityMatrix(rotationMatrix); + return ; + } + + struct Matrix4x4OfFloats rXM={0}; + struct Matrix4x4OfFloats rYM={0}; + struct Matrix4x4OfFloats rZM={0}; + + //R1x=np.matrix([[1,0,0], [0,np.cos(Rx),-np.sin(Rx)], [0,np.sin(Rx),np.cos(Rx)] ]) #[1 0 0; 0 cos(obj.Params(1)) -sin(obj.Params(1)); 0 sin(obj.Params(1)) cos(obj.Params(1))] + rXM.m[0]=1.0; rXM.m[1]=0.0; rXM.m[2]=0.0; rXM.m[3]=0.0; + rXM.m[4]=0.0; rXM.m[5]=cos(rX); rXM.m[6]=-sin(rX); rXM.m[7]=0.0; + rXM.m[8]=0.0; rXM.m[9]=sin(rX); rXM.m[10]=cos(rX); rXM.m[11]=0.0; + rXM.m[12]=0.0; rXM.m[13]=0.0; rXM.m[14]=0.0; rXM.m[15]=1.0; + + //R1y=np.matrix([[np.cos(Ry),0,np.sin(Ry)], [0,1,0], [-np.sin(Ry),0,np.cos(Ry)]]) #[cos(obj.Params(2)) 0 sin(obj.Params(2)); 0 1 0; -sin(obj.Params(2)) 0 cos(obj.Params(2))] + rYM.m[0]=cos(rY); rYM.m[1]=0.0; rYM.m[2]=sin(rY); rYM.m[3]=0.0; + rYM.m[4]=0.0; rYM.m[5]=1.0; rYM.m[6]=0.0; rYM.m[7]=0.0; + rYM.m[8]=-sin(rY); rYM.m[9]=0.0; rYM.m[10]=cos(rY); rYM.m[11]=0.0; + rYM.m[12]=0.0; rYM.m[13]=0.0; rYM.m[14]=0.0; rYM.m[15]=1.0; + + //R1z=np.matrix([[np.cos(Rz),-np.sin(Rz),0], [np.sin(Rz),np.cos(Rz),0], [0,0,1]]) #[cos(obj.Params(3)) -sin(obj.Params(3)) 0; sin(obj.Params(3)) cos(obj.Params(3)) 0; 0 0 1] + rZM.m[0]=cos(rZ); rZM.m[1]=-sin(rZ); rZM.m[2]=0.0; rZM.m[3]=0.0; + rZM.m[4]=sin(rZ); rZM.m[5]=cos(rZ); rZM.m[6]=0.0; rZM.m[7]=0.0; + rZM.m[8]=0.0; rZM.m[9]=0.0; rZM.m[10]=1.0; rZM.m[11]=0.0; + rYM.m[12]=0.0; rZM.m[13]=0.0; rZM.m[14]=0.0; rZM.m[15]=1.0; + + multiplyThree4x4FMatrices( + rotationMatrix , + &rXM , + &rYM, + &rZM + ); + +} + + +int testMultipleLoad(const char * filename) +{ + struct BVH_MotionCapture bvhMotion={0}; + + FILE * fp = fopen(filename,"r"); + if (fp!=0) + { + char * line = NULL; + size_t len = 0; + ssize_t read; + + unsigned int fileNumber=0; + //unsigned int done=0; + // while ( (!done) && ( (read = getline(&line, &len, fp)) != -1) ) + while ( (read = getline(&line, &len, fp)) != -1) + { + if (line!=0) + { + int lineLength = strlen(line); + if (lineLength>=1) + { + if (line[lineLength-1]==10) { line[lineLength-1]=0; } + if (line[lineLength-1]==13) { line[lineLength-1]=0; } + } + if (lineLength>=2) + { + if (line[lineLength-2]==10) { line[lineLength-2]=0; } + if (line[lineLength-2]==13) { line[lineLength-2]=0; } + } + + fprintf(stderr,"Next file is `%s`\n",line); + if ( bvh_loadBVH(line, &bvhMotion, 1.0) ) + { + fprintf(stderr,"Loaded file `%s`\n",line); + //Change joint names.. + bvh_renameJointsForCompatibility(&bvhMotion); + fprintf(stderr,"Did rename `%s`\n",line); + bvh_free(&bvhMotion); + fprintf(stderr,"Freed file `%s`\n",line); + } + } + + ++fileNumber; + //if (fileNumber==10) { done=1; } + } + + if (line!=0) { free(line); line=0; } + fclose(fp); + return 1; + } + return 0; +} + + +void printCallingParameters(int argc,const char **argv) +{ + fprintf(stderr,"Utility was called using following parameters :\n"); + unsigned int z=0; + for (z=0; z=argc) { incorrectArguments(); } + sampleSkip=atoi(argv[i+1]); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--printparams")==0) + { + printCallingParameters(argc,argv); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--debug")==0) + { + bvhMotion.debug=1; + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--filtergimballocks")==0) + { + if (i+1>=argc) { incorrectArguments(); } + filterOutPosesThatAreGimbalLocked(&bvhMotion,atof(argv[i+1])); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--haltonerror")==0) + { + immediatelyHaltOnError=1; + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--occlusions")==0) + { + occlusions=1; + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--print")==0) + { + bvh_printBVH(&bvhMotion); + } else + if (strcmp(argv[i],"--printc")==0) + { + bvh_print_C_Header(&bvhMotion); + } else + if (strcmp(argv[i],"--printprofile")==0) + { + bvh_print_profile(&bvhMotion); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--extractmotionrangeforlistoffiles")==0) + { + if (i+1>=argc) { incorrectArguments(); } + extractMinimaMaximaFromBVHList(argv[i+1]); + + exit(0); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--testmultiple")==0) + { + if (i+1>=argc) { incorrectArguments(); } + testMultipleLoad(argv[i+1]); + exit(0); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--tuneIterations")==0) + { + // ./BVHTester --from Motions/05_01.bvh --selectJoints 0 23 hip eye.r eye.l abdomen chest neck head rshoulder relbow rhand lshoulder lelbow lhand rhip rknee rfoot lhip lknee lfoot toe1-2.r toe5-3.r toe1-2.l toe5-3.l --testIK 80 4 130 0.001 5 100 + + if (i+7>=argc) { incorrectArguments(); } + + unsigned int previousFrame=atoi(argv[i+1]); + unsigned int sourceFrame=atoi(argv[i+2]); + unsigned int targetFrame=atoi(argv[i+3]); + float learningRate = atof(argv[i+4]); + unsigned int iterations=atoi(argv[i+5]); + unsigned int epochs=atoi(argv[i+6]); + float spring = atof(argv[i+7]); + + bvhMeasureIterationInfluence( + &bvhMotion, + learningRate, + spring, + iterations, + epochs, + previousFrame, + sourceFrame, + targetFrame, + multiThreaded + ); + + exit(0); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--testIK")==0) + { + // ./BVHTester --from Motions/05_01.bvh --selectJoints 0 23 hip eye.r eye.l abdomen chest neck head rshoulder relbow rhand lshoulder lelbow lhand rhip rknee rfoot lhip lknee lfoot toe1-2.r toe5-3.r toe1-2.l toe5-3.l --testIK 4 80 130 1 0.1 15 100 0 0 1 1 1 + if (i+12>=argc) { + fprintf(stderr,"--testIK requires 12 arguments, previousFrame sourceFrame targetFrame stepFrame learningRate iterations epochs spring langevin verbosity.."); + fprintf(stderr,"got %u ",argc-i); + incorrectArguments(); + } + + unsigned int previousFrame=atoi(argv[i+1]); + unsigned int sourceFrame=atoi(argv[i+2]); + unsigned int targetFrame=atoi(argv[i+3]); + unsigned int stepFrame=atoi(argv[i+4]); + float learningRate = atof(argv[i+5]); + unsigned int iterations=atoi(argv[i+6]); + unsigned int epochs=atoi(argv[i+7]); + float spring = atof(argv[i+8]); + float langevin=atof(argv[i+9]); + char verbose = atoi(argv[i+10]); + float learningRateDecayRate = atof(argv[i+11]); + float momentum = atof(argv[i+12]); + + float maeSum = 0.0; + unsigned int maeSamples = 0; + unsigned long elapsedTime = 0; + + FILE * fp = fopen("report.html","w"); + if (fp!=0) + { + fprintf(fp,""); + + // + fprintf(fp,"File : %s
\n",bvhMotion.fileName); + fprintf(fp," %u frames / %u joints / %u motion values per frame
\n",bvhMotion.numberOfFrames,bvhMotion.jointHierarchySize,bvhMotion.numberOfValuesPerFrame); + fprintf(fp,"Previous Frame : %u
\n",previousFrame); + fprintf(fp,"Source Frame : %u
\n",sourceFrame); + fprintf(fp,"Target Frame : %u
\n",targetFrame); + fprintf(fp,"Step Frame : %u
\n",stepFrame); + fprintf(fp,"Learning Rate : %f
\n",learningRate); + fprintf(fp,"Langevin Dynamics : %f
\n",langevin); + fprintf(fp,"Iterations : %u
\n",iterations); + fprintf(fp,"Epochs : %u
\n",epochs); + fprintf(fp,"
\n"); + + + fprintf(fp,"\n\n\n"); + unsigned int step = 0; + while( + (sourceFrame+step\n", + sourceFrame+step,targetFrame+step, + mae, + sourceFrame+step,targetFrame+step + ); + //------------------------------------------------------------------------------------------------ + maeSum+=mae; + step+=stepFrame; + maeSamples+=1; + //------------------------------------------------------------------------------------------------ + } + + float maeAverage = 0.0; + unsigned long elapsedTimeAverage = 0; + if (maeSamples>0) + { + maeAverage = (float) maeSum/maeSamples; + elapsedTimeAverage = (unsigned long) elapsedTime/maeSamples; + } + + fprintf(fp,"
Source
Frame
Target
Frame
Mean Average
Error
Link
%u%u%0.2f mmOpen
"); + fprintf(fp,"

Total M.A.E. for %u samples : %0.2f mm
\n",maeSamples,maeAverage); + fprintf(fp,"Elapsed Time : %lu microseconds (%0.2f fps)
\n",(unsigned long) elapsedTime,convertStartEndTimeFromMicrosecondsToFPSIK(0,elapsedTimeAverage)); + fprintf(fp,""); + fclose(fp); + } + + //---------------------------------------------------- + //---------------------------------------------------- + fprintf(stdout,"MAE:%0.2f",(float) maeSum/maeSamples); + if (!fileExistsIK("report.csv")) + { + fp = fopen("report.csv","w"); + if (fp!=0) + { + fprintf(fp,"dataset,learningRate,lrdecay,previous,start,target,step,iterations,epochs,langevin,samples,mae,fps,momentum\n"); + fclose(fp); + } + } + //---------------------------------------------------- + //---------------------------------------------------- + if (fileExistsIK("report.csv")) + { + fp = fopen("report.csv","a"); + if (fp!=0) + { + fprintf( + fp,"%s,%f,%f,%u,%u,%u,%u,%u,%u,%f,%u,%f,%f,%f\n", + bvhMotion.fileName, + learningRate, + learningRateDecayRate, + previousFrame, + sourceFrame, + targetFrame, + stepFrame, + iterations, + epochs, + langevin, + maeSamples, + (float) maeSum/maeSamples, + convertStartEndTimeFromMicrosecondsToFPSIK(0,(unsigned long) elapsedTime/maeSamples), + momentum + ); + fclose(fp); + } + } + //---------------------------------------------------- + //---------------------------------------------------- + int r=0; //int r=system("xdg-open report.html"); + exit(r); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--probefilter")==0) + { + //Filter using 2D rules + //./BVHTester --from Motions/05_01.bvh --probefilter 0 0 -130.0 0 0 0 1920 1080 570.7 570.3 3 rhand lhip 10 12 rhand rhip 5 8 rhand lhand 20 25 + probeForFilterRules(&bvhMotion,argc-i-1,&argv[i+1]); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--filterout")==0) + { + //Filter using 2D rules + //./BVHTester --from Motions/05_01.bvh --filterout 0 0 -130.0 0 0 0 1920 1080 570.7 570.3 3 rhand lhip 140.0 145.0 rhand rhip 65 70 rhand lhand 190 205 + //./BVHTester --printparams --haltonerror --from Motions/05_01.bvh --selectJoints 1 13 hip eye.r eye.l abdomen chest neck head rshoulder relbow rhand lshoulder lelbow lhand --hide2DLocationOfJoints 0 4 abdomen chest eye.r eye.l --perturbJointAngles 4 38.0 rshoulder lshoulder rhip lhip --perturbJointAngles 8 10.0 rhand relbow lelbow lhand lknee rknee lfoot rfoot --repeat 0 --filterout 0 0 -130.0 0 90 0 1920 1080 570.7 570.3 6 rhand lhip 0 120 rhand rhip 0 120 rhand lhand 0 150 lhand rhip 0 120 lhand lhip 0 120 lhand rhand 0 150 --randomize2D 1000 4000 -35 -179.999999 -35 35 180 35 --occlusions --csv ./ upperbody_all.csv 2d+bvh + + if (!filterOutPosesThatAreCloseToRules(&bvhMotion,argc-i-1,&argv[i+1])) + { + haltOnError(immediatelyHaltOnError,"Error while filtering out joints.."); + } + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--nofilter")==0) + { + filterBehindCamera=0; + filterIfAnyJointOutsideof2DFrame=0; + filterTopWeirdRandomSkeletons=0; + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--renderingConfiguration")==0) + { + if (i+17>=argc) { incorrectArguments(); } + //----------------------------------------- + float rX=atof(argv[i+1]); + float rY=atof(argv[i+2]); + float rZ=atof(argv[i+3]); + renderingConfiguration.T[0]=atof(argv[i+4]); + renderingConfiguration.T[1]=atof(argv[i+5]); + renderingConfiguration.T[2]=atof(argv[i+6]); + renderingConfiguration.fX=atof(argv[i+7]); + renderingConfiguration.fY=atof(argv[i+8]); + renderingConfiguration.cX=atof(argv[i+9]); + renderingConfiguration.cY=atof(argv[i+10]); + renderingConfiguration.k1=atof(argv[i+11]); + renderingConfiguration.k2=atof(argv[i+12]); + renderingConfiguration.k3=atof(argv[i+13]); + renderingConfiguration.p1=atof(argv[i+14]); + renderingConfiguration.p2=atof(argv[i+15]); + unsigned int width=atoi(argv[i+16]); + unsigned int height=atoi(argv[i+17]); + //------------------------------------------------------------------------------- + prepare4x4Human36MRotationMatrix(&renderingConfiguration.viewMatrix,rX,rY,rZ); + //copy3x3FMatrixTo4x4F(renderingConfiguration.viewMatrix,renderingConfiguration.R); + + // 0 1 2 3 + // 4 5 6 7 + // 8 9 10 11 + // 12 13 14 15 + //---------------------------------------------------------------- + renderingConfiguration.viewMatrix.m[3] =renderingConfiguration.T[0]; + renderingConfiguration.viewMatrix.m[7] =renderingConfiguration.T[1]; + renderingConfiguration.viewMatrix.m[11]=renderingConfiguration.T[2]; + //---------------------------------------- + renderingConfiguration.viewport[0]=0; + renderingConfiguration.viewport[1]=0; + renderingConfiguration.viewport[2]=width; + renderingConfiguration.viewport[3]=height; + renderingConfiguration.width=width; + renderingConfiguration.height=height; + //---------------------------------------- + buildOpenGLProjectionForIntrinsics( + renderingConfiguration.projection.m , + renderingConfiguration.viewport , + renderingConfiguration.fX, + renderingConfiguration.fY, + 1.0,//sr->skew, + renderingConfiguration.cX, + renderingConfiguration.cY, + width, + height, + renderingConfiguration.near, //Near + renderingConfiguration.far //Far + ); + + if ( (renderingConfiguration.k1!=0.0) || (renderingConfiguration.k2!=0.0) || (renderingConfiguration.k3!=0.0) || (renderingConfiguration.p1!=0.0) || (renderingConfiguration.p2!=0.0) ) + { + fprintf(stderr,"The distortion model has not been implemented so the BVH tool is not able to meet your configuration criteria..!\n"); + exit(1); + } + + if ( (rX!=0.0) || (rY!=0.0) || (rZ!=0.0) ) + { + fprintf(stderr,"Camera rotation change has been prohibited, please don't use a camera rotation..!\n"); + exit(1); + } + + if ( (renderingConfiguration.T[0]!=0.0) || (renderingConfiguration.T[1]!=0.0) || (renderingConfiguration.T[2]!=0.0) ) + { + fprintf(stderr,"Camera position change has been prohibited, please don't use a camera rotation..!\n"); + exit(1); + } + + //TODO: Normally at this point we should have defined the matrices needed and set the following switch + // Due to some missing stuff in the pipeline this is deactivated so only fX,fY , cX,cY , Width/Height + // settings are honored by the converter.. + //---------------------------------------- + //renderingConfiguration.isDefined=1; TODO: <- at some point fix this.. + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--changeJointDimensions")==0) + { + if (i+4>=argc) { incorrectArguments(); } + if ( + !bvh_changeJointDimensions( + &bvhMotion, + argv[i+1], + atof(argv[i+2]), + atof(argv[i+3]), + atof(argv[i+4]) + ) + ) + { + fprintf(stderr,RED "failed to change `%s` joint dimensions\n",argv[i+1]); + haltOnError(immediatelyHaltOnError,"Error while changing joint dimensions.."); + } + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--onlyAnimateGivenJoints")==0) + { + unsigned int numberOfArguments=atoi(argv[i+1]); + bvh_onlyAnimateGivenJoints(&bvhMotion,numberOfArguments,argv+i+2); + // + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--scale")==0) + { + if (i+1>=argc) { incorrectArguments(); } + scaleWorld=atof(argv[i+1]); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--filterOccludedJoints")==0) + { + //TEST: ./BVHTester --from brokenHand.bvh --svg ./ --filterOccludedJoints + // ./BVHTester --from Motions/02_03.bvh --filterOccludedJoints --bvh test.bvh + filterOccludedJoints=1; + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--scaleOffsets")==0) + { + if (i+1>=argc) { incorrectArguments(); } + float scaleRatio = atof(argv[i+1]); + fprintf(stderr,"Offset scaling ratio = %0.2f \n",scaleRatio); + bvh_scaleAllOffsets( + &bvhMotion, + scaleRatio + ); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--scaleJointChildrenOffsets")==0) + { + if (i+2>=argc) { incorrectArguments(); } + const char * jointName = argv[i+1]; + float scaleRatio = atof(argv[i+2]); + fprintf(stderr,"Joint Children of %s will get an offset scaling ratio = %0.2f \n",jointName,scaleRatio); + bvh_scaleAllJointChildrenOffsets( + &bvhMotion, + jointName, + scaleRatio + ); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--onlyFirstFrame")==0) + { + bvh_copyMotionFrame(&bvhMotion, 0, 1 ); + bvhMotion.numberOfFrames=2; //Just Render one frame.. + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--maxFrames")==0) + { + if (i+1>=argc) { incorrectArguments();} + unsigned int maxFrames=atoi(argv[i+1]); + //We can limit the number of frames + if (maxFrames!=0) + { + //Only reducing number of frames + if (bvhMotion.numberOfFrames>maxFrames) + { + bvhMotion.numberOfFrames = maxFrames; + } + } + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--from")==0) + { + if (i+1>=argc) { incorrectArguments();} + fromBVHFile=argv[i+1]; + //First of all we need to load the BVH file + if (!bvh_loadBVH(fromBVHFile, &bvhMotion, scaleWorld)) + { + fprintf(stderr,"Error loading file `%s` \n",fromBVHFile); + haltOnError(immediatelyHaltOnError,"Error loading bvh file.."); + } + + //Change joint names.. + bvh_renameJointsForCompatibility(&bvhMotion); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--addfrom")==0) + { + fprintf(stderr,"File %s initially had %u frames\n",bvhMotion.fileName,bvhMotion.numberOfFrames); + if (i+1>=argc) { incorrectArguments();} + const char * addfromBVHFile=argv[i+1]; + struct BVH_MotionCapture addedMotion={0}; + //First of all we need to load the BVH file + if (!bvh_loadBVH(addfromBVHFile, &addedMotion, scaleWorld)) + { + fprintf(stderr,"Error loading file `%s` \n",addfromBVHFile); + haltOnError(immediatelyHaltOnError,"Error loading bvh file.."); + } + + //Change joint names.. + bvh_renameJointsForCompatibility(&addedMotion); + bvh_GrowMocapFileByCopyingOtherMocapFile( + &bvhMotion, + &addedMotion + ); + fprintf(stderr,"After adding %s file %s has %u frames\n",addedMotion.fileName,bvhMotion.fileName,bvhMotion.numberOfFrames); + bvh_free(&addedMotion); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--addpositionalchannels")==0) + { + // ./BVHTester --from dataset/head.bvh --addpositionalchannels --bvh test.bvh + bvh_mergeOffsetsInMotions(&bvhMotion); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--merge")==0) + { + if (i+2>=argc) { incorrectArguments();} + const char * BVHPathToFileToMerge = argv[i+1]; + const char * pathToMergeRules = argv[i+2]; + + struct BVH_MotionCapture bvhMotionToMerge={0}; + if ( bvh_loadBVH(BVHPathToFileToMerge, &bvhMotionToMerge, scaleWorld) ) + { + bvh_renameJointsForCompatibility(&bvhMotionToMerge); + if ( + !bvh_mergeWith( + &bvhMotion, + &bvhMotionToMerge, + pathToMergeRules + ) + ) + { + fprintf(stderr,"Failed to merge files (%s and %s)..\n",fromBVHFile,BVHPathToFileToMerge); + } + } else + { + fprintf(stderr,"Could not open BVH file that was requested to be merged (%s)..\n",BVHPathToFileToMerge); + } + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--blenderCoordinateSystemChange")==0) + { + //Blender uses a different coordinate system for the BVH files + //This call will try to do the coordinate change to return to our coordinate system + //among other things : ( https://projects.blender.org/blender/blender-addons/issues/104549 ) + //hopefully this will solve the most major discrepancies.. + //./BVHTester --from BLENDERheaderWithHeadAndOneMotionTEST.bvh --blenderCoordinateSystemChange --bvh test.bvh + bvh_coordinateSystemChange(&bvhMotion,"XYZ","X-ZY"); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--swap")==0) + { + if (i+2>=argc) { incorrectArguments(); } + bvh_GrowMocapFileBySwappingJointAndItsChildren( + &bvhMotion, + argv[i+1], + argv[i+2], + 0 + ); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--normalizeRotations")==0) + { + bvh_normalizeRotations(&bvhMotion); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--360")==0) + { + if (i+1>=argc) { incorrectArguments(); } + + bvh_GrowMocapFileByGeneratingPoseFromAllViewingAngles( + &bvhMotion, + atoi(argv[i+1]) + ); + } else + //----------------------------------------------------- + // This does not work.. + //if (strcmp(argv[i],"--mirror")==0) + //{ + // if (i+2>=argc) { incorrectArguments(); } + // bvh_MirrorJointsThroughIK( + // &bvhMotion, + // argv[i+1], + // argv[i+2] + // ); + //} else + //----------------------------------------------------- + //----------------------------------------------------- + if (strcmp(argv[i],"--interpolate")==0) + { + bvh_InterpolateMotion( + &bvhMotion + ); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--symmetricflip")==0) + { + BVHFrameID fID = 0; + for (fID=0; fID=argc) { incorrectArguments(); } + bvh_GrowMocapFileByCopyingExistingMotions( + &bvhMotion, + atoi(argv[i+1]) + ); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--set")==0) + { + //./BVHTester --from dataset/lhand.qbvh --repeat 100 --set 3 0.5 --set 4 -0.5 --set 5 -0.5 --set 6 0.5 --bvh restR.bvh + + if (i+2>=argc) { incorrectArguments(); } + + int mID=atoi(argv[i+1]); + float value=atof(argv[i+2]); + + bvh_setMIDValue( + &bvhMotion, + mID, + value + ); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--setPositionRotation")==0) + { + if (i+6>=argc) { incorrectArguments(); } + + struct motionTransactionData cameraPositionRotation={0}; + cameraPositionRotation.data[MOTIONBUFFER_TRANSACTION_DATA_FIELDS_POSITION_X]=-1*atof(argv[i+1])/10; + cameraPositionRotation.data[MOTIONBUFFER_TRANSACTION_DATA_FIELDS_POSITION_Y]=-1*atof(argv[i+2])/10; + cameraPositionRotation.data[MOTIONBUFFER_TRANSACTION_DATA_FIELDS_POSITION_Z]=-1*atof(argv[i+3])/10; + cameraPositionRotation.data[MOTIONBUFFER_TRANSACTION_DATA_FIELDS_ROTATION_X]=atof(argv[i+4]); + cameraPositionRotation.data[MOTIONBUFFER_TRANSACTION_DATA_FIELDS_ROTATION_Y]=atof(argv[i+5]); + cameraPositionRotation.data[MOTIONBUFFER_TRANSACTION_DATA_FIELDS_ROTATION_Z]=atof(argv[i+6]); + bvh_SetPositionRotation( + &bvhMotion, + &cameraPositionRotation + ); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--offsetPositionRotation")==0) + { + if (i+6>=argc) { incorrectArguments(); } + + struct motionTransactionData cameraPositionRotation={0}; + cameraPositionRotation.data[MOTIONBUFFER_TRANSACTION_DATA_FIELDS_POSITION_X]=-1*atof(argv[i+1])/10; + cameraPositionRotation.data[MOTIONBUFFER_TRANSACTION_DATA_FIELDS_POSITION_Y]=-1*atof(argv[i+2])/10; + cameraPositionRotation.data[MOTIONBUFFER_TRANSACTION_DATA_FIELDS_POSITION_Z]=-1*atof(argv[i+3])/10; + cameraPositionRotation.data[MOTIONBUFFER_TRANSACTION_DATA_FIELDS_ROTATION_X]=atof(argv[i+4]); + cameraPositionRotation.data[MOTIONBUFFER_TRANSACTION_DATA_FIELDS_ROTATION_Y]=atof(argv[i+5]); + cameraPositionRotation.data[MOTIONBUFFER_TRANSACTION_DATA_FIELDS_ROTATION_Z]=atof(argv[i+6]); + bvh_OffsetPositionRotation( + &bvhMotion, + &cameraPositionRotation + ); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--perturbJointAngles")==0) + { + if (i+2>=argc) { incorrectArguments(); } + unsigned int numberOfValues=atoi(argv[i+1]); + float deviation=atof(argv[i+2]); + srand(time(NULL)); + if (i+2+numberOfValues>=argc) { incorrectArguments(); } else + { + if ( + !bvh_PerturbJointAngles( + &bvhMotion, + numberOfValues, + deviation, + argv, + i+2 + ) + ) { haltOnError(immediatelyHaltOnError,"Error while perturbing joint angles"); } + } + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--randomizeMID")==0) + { + //------------------------------------------ + BVHMotionChannelID mID=atoi(argv[i+1]); + float startOfRandomization=atof(argv[i+2]); + float endOfRandomization=atof(argv[i+3]); + //------------------------------------------ + if ( + !bvh_RandomizeSingleMIDInRange( + &bvhMotion, + mID, + startOfRandomization, + endOfRandomization + ) + ) { haltOnError(immediatelyHaltOnError,"Error while randomizing a single mID angle"); } + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--randomizeJointAngles")==0) + { + // ./BVHTester --from Motions/DAZFriendlyCMUPlusHeadAndHandsAndFeet.bvh --repeat 100 --randomizeJointAngles 15 -65 0 1 finger5-1.l finger5-2.l finger5-3.l finger4-1.l finger4-2.l finger4-3.l finger3-1.l finger3-2.l finger3-3.l finger2-1.l finger2-2.l finger2-3.l finger1-1.l finger1-2.l finger1-3.l --randomizeJointAngles 5 -90 0 1 finger5-1.l finger4-1.l finger3-1.l finger2-1.l finger1-1.l --randomizeJointAngles 2 -75 75 2 finger1-1.l finger1-2.l --randomizeJointAngles 1 -45 0 3 finger1-1.l --bvh restR.bvh + // ./BVHTester --from Motions/DAZFriendlyCMUPlusHeadAndHandsAndFeet.bvh --repeat 100 --randomizeJointAngles 15 0 65 1 finger5-1.r finger5-2.r finger5-3.r finger4-1.r finger4-2.r finger4-3.r finger3-1.r finger3-2.r finger3-3.r finger2-1.r finger2-2.r finger2-3.r finger1-1.r finger1-2.r finger1-3.r --randomizeJointAngles 5 0 90 1 finger5-1.r finger4-1.r finger3-1.r finger2-1.r finger1-1.r --randomizeJointAngles 2 -75 75 2 finger1-1.r finger1-2.r --randomizeJointAngles 1 0 45 3 finger1-1.r --bvh restR.bvh + if (i+2>=argc) { incorrectArguments(); } + unsigned int numberOfValues=atoi(argv[i+1]); + float startOfRandomization=atof(argv[i+2]); + float endOfRandomization=atof(argv[i+3]); + unsigned int specificChannelRandomization=atoi(argv[i+4]); + srand(time(NULL)); + if (i+2+numberOfValues>=argc) { incorrectArguments(); } else + { + if ( + !bvh_PerturbJointAnglesRange( + &bvhMotion, + numberOfValues, + startOfRandomization, + endOfRandomization, + specificChannelRandomization, + argv, + i+4 + ) + ) { haltOnError(immediatelyHaltOnError,"Error while randomizing joint angles"); } + } + //exit(0); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--importCSVPoses")==0) + { + //./BVHTester --from lhand.qbvh --importCSVPoses sobolLHand_131072.csv + if (i+1>=argc) { incorrectArguments(); } + const char * filenameOfCSVFile=argv[i+1]; + fprintf(stderr,"bvh_ImportCSVPoses(%s)\n",filenameOfCSVFile); + srand(time(NULL)); + if ( + !bvh_ImportCSVPoses( + &bvhMotion, + filenameOfCSVFile + ) + ) { haltOnError(immediatelyHaltOnError,"Error while importing CSV poses"); } + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--randomizeBasedOnIKConstrains")==0) + { + // ./BVHTester --from dataset/lhand.qbvh --repeat 100 --randomizeBasedOnIKConstrains lhand --bvh restR.bvh + if (i+1>=argc) { incorrectArguments(); } + const char * nameOfIKProblem=argv[i+1]; + fprintf(stderr,"bvh_RandomizeBasedOnIKProblem(%s)\n",nameOfIKProblem); + srand(time(NULL)); + if ( + !bvh_RandomizeBasedOnIKProblem( + &bvhMotion, + nameOfIKProblem + ) + ) { haltOnError(immediatelyHaltOnError,"Error while randomizing joint angles based on IK problem"); } + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--selectJoints")==0) + { + if (i+2>=argc) { incorrectArguments(); } + unsigned int includeEndJoints=atoi(argv[i+1]); + unsigned int numberOfValues=atoi(argv[i+2]); + if (i+2+numberOfValues>=argc) { incorrectArguments(); } else + { + if ( + !bvh_selectJoints( + &bvhMotion, + numberOfValues, + includeEndJoints,//include End Joints + argv, + i+2 + ) + ) { haltOnError(immediatelyHaltOnError,"Error while selecting Joints"); } + } + } else + //----------------------------------------------------- + //----------------------------------------------------- + if (strcmp(argv[i],"--hide2DLocationOfJoints")==0) + { + if (i+2>=argc) { incorrectArguments(); } + unsigned int includeEndJoints=atoi(argv[i+1]); + unsigned int numberOfValues=atoi(argv[i+2]); + if (i+2+numberOfValues>=argc) { incorrectArguments(); } else + { + if ( + !bvh_selectJointsToHide2D( + &bvhMotion, + numberOfValues, + includeEndJoints, + argv, + i+2 + ) + ) { haltOnError(immediatelyHaltOnError,"Error while selecting 2D Joints"); } + } + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--eraseJoints")==0) + { + if (i+1>=argc) { incorrectArguments(); } + unsigned int numberOfValues=atoi(argv[i+1]); + if (i+1+numberOfValues>=argc) { incorrectArguments(); } else + { + if ( + !bvh_eraseJoints( + &bvhMotion, + numberOfValues, + 1,//include End Joints + argv, + i+1 + ) + ) { haltOnError(immediatelyHaltOnError,"Error while selecting joints to erase"); } + } + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--randomize2D")==0) + { + // ./BVHTester --from Motions/02_03.bvh --randomize2D 1400 5000 -35 -90 -35 35 90 35 --occlusions --svg tmp/ + if (i+8>=argc) { incorrectArguments(); } + float minimumRotation[3]; + float maximumRotation[3]; + + //Randomize 2D expects millimeters and converts them to centimeters internally + float minimumDepth=-1*atof(argv[i+1])/10; + float maximumDepth=-1*atof(argv[i+2])/10; + //---- + minimumRotation[0]=atof(argv[i+3]); + minimumRotation[1]=atof(argv[i+4]); + minimumRotation[2]=atof(argv[i+5]); + //---- + maximumRotation[0]=atof(argv[i+6]); + maximumRotation[1]=atof(argv[i+7]); + maximumRotation[2]=atof(argv[i+8]); + //---- + + if (bvhMotion.jointHierarchy[bvhMotion.rootJointID].hasQuaternionRotation) + { //BVH Quaternion + fprintf(stderr,"Quaternion rotations handled in bvh_RandomizeRotationsOfFrameBasedOn3D using euler2Quaternions..!\n"); + } + + bvh_RandomizePositionFrom2D( + &bvhMotion, + minimumRotation, + maximumRotation, + minimumDepth, + maximumDepth, + renderingConfiguration.fX, + renderingConfiguration.fY, + renderingConfiguration.cX, + renderingConfiguration.cY, + renderingConfiguration.width, + renderingConfiguration.height + ); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--randomize")==0) + { + if (i+12>=argc) { incorrectArguments(); } + srand(time(NULL)); + + float minimumPosition[3]; + float minimumRotation[3]; + float maximumPosition[3]; + float maximumRotation[3]; + + //---- + minimumPosition[0]=-1*atof(argv[i+1])/10; + minimumPosition[1]=-1*atof(argv[i+2])/10; + minimumPosition[2]=-1*atof(argv[i+3])/10; + //---- + minimumRotation[0]=atof(argv[i+4]); + minimumRotation[1]=atof(argv[i+5]); + minimumRotation[2]=atof(argv[i+6]); + //---- + maximumPosition[0]=-1*atof(argv[i+7])/10; + maximumPosition[1]=-1*atof(argv[i+8])/10; + maximumPosition[2]=-1*atof(argv[i+9])/10; + //---- + maximumRotation[0]=atof(argv[i+10]); + maximumRotation[1]=atof(argv[i+11]); + maximumRotation[2]=atof(argv[i+12]); + + + bvh_RandomizePositionRotation( + &bvhMotion, + minimumPosition, + minimumRotation, + maximumPosition, + maximumRotation + ); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--randomize2Dranges")==0) + { + // ./BVHTester --from Motions/02_03.bvh --randomize2Dranges 1400 5000 -35 -179.999999 -35 35 -90 35 -35 90 -35 35 180 35 --occlusions --svg tmp/ + if (i+14>=argc) { incorrectArguments(); } + float minimumRotationRangeA[3]; + float maximumRotationRangeA[3]; + float minimumRotationRangeB[3]; + float maximumRotationRangeB[3]; + + float minimumDepth=-1*atof(argv[i+1])/10; + float maximumDepth=-1*atof(argv[i+2])/10; + //---- + minimumRotationRangeA[0]=atof(argv[i+3]); + minimumRotationRangeA[1]=atof(argv[i+4]); + minimumRotationRangeA[2]=atof(argv[i+5]); + //---- + maximumRotationRangeA[0]=atof(argv[i+6]); + maximumRotationRangeA[1]=atof(argv[i+7]); + maximumRotationRangeA[2]=atof(argv[i+8]); + //---- + minimumRotationRangeB[0]=atof(argv[i+9]); + minimumRotationRangeB[1]=atof(argv[i+10]); + minimumRotationRangeB[2]=atof(argv[i+11]); + //---- + maximumRotationRangeB[0]=atof(argv[i+12]); + maximumRotationRangeB[1]=atof(argv[i+13]); + maximumRotationRangeB[2]=atof(argv[i+14]); + //---- + + bvh_RandomizePositionFrom2DRotation2Ranges( + &bvhMotion, + minimumRotationRangeA, + maximumRotationRangeA, + minimumRotationRangeB, + maximumRotationRangeB, + minimumDepth, + maximumDepth, + renderingConfiguration.fX, + renderingConfiguration.fY, + renderingConfiguration.cX, + renderingConfiguration.cY, + renderingConfiguration.width, + renderingConfiguration.height + ); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--randomizeranges")==0) + { + if (i+24>=argc) { incorrectArguments(); } + srand(time(NULL)); + + float minimumPositionRangeA[3]; + float minimumRotationRangeA[3]; + float maximumPositionRangeA[3]; + float maximumRotationRangeA[3]; + + float minimumPositionRangeB[3]; + float minimumRotationRangeB[3]; + float maximumPositionRangeB[3]; + float maximumRotationRangeB[3]; + + //---- + minimumPositionRangeA[0]=-1*atof(argv[i+1])/10; + minimumPositionRangeA[1]=-1*atof(argv[i+2])/10; + minimumPositionRangeA[2]=-1*atof(argv[i+3])/10; + //---- + minimumRotationRangeA[0]=atof(argv[i+4]); + minimumRotationRangeA[1]=atof(argv[i+5]); + minimumRotationRangeA[2]=atof(argv[i+6]); + //---- + maximumPositionRangeA[0]=-1*atof(argv[i+7])/10; + maximumPositionRangeA[1]=-1*atof(argv[i+8])/10; + maximumPositionRangeA[2]=-1*atof(argv[i+9])/10; + //---- + maximumRotationRangeA[0]=atof(argv[i+10]); + maximumRotationRangeA[1]=atof(argv[i+11]); + maximumRotationRangeA[2]=atof(argv[i+12]); + + //---- + minimumPositionRangeB[0]=-1*atof(argv[i+13])/10; + minimumPositionRangeB[1]=-1*atof(argv[i+14])/10; + minimumPositionRangeB[2]=-1*atof(argv[i+15])/10; + //---- + minimumRotationRangeB[0]=atof(argv[i+16]); + minimumRotationRangeB[1]=atof(argv[i+17]); + minimumRotationRangeB[2]=atof(argv[i+18]); + //---- + maximumPositionRangeB[0]=-1*atof(argv[i+19])/10; + maximumPositionRangeB[1]=-1*atof(argv[i+20])/10; + maximumPositionRangeB[2]=-1*atof(argv[i+21])/10; + //---- + maximumRotationRangeB[0]=atof(argv[i+22]); + maximumRotationRangeB[1]=atof(argv[i+23]); + maximumRotationRangeB[2]=atof(argv[i+24]); + + bvh_RandomizePositionRotation2Ranges( + &bvhMotion, + minimumPositionRangeA, + minimumRotationRangeA, + maximumPositionRangeA, + maximumRotationRangeA, + minimumPositionRangeB, + minimumRotationRangeB, + maximumPositionRangeB, + maximumRotationRangeB + ); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--to")==0) + { + //./BVHTester --from Motions/02_03.bvh --to Motions/cmuTomakehuman.profile test.conf + if (i+2>=argc) { incorrectArguments(); } + const char * retargetProfile=argv[i+1];//"Motions/cmu.profile"; + const char * toSceneFile=argv[i+2]; + //toSceneFileTRI + + struct bvhToTRI bvhtri={0}; + bvh_loadBVHToTRIAssociationFile(retargetProfile,&bvhtri,&bvhMotion); + dumpBVHToTrajectoryParserTRI(toSceneFileTRI,&bvhMotion,&bvhtri,1/*USE Irugubak oisutuib*/,0); + dumpBVHToTrajectoryParserPrimitives(toSceneFile,&bvhMotion); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--studymid")==0) + { + BVHFrameID fID = atoi(argv[i+1]); + BVHMotionChannelID mID = atoi(argv[i+2]); + float minRange = -180.0; + float maxRange = 180.0; + float resolution = 3.0; + + fprintf(stderr,"abvh_studyMID2DImpact(%u,%u,%0.2f,%0.2f)\n",fID,mID,minRange,maxRange); + bvh_studyMID2DImpact( + &bvhMotion, + &renderingConfiguration, + fID, + mID, + &minRange, + &maxRange, + &resolution + ); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--study3d")==0) + { + BVHFrameID fID = atoi(argv[i+1]); + BVHMotionChannelID jID = atoi(argv[i+2]); + float minRange = -180.0; + float maxRange = 180.0; + float resolution = 6.0; + bvh_study3DJoint2DImpact( + &bvhMotion, + &renderingConfiguration, + fID, + jID, + &minRange, + &maxRange, + &resolution + ); + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--bvh")==0) + { + if (i+1>=argc) { incorrectArguments(); } + const char * toBVHFile=argv[i+1]; + if ( + !dumpBVHToBVH( + toBVHFile, + &bvhMotion, + 1, //Write Hierarchy + 1 //Write Motion + ) + ) { haltOnError(immediatelyHaltOnError,"Error while outputing a BVH file.."); } + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--angleheatmap")==0) + { + convertToAngleHeatmap=1; + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--csv")==0) + { + if (i+3>=argc) { incorrectArguments(); } + toSVGDirectory=argv[i+1]; + toCSVFilename=argv[i+2]; + convertToCSV=1; + if (strcmp(argv[i+3],"2d+3d+bvh")==0){ useCSV_2D_Output=1; useCSV_3D_Output=1; useCSV_BVH_Output=1; } else + if (strcmp(argv[i+3],"2d+bvh")==0 ) { useCSV_2D_Output=1; useCSV_3D_Output=0; useCSV_BVH_Output=1; } else + if (strcmp(argv[i+3],"2d")==0 ) { useCSV_2D_Output=1; useCSV_3D_Output=0; useCSV_BVH_Output=0; } else + if (strcmp(argv[i+3],"3d")==0 ) { useCSV_2D_Output=0; useCSV_3D_Output=1; useCSV_BVH_Output=0; } else + if (strcmp(argv[i+3],"bvh")==0 ) { useCSV_2D_Output=0; useCSV_3D_Output=0; useCSV_BVH_Output=1; } else + { useCSV_2D_Output=1; useCSV_3D_Output=1; useCSV_BVH_Output=1; } + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--json")==0) + { + convertToJSON=1; + if (i+2>=argc) { incorrectArguments(); } + toSVGDirectory=argv[i+1]; + toCSVFilename=argv[i+2]; + //if (i+3>=argc) { incorrectArguments(); } + //if (strcmp(argv[i+3],"2d+bvh")==0 ) { useCSV_2D_Output=1; useCSV_3D_Output=0; useCSV_BVH_Output=1; } else + //if (strcmp(argv[i+3],"2d")==0 ) { useCSV_2D_Output=1; useCSV_3D_Output=0; useCSV_BVH_Output=0; } else + //if (strcmp(argv[i+3],"3d")==0 ) { useCSV_2D_Output=0; useCSV_3D_Output=1; useCSV_BVH_Output=0; } else + //if (strcmp(argv[i+3],"bvh")==0 ) { useCSV_2D_Output=0; useCSV_3D_Output=0; useCSV_BVH_Output=1; } else + // { useCSV_2D_Output=1; useCSV_3D_Output=1; useCSV_BVH_Output=1; } + } else + //----------------------------------------------------- + if (strcmp(argv[i],"--svg")==0) + { + if (i+1>=argc) { incorrectArguments(); } + toSVGDirectory=argv[i+1]; + + char removeOldSVGFilesCommand[512]; + snprintf(removeOldSVGFilesCommand,512,"rm %s/*.svg",toSVGDirectory); + int res = system(removeOldSVGFilesCommand); + if (res!=0) { fprintf(stderr,"Could not clean svg files in %s",toSVGDirectory); } + convertToSVG=1; + } + //----------------------------------------------------- + /* else + //Check for incorrect input, this needs to become a smarter check + { + if ((i>0) && (argv[i][0]!="-")) + { + fprintf(stderr,RED "Unidentified argument %u = %s ..!" NORMAL,i,argv[i]); + incorrectArguments(); + printCallingParameters(argc,argv); + } + }*/ + } + + //SVG or CSV output .. + if ( (convertToJSON) || (convertToSVG) || (convertToCSV) ) + { + struct filteringResults filterStats={0}; + + dumpBVHTo_JSON_SVG_CSV( + toSVGDirectory, + toCSVFilename, + convertToJSON, + convertToSVG, + convertToCSV, + convertToAngleHeatmap, + useCSV_2D_Output,useCSV_3D_Output,useCSV_BVH_Output, + wipe_2D_Output,wipe_3D_Output,wipe_BVH_Output, + &bvhMotion, + &renderingConfiguration, + &filterStats, + sampleSkip, + occlusions, + filterOccludedJoints, + filterBehindCamera,//Filter out all poses where even one joint is behind camera + filterIfAnyJointOutsideof2DFrame,//Filter out all poses where even one joint is outside of 2D frame + filterTopWeirdRandomSkeletons,//Filter top left weird random skelingtons ( skeletons ) + 0//We don't want to convert to radians + ); + } + + + bvh_free(&bvhMotion); + + return 0; +} + + +#ifndef BVH_USE_AS_A_LIBRARY +int main(int argc,const char **argv) +{ + srand(time(NULL)); // randomize seed + fprintf(stderr,"BVH Loader code - v%s\n\n",BVH_LOADER_VERSION_STRING); + return bvhConverter(argc,argv); +} +#endif // BVH_USE_AS_A_LIBRARY + diff --git a/src/python/mnet4/BVH/makeLibrary.sh b/src/python/mnet4/BVH/makeLibrary.sh new file mode 100755 index 0000000..c8c3a2e --- /dev/null +++ b/src/python/mnet4/BVH/makeLibrary.sh @@ -0,0 +1,130 @@ +#!/bin/bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "$DIR" + +echo "JIT Python/C Compilation *made by AmmarTM* handled by : " +gcc --version + +#in case of a build after ./batherFiles.sh +BVHTESTER_DIR="opengl_acquisition_shared_library/opengl_depth_and_color_renderer/src/Applications/BVHTester" +AMMATRIX_DIRECTORY="tools/AmMatrix" +MODELLOADER_DIRECTORY="opengl_acquisition_shared_library/opengl_depth_and_color_renderer/src/Library/ModelLoader" +BVH_DIRECTORY="opengl_acquisition_shared_library/opengl_depth_and_color_renderer/src/Library/MotionCaptureLoader" +INPUTPARSER_DIRECTORY="opengl_acquisition_shared_library/opengl_depth_and_color_renderer/src/Library/TrajectoryParser" + +BVHTESTER_DIR="." +AMMATRIX_DIRECTORY="../../../../../tools/AmMatrix" +MODELLOADER_DIRECTORY="../../Library/ModelLoader" +BVH_DIRECTORY="../../Library/MotionCaptureLoader" +INPUTPARSER_DIRECTORY="../../Library/TrajectoryParser" + +SOURCE=" +$AMMATRIX_DIRECTORY/matrix3x3Tools.c +$AMMATRIX_DIRECTORY/matrix3x3Tools.h +$AMMATRIX_DIRECTORY/matrix4x4Tools.c +$AMMATRIX_DIRECTORY/matrix4x4Tools.h +$AMMATRIX_DIRECTORY/matrixCalculations.c +$AMMATRIX_DIRECTORY/matrixCalculations.h +$AMMATRIX_DIRECTORY/matrixOpenGL.c +$AMMATRIX_DIRECTORY/matrixOpenGL.h +$AMMATRIX_DIRECTORY/quaternions.c +$AMMATRIX_DIRECTORY/quaternions.h +$AMMATRIX_DIRECTORY/simpleRenderer.c +$AMMATRIX_DIRECTORY/simpleRenderer.h +$AMMATRIX_DIRECTORY/solveLinearSystemGJ.c +$AMMATRIX_DIRECTORY/solveLinearSystemGJ.h +$MODELLOADER_DIRECTORY/hardcoded_shapes.h +$MODELLOADER_DIRECTORY/model_converter.c +$MODELLOADER_DIRECTORY/model_converter.h +$MODELLOADER_DIRECTORY/model_editor.c +$MODELLOADER_DIRECTORY/model_editor.h +$MODELLOADER_DIRECTORY/model_loader.h +$MODELLOADER_DIRECTORY/model_loader_assimp.h +$MODELLOADER_DIRECTORY/model_loader_hardcoded.h +$MODELLOADER_DIRECTORY/model_loader_obj.h +$MODELLOADER_DIRECTORY/model_loader_setup.h +$MODELLOADER_DIRECTORY/model_loader_transform_joints.c +$MODELLOADER_DIRECTORY/model_loader_transform_joints.h +$MODELLOADER_DIRECTORY/model_loader_tri.c +$MODELLOADER_DIRECTORY/model_loader_tri.h +$MODELLOADER_DIRECTORY/model_processor.c +$MODELLOADER_DIRECTORY/model_processor.h +$MODELLOADER_DIRECTORY/tri_bvh_controller.h +$BVH_DIRECTORY/bvh_loader.c +$BVH_DIRECTORY/bvh_loader.h +$BVH_DIRECTORY/calculate/bvh_project.c +$BVH_DIRECTORY/calculate/bvh_project.h +$BVH_DIRECTORY/calculate/bvh_to_tri_pose.c +$BVH_DIRECTORY/calculate/bvh_to_tri_pose.h +$BVH_DIRECTORY/calculate/smoothing.c +$BVH_DIRECTORY/calculate/smoothing.h +$BVH_DIRECTORY/calculate/bvh_transform.c +$BVH_DIRECTORY/calculate/bvh_transform.h +$BVH_DIRECTORY/edit/bvh_cut_paste.c +$BVH_DIRECTORY/edit/bvh_cut_paste.h +$BVH_DIRECTORY/edit/bvh_filter.c +$BVH_DIRECTORY/edit/bvh_filter.h +$BVH_DIRECTORY/edit/bvh_interpolate.c +$BVH_DIRECTORY/edit/bvh_interpolate.h +$BVH_DIRECTORY/edit/bvh_merge.c +$BVH_DIRECTORY/edit/bvh_merge.h +$BVH_DIRECTORY/edit/bvh_randomize.c +$BVH_DIRECTORY/edit/bvh_randomize.h +$BVH_DIRECTORY/edit/bvh_remapangles.c +$BVH_DIRECTORY/edit/bvh_remapangles.h +$BVH_DIRECTORY/edit/bvh_rename.c +$BVH_DIRECTORY/edit/bvh_rename.h +$BVH_DIRECTORY/edit/cTextFileToMemory.h +$BVH_DIRECTORY/export/bvh_export.c +$BVH_DIRECTORY/export/bvh_export.h +$BVH_DIRECTORY/export/bvh_to_bvh.c +$BVH_DIRECTORY/export/bvh_to_bvh.h +$BVH_DIRECTORY/export/bvh_to_c.c +$BVH_DIRECTORY/export/bvh_to_c.h +$BVH_DIRECTORY/export/bvh_to_csv.c +$BVH_DIRECTORY/export/bvh_to_csv.h +$BVH_DIRECTORY/export/bvh_to_svg.c +$BVH_DIRECTORY/export/bvh_to_svg.h +$BVH_DIRECTORY/export/bvh_to_json.c +$BVH_DIRECTORY/export/bvh_to_json.h +$BVH_DIRECTORY/export/bvh_to_trajectoryParserPrimitives.c +$BVH_DIRECTORY/export/bvh_to_trajectoryParserPrimitives.h +$BVH_DIRECTORY/export/bvh_to_trajectoryParserTRI.c +$BVH_DIRECTORY/export/bvh_to_trajectoryParserTRI.h +$BVH_DIRECTORY/ik/bvh_inverseKinematics.c +$BVH_DIRECTORY/ik/bvh_inverseKinematics.h +$BVH_DIRECTORY/ik/hardcodedProblems_inverseKinematics.c +$BVH_DIRECTORY/ik/hardcodedProblems_inverseKinematics.h +$BVH_DIRECTORY/ik/levmar.c +$BVH_DIRECTORY/ik/levmar.h +$BVH_DIRECTORY/import/fromBVH.c +$BVH_DIRECTORY/import/fromBVH.h +$BVH_DIRECTORY/metrics/bvh_measure.c +$BVH_DIRECTORY/metrics/bvh_measure.h +$BVH_DIRECTORY/tests/test.c +$BVH_DIRECTORY/tests/test.h +$INPUTPARSER_DIRECTORY/InputParser_C.c +$INPUTPARSER_DIRECTORY/InputParser_C.h +$BVHTESTER_DIR/bvhLibrary.h +$BVHTESTER_DIR/bvhConverter.c +" + +#$BVHTESTER_DIR/main.c <- This used to be in the same binary with the BVHTester utility, now its split.. + +INTEL_OPTIMIZATIONS=`cat /proc/cpuinfo | grep sse3` + +if [ -z "$var" ] ; then + echo "No intel optimizations available" + EXTRA_FLAGS=" " +else + echo "Intel Optimizations available and will be used" + EXTRA_FLAGS="-DINTEL_OPTIMIZATIONS" +fi + + +gcc -shared -o libBVHConverter.so -O3 -fPIC $EXTRA_FLAGS -march=native -mtune=native -lm -DBVH_USE_AS_A_LIBRARY $SOURCE + + + +exit 0 diff --git a/src/python/mnet4/EDM.py b/src/python/mnet4/EDM.py new file mode 100755 index 0000000..f5e7497 --- /dev/null +++ b/src/python/mnet4/EDM.py @@ -0,0 +1,75 @@ +#!/usr/bin/python3 + +""" +Author : "Ammar Qammaz" +Copyright : "2022 Foundation of Research and Technology, Computer Science Department Greece, See license.txt" +License : "FORTH" +""" + +import numpy as np +from enum import Enum +from NSDM import getCompositeLabel,getCompositePoint,getJoint2DDistancePoints + +def EDMLabels(rules): + result=list() + numberOfNSDMRules=len(rules['NSDM']) + print("Rules Number ",numberOfNSDMRules) + + for i in range(0,numberOfNSDMRules): + #----------------------------------------------------------------- + labelI = getCompositeLabel( + rules['NSDM'][i]['joint'], + rules['NSDM'][i]['halfWayFromThisAnd'], + rules['NSDM'][i]['xOffset'], + rules['NSDM'][i]['yOffset'], + rules['NSDM'][i]['isVirtual'] + ) + #----------------------------------------------------------------- + for j in range(0,numberOfNSDMRules): + if (i>j): + #----------------------------------------------------------------- + labelJ = getCompositeLabel( + rules['NSDM'][j]['joint'], + rules['NSDM'][j]['halfWayFromThisAnd'], + rules['NSDM'][j]['xOffset'], + rules['NSDM'][j]['yOffset'], + rules['NSDM'][j]['isVirtual'] + ) + #----------------------------------------------------------------- + result.append("EDM-%sY-%sY-Distance"%(labelI,labelJ)) + + #print("EDM matrix will look like this ",result) + return result; + + +def createEDMUsingRules(rules,thisInput): + result=list() + #----------------------------------------------------------------------------------------------------- + if (len(thisInput)==0): + print("createNSDMUsingRules called with no input") + return result + + if (not rules['inputJointMap'].checkJointListDimensions(thisInput)): + print("createNSDMUsingRules called with incorrect input size ") + return thisInput + #----------------------------------------------------------------------------------------------------- + numberOfNSDMRules=len(rules['NSDM']) + for i in range(0,numberOfNSDMRules): + iX,iY,iVisibility,iInvalidPoint = getCompositePoint(rules,i,thisInput) + for j in range(0,numberOfNSDMRules): + if (i>j): + # Ensure that each distance is computed only once since the EDM is a symmetric matrix. + #--------------------------------------------------------------------------- + jX,jY,jVisibility,jInvalidPoint = getCompositePoint(rules,j,thisInput) + if (iInvalidPoint or jInvalidPoint): #Changed to or 17/5/23 <- Why was this AND and not OR ? also C++ EDM.h code + result.append(np.float32(0.0)) + else: + result.append(getJoint2DDistancePoints(iX,iY,jX,jY)) + #--------------------------------------------------------------------------- + return result + + + + +if __name__ == '__main__': + print("EDM.py is a library it cannot be run standalone") diff --git a/src/python/mnet4/EigenPoses.py b/src/python/mnet4/EigenPoses.py new file mode 100755 index 0000000..3338575 --- /dev/null +++ b/src/python/mnet4/EigenPoses.py @@ -0,0 +1,68 @@ +#!/usr/bin/python3 + +""" +Author : "Ammar Qammaz" +Copyright : "2022 Foundation of Research and Technology, Computer Science Department Greece, See license.txt" +License : "FORTH" +""" + +import math +import sys +from enum import Enum +from NSDM import getCompositeLabel,getCompositePoint,getJoint2DDistancePoints + + +def EigenPoseLabels(rules): + result=list() + if ('eigenPoseData' in rules) and ('eigenPoses' in rules) and (int(rules['eigenPoses'])==1): + numberOfEigenPoseRules=len(rules['eigenPoseData']['in']) + print("EigenPoses Rules Number ",numberOfEigenPoseRules) + + for i in range(0,numberOfEigenPoseRules): + #----------------------------------------------------------------- + result.append("EigenPose-%u"%(i)) + #----------------------------------------------------------------- + return result; + + +def computeVectorSimilarity_SAD(vec1,vec2): + loss=float(0.0) + if (len(vec1)==len(vec2)): + #If we are here the vectors have the same size + for i in range(0,len(vec1)): + loss = loss + abs(vec1[i]-vec2[i]) + return loss + +def computeVectorSimilarity_MAD(vec1,vec2): + loss=float(0.0) + if (len(vec1)==len(vec2)): + #If we are here the vectors have the same size + for i in range(0,len(vec1)): + loss = loss + abs(vec1[i]-vec2[i]) + return loss/len(vec1) + +def computeVectorSimilarity(vec1,vec2,mode="MAD"): + if (len(vec1)!=len(vec2)): + print("EigenPoses.py: Asked for similarity on Vectors with Lengths",len(vec1)," vs ",len(vec2)) + sys.exit(0) + return float("nan") + #If we are here the vectors have the same size + if (mode=="SAD"): + return computeVectorSimilarity_SAD(vec1,vec2) + if (mode=="MAD"): + return computeVectorSimilarity_MAD(vec1,vec2) + +def createEigenPosesUsingRules(rules,thisInput): + result=list() + if ('eigenPoseData' in rules) and ('eigenPoses' in rules) and (int(rules['eigenPoses'])==1): + numberOfEigenPoseRules=len(rules['eigenPoseData']['in']) + for i in range(0,numberOfEigenPoseRules): + result.append(computeVectorSimilarity(thisInput,rules['eigenPoseData']['in'][i])) + return result + + + + + +if __name__ == '__main__': + print("EigenPoses.py is a library it cannot be run standalone") diff --git a/src/python/mnet4/MocapNET.py b/src/python/mnet4/MocapNET.py new file mode 100755 index 0000000..9a07220 --- /dev/null +++ b/src/python/mnet4/MocapNET.py @@ -0,0 +1,953 @@ +#!/usr/bin/python3 +#test +""" +Author : "Ammar Qammaz" +Copyright : "2022 Foundation of Research and Technology, Computer Science Department Greece, See license.txt" +License : "FORTH" +""" + +#------------------------------------------------------------------------------------------- +from readCSV import parseConfiguration,parseConfigurationInputJointMap,transformNetworkInput,initializeDecompositionForExecutionEngine,readGroundTruthFile,readCSVFile,parseOutputNormalization +from NSDM import NSDMLabels,createNSDMUsingRules,inputIsEnoughToCreateNSDM,performNSRMAlignment +from EDM import EDMLabels,createEDMUsingRules +from tools import bcolors,checkIfFileExists,readListFromFile,convertListToLowerCase,secondsToHz,getEntryIndexInList +#------------------------------------------------------------------------------------------- +from BVH.bvhConverter import BVH +#------------------------------------------------------------------------------------------- +from Smooth.smoothing import Smooth +#------------------------------------------------------------------------------------------- +from principleComponentAnalysis import PCA +#------------------------------------------------------------------------------------------- + +#------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------- +import time +import os +import numpy as np +#------------------------------------------------------------------------------------------- +class MocapNETEnsembleCombination(): + def __init__(self): + self.ensembleNameList = list() + self.ensemblePathList = list() + def addEnsemble(self,name:str,path:str): + self.ensembleNameList.append(name) + self.ensemblePathList.append(path) +#------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------- +def checkIfAllListedElementsExistInDict(theList,theDict): + for element in theList: + if not element in theDict: + return False + return True +#------------------------------------------------------------------------------------------- +def checkIfAnyListedElementsExistsInString(theList,theString): + #-------------------------- + if (len(theList)==0): + return False + #-------------------------- + for element in theList: + if element in theString: + return True + return False +#------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------- +def getSymmetricLEyeOutputs(): + #I AM NOT AT ALL SURE THE FOLLOWING ARE CORRECT.. + bn=dict() + #--------------------------------------------------- + #These are the actual useful outputs.. that REye has.. + bn["hip_xposition"] = (0.0,"") #0 #ignored + bn["hip_yposition"] = (0.0,"") #1 #ignored + bn["hip_zposition"] = (0.0,"") #2 #ignored + bn["neck_zrotation"] = (0.0,"") #3 #ignored + bn["neck_xrotation"] = (0.0,"") #4 #ignored + bn["neck_yrotation"] = (0.0,"") #5 #ignored + bn["eye.r_zrotation"] = (1.0,"eye.l_zrotation") # + bn["eye.r_xrotation"] = (1.0,"eye.l_xrotation") # + bn["oculi01.r_zrotation"] = (1.0,"oculi01.l_zrotation") # + bn["orbicularis03.r_xrotation"] = (1.0,"orbicularis03.l_xrotation") # + bn["jaw_xrotation"] = (0.0,"") # + bn["jaw_yrotation"] = (0.0,"") # + #--------------------------------------------------- + #The rest should all be ignored ? + #--------------------------------------------------- + bn["oculi01.l_zrotation"] = (0.0,"") #ignored + bn["eye.l_zrotation"] = (0.0,"") # ignored + bn["eye.l_xrotation"] = (0.0,"") # ignored + bn["orbicularis04.r_xrotation"] = (0.0,"") #ignored + bn["orbicularis03.r_yrotation"] = (0.0,"") #ignored + bn["orbicularis04.r_yrotation"] = (0.0,"") #ignored + + bn["levator06.l_xrotation"] = (0.0,"") #ignored + bn["levator06.r_xrotation"] = (0.0,"") #ignored + bn["levator03.l_zrotation"] = (0.0,"") #ignored + bn["levator03.r_zrotation"] = (0.0,"") #ignored + + bn["oris03.l_zrotation"] = (0.0,"") #ignored + bn["oris03.r_zrotation"] = (0.0,"") #ignored + bn["oris07.l_zrotation"] = (0.0,"") #ignored + bn["oris07.r_zrotation"] = (0.0,"") #ignored + + bn["oris04.l_zrotation"] = (0.0,"") #ignored + bn["oris04.r_zrotation"] = (0.0,"") #ignored + bn["oris06.l_zrotation"] = (0.0,"") #ignored + bn["oris06.r_zrotation"] = (0.0,"") #ignored + + bn["orbicularis03.r_yrotation"] = (0.0,"") #ignored + bn["orbicularis04.r_yrotation"] = (0.0,"") #ignored + bn["orbicularis03.l_yrotation"] = (0.0,"") #ignored + bn["orbicularis04.l_yrotation"] = (0.0,"") #ignored + + bn["orbicularis03.l_xrotation"] = (0.0,"") # ignored + bn["orbicularis04.l_xrotation"] = (0.0,"") # ignored + + bn["levator06.l_yrotation"] = (0.0,"") #ignored + bn["levator06.r_yrotation"] = (0.0,"") #ignored + + bn["oris03.l_xrotation"] = (0.0,"") #ignored + bn["oris03.l_yrotation"] = (0.0,"") #ignored + bn["oris07.l_yrotation"] = (0.0,"") #ignored + bn["oris03.r_xrotation"] = (0.0,"") #ignored + bn["oris03.r_yrotation"] = (0.0,"") #ignored + bn["oris07.r_yrotation"] = (0.0,"") #ignored + + bn["oris05_xrotation"] = (0.0,"") #ignored + bn["oris05_yrotation"] = (0.0,"") #ignored + return bn +#--------------------------------------------------- +def getSymmetricLEyeNameList(): + bn=dict() + #--------------------------------------------------- + bn["head_reye_0"] = "head_leye_3" #0 + bn["head_reye_1"] = "head_leye_2" #1 + bn["head_reye_2"] = "head_leye_1" #2 + bn["head_reye_3"] = "head_leye_0" #3 + bn["head_reye_4"] = "head_leye_5" #4 + bn["head_reye_5"] = "head_leye_4" #5 + bn["head_reyebrow_0"] = "head_leyebrow_0" #6 + bn["head_reyebrow_1"] = "head_leyebrow_1" #7 + bn["head_reyebrow_2"] = "head_leyebrow_2" #8 + bn["head_reyebrow_3"] = "head_leyebrow_3" #9 + bn["head_reyebrow_4"] = "head_leyebrow_4" #10 + bn["head_reye"] = "head_leye" #11 + bn["head_rchin_0"] = "head_lchin_0" #12 + bn["head_nostrills_2"]= "head_nostrills_2" #13 + bn["head_chin"] = "head_chin" #14 + return bn +#--------------------------------------------------- +#--------------------------------------------------- +#--------------------------------------------------- + + +def getSymmetricLHandOutputs(): + bn=dict() + #--------------------------------------------------- + bn["lhand_xposition"] = (-1.0,"rhand_xposition") #0 + bn["lhand_yposition"] = (1.0,"rhand_yposition") #1 + bn["lhand_zposition"] = (1.0,"rhand_zposition") #2 + #-------------------------------------------------------------------- + #Flip Quaternion During Symmetric output Calculations + bn["lhand_wrotation"] = (-1.0,"rhand_wrotation") #3 {-w,z,y,x} + bn["lhand_xrotation"] = ( 1.0,"rhand_zrotation") #4 + bn["lhand_yrotation"] = ( 1.0,"rhand_yrotation") #5 + bn["lhand_zrotation"] = ( 1.0,"rhand_xrotation") #6 + #-------------------------------------------------------------------- + bn["finger2-1.l_zrotation"] = (-1.0,"finger2-1.r_zrotation") #7 + bn["finger2-1.l_xrotation"] = (-1.0,"finger2-1.r_xrotation") #8 + bn["finger2-1.l_yrotation"] = (-1.0,"finger2-1.r_yrotation") #9 + bn["finger2-2.l_zrotation"] = (-1.0,"finger2-2.r_zrotation") #10 + bn["finger2-2.l_xrotation"] = (-1.0,"finger2-2.r_xrotation") #11 + bn["finger2-2.l_yrotation"] = (-1.0,"finger2-2.r_yrotation") #12 + bn["finger2-3.l_zrotation"] = (-1.0,"finger2-3.r_zrotation") #13 + bn["finger2-3.l_xrotation"] = (-1.0,"finger2-3.r_xrotation") #14 + bn["finger2-3.l_yrotation"] = (-1.0,"finger2-3.r_yrotation") #15 + #-------------------------------------------------------------------- + bn["finger3-1.l_zrotation"] = (-1.0,"finger3-1.r_zrotation") #16 + bn["finger3-1.l_xrotation"] = (-1.0,"finger3-1.r_xrotation") #17 + bn["finger3-1.l_yrotation"] = (-1.0,"finger3-1.r_yrotation") #18 + bn["finger3-2.l_zrotation"] = (-1.0,"finger3-2.r_zrotation") #19 + bn["finger3-2.l_xrotation"] = (-1.0,"finger3-2.r_xrotation") #20 + bn["finger3-2.l_yrotation"] = (-1.0,"finger3-2.r_yrotation") #21 + bn["finger3-3.l_zrotation"] = (-1.0,"finger3-3.r_zrotation") #22 + bn["finger3-3.l_xrotation"] = (-1.0,"finger3-3.r_xrotation") #23 + bn["finger3-3.l_yrotation"] = (-1.0,"finger3-3.r_yrotation") #24 + #-------------------------------------------------------------------- + bn["finger4-1.l_zrotation"] = (-1.0,"finger4-1.r_zrotation") #25 + bn["finger4-1.l_xrotation"] = (-1.0,"finger4-1.r_xrotation") #26 + bn["finger4-1.l_yrotation"] = (-1.0,"finger4-1.r_yrotation") #27 + bn["finger4-2.l_zrotation"] = (-1.0,"finger4-2.r_zrotation") #28 + bn["finger4-2.l_xrotation"] = (-1.0,"finger4-2.r_xrotation") #29 + bn["finger4-2.l_yrotation"] = (-1.0,"finger4-2.r_yrotation") #30 + bn["finger4-3.l_zrotation"] = (-1.0,"finger4-3.r_zrotation") #31 + bn["finger4-3.l_xrotation"] = (-1.0,"finger4-3.r_xrotation") #32 + bn["finger4-3.l_yrotation"] = (-1.0,"finger4-3.r_yrotation") #33 + #-------------------------------------------------------------------- + bn["finger5-1.l_zrotation"] = (-1.0,"finger5-1.r_zrotation") #34 + bn["finger5-1.l_xrotation"] = (-1.0,"finger5-1.r_xrotation") #35 + bn["finger5-1.l_yrotation"] = (-1.0,"finger5-1.r_yrotation") #36 + bn["finger5-2.l_zrotation"] = (-1.0,"finger5-2.r_zrotation") #37 + bn["finger5-2.l_xrotation"] = (-1.0,"finger5-2.r_xrotation") #38 + bn["finger5-2.l_yrotation"] = (-1.0,"finger5-2.r_yrotation") #39 + bn["finger5-3.l_zrotation"] = (-1.0,"finger5-3.r_zrotation") #40 + bn["finger5-3.l_xrotation"] = (-1.0,"finger5-3.r_xrotation") #41 + bn["finger5-3.l_yrotation"] = (-1.0,"finger5-3.r_yrotation") #42 + #-------------------------------------------------------------------- + bn["lthumbBase_zrotation"] = (-1.0,"rthumbBase_zrotation") #43 +? + bn["lthumbBase_xrotation"] = (-1.0,"rthumbBase_xrotation") #44 +? + bn["lthumbBase_yrotation"] = (-1.0,"rthumbBase_yrotation") #45 + bn["lthumb_zrotation"] = (-1.0,"rthumb_zrotation") #46 + bn["lthumb_xrotation"] = (-1.0,"rthumb_xrotation") #47 + bn["lthumb_yrotation"] = (-1.0,"rthumb_yrotation") #48 + bn["finger1-2.l_zrotation"] = (-1.0,"finger1-2.r_zrotation") #49 + bn["finger1-2.l_xrotation"] = (-1.0,"finger1-2.r_xrotation") #50 + bn["finger1-2.l_yrotation"] = (-1.0,"finger1-2.r_yrotation") #51 + bn["finger1-3.l_zrotation"] = (-1.0,"finger1-3.r_zrotation") #52 + bn["finger1-3.l_xrotation"] = (-1.0,"finger1-3.r_xrotation") #53 + bn["finger1-3.l_yrotation"] = (-1.0,"finger1-3.r_yrotation") #54 + return bn +#--------------------------------------------------- +def getSymmetricLHandNameList(): + bn=dict() + #--------------------------------------------------- + #--------------------------------------------------- + bn["lhand"] = "rhand" #0 - wrist + bn["lthumb"] = "rthumb" #1 - thumb_cmc + bn["lthumbbase"] = "rthumbbase" #1 ? - thumb_cmc + bn["finger1-2.l"] = "finger1-2.r" #2 - thumb_mcp + bn["finger1-3.l"] = "finger1-3.r" #3 - thumb_ip + bn["endsite_finger1-3.l"] = "endsite_finger1-3.r" #4 - thumb_tip + bn["finger2-1.l"] = "finger2-1.r" #5 - index_finger_mcp + bn["finger2-2.l"] = "finger2-2.r" #6 - index_finger_pip + bn["finger2-3.l"] = "finger2-3.r" #7 - index_finger_dip + bn["endsite_finger2-3.l"] = "endsite_finger2-3.r" #8 - index_finger_tip + bn["finger3-1.l"] = "finger3-1.r" #9 - middle_finger_mcp + bn["finger3-2.l"] = "finger3-2.r" #10 - middle_finger_pip + bn["finger3-3.l"] = "finger3-3.r" #11 - middle_finger_dip + bn["endsite_finger3-3.l"] = "endsite_finger3-3.r" #12 - middle_finger_tip + bn["finger4-1.l"] = "finger4-1.r" #13 - ring_finger_mcp + bn["finger4-2.l"] = "finger4-2.r" #14 - ring_finger_pip + bn["finger4-3.l"] = "finger4-3.r" #15 - ring_finger_dip + bn["endsite_finger4-3.l"] = "endsite_finger4-3.r" #16 - ring_tip + bn["finger5-1.l"] = "finger5-1.r" #17 - pinky_mcp + bn["finger5-2.l"] = "finger5-2.r" #18 - pinky_pip + bn["finger5-3.l"] = "finger5-3.r" #19 - pinky_dip + bn["endsite_finger5-3.l"] = "endsite_finger5-3.r" #20 - pinky_tip + return bn +#------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------- + + + +#------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------- +class SimulatedMirroredEnsemble(): + def __init__(self, + mirroredModel, + mirroringName, + symmetricNames = list(), + outputOperationsNeeded = list() + ): + self.mirroredModel = mirroredModel + self.partName = mirroringName + self.inputReadyForTF = np.empty([2, 1]) + self.NSRM = np.empty([2, 1]) + self.leftToRightNames = symmetricNames + self.mirroringName = mirroringName + self.outputOperationsNeeded = outputOperationsNeeded + self.outputBVH = dict() + self.outputBVHMinima = dict() + self.outputBVHMaxima = dict() + #------------------------------- + self.simulated = True + #------------------------------- + self.output = dict() + self.outputMinimumValue = dict() + self.outputMaximumValue = dict() + #------------------------------- + + def getModel(self): + return self.mirroredModel.model + + def getModelFlops(self): + return 0 + + def getModelParameters(self): + return 0 + + def test(self): + return 1 + + def prepareInput( + self, + input2D :dict, + configuration : dict + ): + self.inputReadyForTF = self.mirroredModel.predict(input2D=input2D) + self.NSRM = self.mirroredModel.NSRM + return self.inputReadyForTF + + def predict(self,input2D :dict): + #Replicating : https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/mnet3/src/MocapNET2/MocapNETLib2/solutionParts/rightHandSym.cpp + #------------------------------------------------------------ + import copy + flippedInput2D = dict() + #------------------------------------------------------------ + doFlips = True # Debug switch should alawys be set to True + #------------------------------------------------------------ + if (doFlips): #Do Input flips! + for key in input2D.keys(): + if ("2dx_" in key.lower()): + s = key.split("_",1) + if (len(s)>0): + originalName = s[1] + xKey = "2dx_%s" % originalName + yKey = "2dy_%s" % originalName + visibleKey = "visible_%s" % originalName + #----------------------------------------------------- + #----------------------------------------------------- + if originalName in self.leftToRightNames: + #print("INPUT 2D FOUND ",originalName," ",end="") #Debug + flippedName = self.leftToRightNames[originalName] + flippedXKey = "2dx_%s" % flippedName + flippedYKey = "2dy_%s" % flippedName + flippedVisibleKey = "visible_%s" % flippedName + if (flippedName == originalName): + #print("KEPT ",flippedName) #Debug + #There is no flip.. Just Copy.. + flippedInput2D[xKey] = float(input2D[flippedXKey]) + flippedInput2D[yKey] = float(input2D[flippedYKey]) + flippedInput2D[visibleKey] = float(input2D[flippedVisibleKey]) + else: + #print("FLIPPED ",flippedName) #Debug + if (flippedXKey in input2D) and (flippedYKey in input2D) and (flippedVisibleKey in input2D): + if (float(input2D[visibleKey])>0.0): + flippedInput2D[xKey] = 1.0 - float(input2D[flippedXKey]) + else: + flippedInput2D[xKey] = float(input2D[flippedXKey]) + flippedInput2D[yKey] = float(input2D[flippedYKey]) + flippedInput2D[visibleKey] = float(input2D[flippedVisibleKey]) + #---------------------------------------------------------------------------- + leftHandinputReadyForTF = copy.deepcopy(self.mirroredModel.inputReadyForTF) + leftHandinputNSRM = copy.deepcopy(self.mirroredModel.NSRM) + # =========================================================================== + self.mirroredOutput = self.mirroredModel.predict(input2D=flippedInput2D) + #print("flipped yield ",self.mirroredOutput) #Debug + # =========================================================================== + self.inputReadyForTF = copy.deepcopy(self.mirroredModel.inputReadyForTF) + self.NSRM = copy.deepcopy(self.mirroredModel.NSRM) + #---------------------------------------------------------------------------- + if (doFlips): #Do Output Flips! + for originalKeyRaw in self.mirroredOutput: + originalKey = originalKeyRaw.lower() + #print("OUTPUT 3D FOUND ",originalKey," ",end="") #Debug + if (originalKey in self.outputOperationsNeeded): + flippedKey = self.outputOperationsNeeded[originalKey][1] + flippedFactor = self.outputOperationsNeeded[originalKey][0] + if (flippedFactor!=0.0) and (flippedKey!=""): + #print("USED ",flippedKey) #Debug + self.output[flippedKey] = flippedFactor * float(self.mirroredOutput[originalKey]) + #else: #Debug + # print("IGNORED ",flippedKey) #Debug + + else: + print("SYMMETRY: THIS SHOULD NOT HAPPEN / NO RULE FOR ",originalKey,flippedKey) + #------------------------------------------------------------ + #Restore left hand + self.mirroredModel.inputReadyForTF = copy.deepcopy(leftHandinputReadyForTF) + self.mirroredModel.NSRM = copy.deepcopy(leftHandinputNSRM) + #------------------------------------------------------------ + return self.output +#------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------- + + + + +#------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------- +class MocapNET(): + def __init__(self, + #------------------------------------------------- + bvhFilePath:str = "BVH/headerWithHeadAndOneMotion.bvh", + disablePCACode = 0, + disableSmoothingCode = 0, + doPerformanceProfiling = False, + doHCDPostProcessing = 1, + hcdLearningRate = 0.1, + hcdEpochs = 20, + hcdIterations = 15, + langevinDynamics = 0.0, + bvhScale = 1.0, + addNoise = 0.0, + multiThreaded = False, + bvhLibraryPath:str = "BVH/libBVHConverter.so", + smootherLibraryPath:str = "Smooth/libSmoothing.so", + #------------------------------------------------- + engine:str = "onnx", + ensembleToLoad: MocapNETEnsembleCombination = MocapNETEnsembleCombination(), + #------------------------------------------------- + record = False + #------------------------------------------------- + ): + #------------------------------------------------------------------------------- + self.record = record + self.inputHistory = list() + self.history = list() + self.outputHistory = list() + self.ensemble = dict() + #------------------------------------------------------------------------------- + #First initialize the engine.. + if (engine=="tensorflow") or (engine=="tf"): + from MocapNETTensorflow import MocapNETTensorflow + self.engineContext = MocapNETTensorflow() + elif (engine=="tflite"): + from MocapNETTFLite import MocapNETTFLite + self.engineContext = MocapNETTFLite() + elif (engine=="onnx"): + from MocapNETONNX import MocapNETONNX + self.engineContext = MocapNETONNX() + else: + print("selectMocapNETClassBasedOnEngine: Unknown engine ",engine) + sys.exit(1) + #------------------------------------------------------------------------------- + for ensembleID in range(0,len(ensembleToLoad.ensembleNameList)): + ensembleName = ensembleToLoad.ensembleNameList[ensembleID] + partName = "%s_all" % ensembleName + ensemblePath = ensembleToLoad.ensemblePathList[ensembleID] + if (ensemblePath!="symmetric"): + print(bcolors.OKGREEN,"Loading ",ensembleName,"..",bcolors.ENDC) + configurationPath = "%s/%s_configuration.json" % (ensemblePath,ensembleName) + if (engine=="tensorflow") or (engine=="tf"): + from MocapNETTensorflow import MocapNETTensorflowSubProblem + modelPath = "%s/" % (ensemblePath) + self.ensemble[ensembleName] = MocapNETTensorflowSubProblem( + context = self.engineContext, + configPath = configurationPath, + modelPath = modelPath, + partName = partName, + completelyDisablePCACode = disablePCACode + ) + elif (engine=="tflite"): + from MocapNETTFLite import MocapNETTFLiteSubProblem + modelPath = "%s/model.tflite" % (ensemblePath) + self.ensemble[ensembleName] = MocapNETTFLiteSubProblem( + context = self.engineContext, + configPath = configurationPath, + modelPath = modelPath, + partName = partName, + completelyDisablePCACode = disablePCACode + ) + elif (engine=="onnx"): + from MocapNETONNX import MocapNETONNXSubProblem + modelPath = "%s/model.onnx" % (ensemblePath) + self.ensemble[ensembleName] = MocapNETONNXSubProblem( + context = self.engineContext, + configPath = configurationPath, + modelPath = modelPath, + partName = partName, + completelyDisablePCACode = disablePCACode + ) + elif (ensemblePath=="symmetric"): + #If we are handling a lhand we get an rhand for free :P + if (ensembleName=="rhand"): + self.ensemble["rhand"] = SimulatedMirroredEnsemble( + mirroredModel = self.ensemble["lhand"], + mirroringName = "rhand", + symmetricNames = getSymmetricLHandNameList(), + outputOperationsNeeded = getSymmetricLHandOutputs() + ) + #If we are handling a reye we get an leye for free :P + if (ensembleName=="leye"): + self.ensemble["leye"] = SimulatedMirroredEnsemble( + mirroredModel = self.ensemble["reye"], + mirroringName = "leye", + symmetricNames = getSymmetricLEyeNameList(), + outputOperationsNeeded = getSymmetricLEyeOutputs() + ) + #------------------------------------------------------------------------------- + print(bcolors.OKGREEN,"Combined network has ",self.getModelParameters()," parameters..",bcolors.ENDC) + #------------------------------------------------------------------------------- + #------------------------------------------------------------------------------- + print(bcolors.OKGREEN,"Loading C/Python libraries..",bcolors.ENDC) + self.multiThreaded = multiThreaded + self.doFineTuning = doHCDPostProcessing + self.addNoise = addNoise + self.smoothingSampling = 15.0 + self.smoothingCutoff = 5.0 + self.bvhScale = bvhScale + self.lastMAEErrorInPixels = 0.0 + if (disableSmoothingCode==1): + self.smoothingSampling = 0.0 + self.smoothingCutoff = 0.0 + self.hcdLearningRate = hcdLearningRate + self.hcdEpochs = hcdEpochs + self.hcdIterations = hcdIterations + self.langevinDynamics = langevinDynamics + self.bvhFilePath = bvhFilePath + self.bvh = BVH(bvhPath = bvhFilePath,libraryPath = bvhLibraryPath) + self.bvh.scale(self.bvhScale) + self.bvhJointList = convertListToLowerCase(self.bvh.getJointList()) + self.bvhJointParentList = self.bvh.getJointParentList() + #------------------------------------------------------------------------------- + self.incompleteUpperbodyInput = 1 + self.incompleteLowerbodyInput = 1 + #------------------------------------------------------------------------------- + self.framesProcessed = 0 + self.currentPrediction = dict() + self.previousPrediction = dict() + self.input2D = dict() + self.output = dict() + self.output2D = dict() + self.outputBVH = dict() + self.outputBVHMinima = dict() + self.outputBVHMaxima = dict() + self.output3D = dict() + + self.perfHistorySize = 30 + self.history_hz_NN = [] + self.hz_NN = 0.0 + self.history_hz_HCD = [] + self.hz_HCD = 0.0 + #------------------------------------------------------------------------------- + + + #------------------------------------------------------------------------------- + print("Caching networks : ") + self.test() + print(bcolors.OKGREEN,"MocapNET ready for use! ",bcolors.ENDC) + + def recordBVH(self,val:bool): + self.record=val + return True + + def hasEnsemble(self,name): + if (name in self.ensemble): + return True + else: + return False + + + def getUpperBodyModel(self): + return self.ensemble["upperbody"].getModel() + + def getLowerBodyModel(self): + return self.ensemble["lowerbody"].getModel() + + def getModelFlops(self): + total = 0.0 + for k in self.ensemble.keys(): + total = total + self.ensemble[k].getModelFlops() + return total + + def getModelParameters(self): + total = 0 + for k in self.ensemble.keys(): + total = total + self.ensemble[k].getModelParameters() + return total + + def test(self): + #------------------------------------------- + for k in self.ensemble.keys(): + print("Testing loaded ",k," model ") + self.ensemble[k].test() + #------------------------------------------- + + + def enforceBanlistOnOutput(self,output): + #Banlist ------------------------------------- + if "abdomen_zrotation" in output.keys(): + output["abdomen_zrotation"]=0.0 + if "abdomen_xrotation" in output.keys(): + output["abdomen_xrotation"]=0.0 + if "abdomen_yrotation" in output.keys(): + output["abdomen_yrotation"]=0.0 + #-------------------------------------------- + if "chest_zrotation" in output.keys(): + output["chest_zrotation"]=0.0 + if "chest_xrotation" in output.keys(): + output["chest_xrotation"]=0.0 + if "chest_yrotation" in output.keys(): + output["chest_yrotation"]=0.0 + #-------------------------------------------- + return output + + + + + def perturbInput(self,input2D :dict): + import random + for i in range(5): + print(bcolors.FAIL,"NOISE SCHEDULE IS ACTIVE AND SYNTHETIC NOISE IS ADDED (",self.addNoise,") ..",bcolors.ENDC) + peturbedInput2D = input2D + for coordLabel in peturbedInput2D.keys(): + #print(coordLabel) + coordLabelL = coordLabel.lower() + if ("2dx_" in coordLabelL) or ("2dy_" in coordLabelL): + if (peturbedInput2D[coordLabel]>0.0): + perturbation = random.uniform(float(-self.addNoise/2.0),float(self.addNoise/2.0)) + #print("Perturbing ",peturbedInput2D[coordLabel]," with ",perturbation," -> ",end="") + peturbedInput2D[coordLabel]=peturbedInput2D[coordLabel] + perturbation + #print(" ",peturbedInput2D[coordLabel]) + + return peturbedInput2D + + """ + Convert a dictionary of 2D inputs to MocapNET output + (Whatever that maybe [it is listed in self.inputs and self.outputs]) + """ + def predict(self,input2D :dict): + start = time.time() + #-------------------------------------------------------------------------------------- + if (self.addNoise>0.0): + input2D = self.perturbInput(input2D) + #-------------------------------------------------------------------------------------- + self.input2D = input2D + self.output = dict() + self.outputBVHMinima = dict() + self.outputBVHMaxima = dict() + #-------------------------------------------------------------------------------------- + if (self.record): + self.inputHistory.append(input2D) + #-------------------------------------------------------------------------------------- + + #print("INPUT2D : ",input2D) + for k in self.ensemble.keys(): + thisEnsemble = self.ensemble[k] + #--------------------------------------------------------------- + ensembleOutput = thisEnsemble.predict(input2D) + #print("Ensemble ",thisEnsemble.partName," : ",ensembleOutput) #Debug + #--------------------------------------------------------------- + self.output.update(ensembleOutput) + if (not thisEnsemble.simulated): + self.outputBVHMinima.update(thisEnsemble.outputMinimumValue) + self.outputBVHMaxima.update(thisEnsemble.outputMaximumValue) + #--------------------------------------------------------------- + + self.output = self.enforceBanlistOnOutput(self.output) + + self.framesProcessed = self.framesProcessed + 1 + #-------------------------------------------------------------------------------------- + end = time.time() # Time elapsed + self.hz_NN = secondsToHz(end - start) + #--------------------------------------------------------------- + self.history_hz_NN.append(self.hz_NN) + if (len(self.history_hz_NN)>self.perfHistorySize): + self.history_hz_NN.pop(0) #Keep mnet history on limits + #--------------------------------------------------------------- + + + #If we want record, record the raw BVH prediction + #print("RECORD : ",self.output) + if (self.record): + self.outputHistory.append(self.output) #This does not have HCD improvement.. + + print("\r MocapNET Wrapper NeuralNetwork Framerate : ",round(self.hz_NN,2)," fps \r", end="", flush=True) + print("\n", end="", flush=True) + + return self.output + + + """ + Convert a dictionary of 2D inputs to MocapNET output + (Whatever that maybe [it is listed in self.inputs and self.outputs]) + """ + def predictMultiThreaded(self,input2D :dict): + #---------------------------------- + if (self.multiThreaded): + start = time.time() + #-------------------------------------------------------------------------------------- + if (self.addNoise>0.0): + input2D = self.perturbInput(input2D) + #-------------------------------------------------------------------------------------- + self.input2D = input2D + self.output = dict() + self.outputBVHMinima = dict() + self.outputBVHMaxima = dict() + #-------------------------------------------------------------------------------------- + if (self.record): + self.inputHistory.append(input2D) + #-------------------------------------------------------------------------------------- + + #Create and handle thread initialization if needed.. + self.threads = [] + import threading + for k in self.ensemble.keys(): + #--------------------------------------------------------------- + thisEnsemble = self.ensemble[k] + self.threads.append(threading.Thread(target=thisEnsemble.predict, args=(self.input2D,)) ) + #--------------------------------------------------------------- + #--------------------------------------- + for thread in self.threads: + thread.start() + #--------------------------------------- + #Parallel execution here.. + #--------------------------------------- + for thread in self.threads: + thread.join() + #--------------------------------------- + for k in self.ensemble.keys(): + thisEnsemble = self.ensemble[k] + self.output.update(thisEnsemble.output) + if (k!="rhand") and (k!="lhand") and (k!="leye") : #/Why ? + self.outputBVHMinima.update(thisEnsemble.outputMinimumValue) + self.outputBVHMaxima.update(thisEnsemble.outputMaximumValue) + #--------------------------------------------------------------- + self.output = self.enforceBanlistOnOutput(self.output) + self.framesProcessed = self.framesProcessed + 1 + end = time.time() # Time elapsed + self.hz_NN = secondsToHz(end - start) + #--------------------------------------------------------------- + self.history_hz_NN.append(self.hz_NN) + if (len(self.history_hz_NN)>self.perfHistorySize): + self.history_hz_NN.pop(0) #Keep mnet history on limits + #--------------------------------------------------------------- + + #If we want record, record the raw BVH prediction + #print("RECORD MT : ",self.output) + if (self.record): + self.outputHistory.append(self.output) #This does not have HCD improvement.. + + + print("\r MocapNET MultiThreaded NeuralNetwork Framerate : ",round(self.hz_NN,2)," fps \r", end="", flush=True) + print("\n", end="", flush=True) + else: + print("Fallback to single threaded code..") + self.predict(input2D) + return self.output + + + + """ + Convert a dictionary of 2D inputs to MocapNET output + (Whatever that maybe [it is listed in self.inputs and self.outputs]) + """ + def fineTune(self,input2D :dict, NNOutput:dict): + if (self.bvh.modify(NNOutput)): + self.bvh.processFrame(0) #only have 1 frame ID + + if (self.hcdIterations>0) and (self.doFineTuning==1): + print(bcolors.OKGREEN,"Running HCD..",bcolors.ENDC) + self.bvh.fineTuneToMatch("body",input2D,frameID=0,iterations=self.hcdIterations,epochs=self.hcdEpochs,lr=self.hcdLearningRate) + self.bvh.processFrame(0) #This should now be updated with the IK fine tuned prediction..! + + + def predict3DJoints(self,input2D :dict,runNN:bool=True,runHCD:bool=True): + #Extract a BVH dict of BVH motion fields + if ((runNN) or (len(self.previousPrediction)==0)): + if (self.multiThreaded): + rawBVHPrediction = self.predictMultiThreaded(input2D) #If multithreading is disabled this fallbacks to single threaded.. + else: + rawBVHPrediction = self.predict(input2D) + self.previousPrediction = rawBVHPrediction + else: + rawBVHPrediction = self.previousPrediction + + + + # Deal with 3D Mode + #-------------------------------------------------------------------- + if ("upperbody" in self.ensemble) and ("lowerbody" in self.ensemble): + if ('outputMode' in self.ensemble["upperbody"].configuration) and ('outputMode' in self.ensemble["lowerbody"].configuration): + #Running on a recent build with bvh/3d output mode switching + if (self.ensemble["upperbody"].configuration['outputMode']=='3d') and (self.ensemble["lowerbody"].configuration['outputMode']=='3d'): + self.output3D = capitalizeCoordinateTags(self.output) + print(bcolors.OKGREEN,"DIRECT 3D RECOVERY!",bcolors.ENDC) + #print(self.output3D) + return self.output3D + #-------------------------------------------------------------------- + + #Modify our BVH armature with the new BVH values + if (self.bvh.modify(rawBVHPrediction)): + + #Remember BVH Pose + self.outputBVH = rawBVHPrediction + + #Render to 2D/3D + self.bvh.processFrame(0) #only have 1 frame ID <- we load our raw prediction + + fineTuningPasses = 0 + if (self.hcdIterations>0) and (self.doFineTuning==1) and (runHCD): + print(bcolors.OKGREEN,"Running HCD..",bcolors.ENDC) + start = time.time() + if ("upperbody" in self.ensemble) or ("lowerbody" in self.ensemble): + self.bvh.fineTuneToMatch( + "body", + input2D, + frameID=0, + iterations=self.hcdIterations, + epochs=self.hcdEpochs, + lr=self.hcdLearningRate, + fSampling=self.smoothingSampling, + fCutoff=self.smoothingCutoff, + langevinDynamics=self.langevinDynamics + ) + fineTuningPasses = fineTuningPasses + 1 + self.lastMAEErrorInPixels = self.bvh.lastMAEErrorInPixels + if ("lhand" in self.ensemble): + self.bvh.fineTuneToMatch( + "lhand", + input2D, + frameID = 0, + iterations = self.hcdIterations, + epochs = self.hcdEpochs, + lr = self.hcdLearningRate, + fSampling = self.smoothingSampling, + fCutoff = self.smoothingCutoff, + langevinDynamics = self.langevinDynamics + ) + fineTuningPasses = fineTuningPasses + 1 + if ("rhand" in self.ensemble): + self.bvh.fineTuneToMatch( + "rhand", + input2D, + frameID = 0, + iterations = self.hcdIterations, + epochs = self.hcdEpochs, + lr = self.hcdLearningRate, + fSampling = self.smoothingSampling, + fCutoff = self.smoothingCutoff, + langevinDynamics = self.langevinDynamics + ) + fineTuningPasses = fineTuningPasses + 1 + + #-------------------------------------------------------------------------------------- + self.bvh.smooth(frameID=0,fSampling = self.smoothingSampling,fCutoff = self.smoothingCutoff) + #self.bvh.processFrame(0) # <- this is now done internally to simplify code.. This should now be updated with the IK fine tuned prediction..! + #-------------------------------------------------------------------------------------- + end = time.time() + # Time elapsed + seconds = end - start + if (seconds==0.0): + seconds=1.0 + # Calculate frames per second + self.hz_HCD = 1 / seconds + #------------------------------------------------------------- + self.history_hz_HCD.append(self.hz_HCD) + if (len(self.history_hz_HCD)>self.perfHistorySize): + self.history_hz_HCD.pop(0) #Keep mnet history on limits + #------------------------------------------------------------- + if (fineTuningPasses>0): + print("MocapNET HCD Fine tuning Framerate : ",round(self.hz_HCD,2)," fps \n", end="", flush=True) + + #If we want record the file + if (self.record): + self.history.append(self.bvh.getAllMotionValuesOfFrame(0)) + + #Retreive 2D/3D Values + self.output2D = dict() + self.output3D = dict() + for jointID in range(0,self.bvh.numberOfJoints): + #------------------------------------------- + jointName = self.bvh.getJointName(jointID).lower() + #------------------------------------------- + x3D,y3D,z3D = self.bvh.getJoint3D(jointID) + self.output3D["3DX_"+jointName]=float(x3D) + self.output3D["3DY_"+jointName]=float(y3D) + self.output3D["3DZ_"+jointName]=float(z3D) + #------------------------------------------- + x2D,y2D = self.bvh.getJoint2D(jointID) + self.output2D["2DX_"+jointName]=float(x2D) + self.output2D["2DY_"+jointName]=float(y2D) + #------------------------------------------- + else: + print(bcolors.FAIL,"We where unable to process the BVH output",bcolors.ENDC) + + + return self.output3D + + def __del__(self): + if (self.record): + print("Write BVH Output!") + self.bvh.saveBVHFileFromList("out.bvh",self.history) + + from tools import saveCSVFileFromListOfDicts + print("Write BVH Output in CSV format!") + saveCSVFileFromListOfDicts("out.csv",self.outputHistory) + print("Write 2D Input!") + saveCSVFileFromListOfDicts("in.csv",self.inputHistory) + print('MocapNET stopped.') + + +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +def easyMocapNETConstructor( + engine="onnx", + doProfiling=False, + doHCDPostProcessing=1, + hcdLearningRate = 0.1, + hcdEpochs = 20, + hcdIterations = 15, + multiThreaded=False, + bvhScale=1.0, + doBody=True, + doUpperbody=False, #<- These get auto activated if doBody=True + doLowerbody=False, #<- These get auto activated if doBody=True + doFace=False, + doREye=False, + doMouth=False, + doHands=False, + doSymmetries=True, + addNoise=0.0 + ): + combo = MocapNETEnsembleCombination() + #-------------------------------------------------------------- + if (doFace): + combo.addEnsemble("face","step1_face_all/") + if (doMouth): + combo.addEnsemble("mouth","step1_mouth_all/") + if (doREye): + combo.addEnsemble("reye","step1_reye_all/") + if(doSymmetries): + combo.addEnsemble("leye","symmetric") #leye will get initialized automatically + if (doHands): + combo.addEnsemble("lhand","step1_lhand_all/") + if(doSymmetries): + combo.addEnsemble("rhand","symmetric") #rhand will get initialized automatically + if (doBody or doLowerbody) : + combo.addEnsemble("lowerbody","step1_lowerbody_all/") + if (doBody or doUpperbody) : + combo.addEnsemble("upperbody","step1_upperbody_all/") + #-------------------------------------------------------------- + mnet = MocapNET( + doPerformanceProfiling = doProfiling, + doHCDPostProcessing = doHCDPostProcessing, + hcdLearningRate = hcdLearningRate, + hcdEpochs = hcdEpochs, + hcdIterations = hcdIterations, + multiThreaded = multiThreaded, + bvhScale = bvhScale, + engine = engine, + ensembleToLoad = combo, + addNoise = addNoise + ) + return mnet +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- + +if __name__ == '__main__': + mnet = MocapNET( + configUpperBodyPath = "step1_upperbody_all/upperbody_configuration.json", + modelUpperBodyPath="step1_upperbody_all", + configLowerBodyPath = "step1_lowerbody_all/lowerbody_configuration.json", + modelLowerBodyPath="step1_lowerbody_all", + bvhFilePath="BVH/headerWithHeadAndOneMotion.bvh" + ) + + mnet.test() + print("Survived Test!") diff --git a/src/python/mnet4/MocapNETONNX.py b/src/python/mnet4/MocapNETONNX.py new file mode 100755 index 0000000..80ffd09 --- /dev/null +++ b/src/python/mnet4/MocapNETONNX.py @@ -0,0 +1,385 @@ +#!/usr/bin/python3 + +""" +Author : "Ammar Qammaz" +Copyright : "2022 Foundation of Research and Technology, Computer Science Department Greece, See license.txt" +License : "FORTH" +""" + +import onnxruntime as ort +import onnx +import os +import sys +import time + +#Depending on where the scripts get run +#attempt to import from the correct directory +checkPaths = [ "./" , "../" , "../../" ] +for potentialPath in checkPaths: + if os.path.exists(potentialPath+"bonseyes_aiasset_automnet/"): + print("We appear to be running from the `",potentialPath,"` path ") + sys.path.append(os.path.abspath(potentialPath+'bonseyes_aiasset_automnet/train/')) + sys.path.append(potentialPath+'bonseyes_aiasset_automnet/data/datatool_api') + sys.path.append(potentialPath+'bonseyes_aiasset_automnet/data') + sys.path.append(potentialPath+'bonseyes_aiasset_automnet/utils') + sys.path.append(potentialPath+'bonseyes_aiasset_automnet/algorithm') + print("We cd `",potentialPath,"` to run from root directory ") + os.chdir(potentialPath) + break + + +#------------------------------------------------------------------------------------------- +from readCSV import parseConfiguration,parseConfigurationInputJointMap,transformNetworkInput,initializeDecompositionForExecutionEngine,readGroundTruthFile,readCSVFile,parseOutputNormalization +from NSDM import NSDMLabels,createNSDMUsingRules,inputIsEnoughToCreateNSDM,performNSRMAlignment +from EDM import EDMLabels,createEDMUsingRules +from tools import bcolors,checkIfFileExists,readListFromFile,convertListToLowerCase,secondsToHz,capitalizeCoordinateTags,getEntryIndexInList +#------------------------------------------------------------------------------------------- +from BVH.bvhConverter import BVH +#------------------------------------------------------------------------------------------- +from Smooth.smoothing import Smooth +#------------------------------------------------------------------------------------------- +from principleComponentAnalysis import PCA +#------------------------------------------------------------------------------------------- + +import numpy as np + +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +class MocapNETONNXSubProblem(): + def __init__(self, + context, + configPath:str, + modelPath:str, + partName:str, + completelyDisablePCACode = 0 + ): + #self.options = context.sess_options + self.options = ort.SessionOptions() + #------------------------------------------------------------------------------- + self.useOutputLimits = True #Careful, this should always be on! + self.partName = partName + self.configPath = configPath + self.configuration = parseConfiguration(configPath) + self.part = self.configuration["OutputDirectory"] + self.inputName = "input_all" + self.modelPath = modelPath + self.modelDirectory = os.path.dirname(self.modelPath) + self.frameNumber = 0 + #------------------------------------------------------------------------------- + onnxModelForCheck = onnx.load(modelPath) + onnx.checker.check_model(onnxModelForCheck) + print("ONNX devices available : ", ort.get_device()) + providers = ['CPUExecutionProvider'] + #providers = ['CUDAExecutionProvider'] + self.model = ort.InferenceSession(modelPath, providers=providers, sess_options=self.options) + for i in range(0,len(self.model.get_inputs())): + print("ONNX INPUTS ",self.model.get_inputs()[i].name) + self.inputName = self.model.get_inputs()[i].name + + self.model_input_name = self.model.get_inputs() + #------------------------------------------------------------------------------- + self.inputsWithNSRM = convertListToLowerCase(readListFromFile(self.modelDirectory+"/neuralNetworkInputs.list")) + self.inputs = convertListToLowerCase(readListFromFile(self.modelDirectory+"/neuralNetworkJoints.list")) + self.outputs = convertListToLowerCase(readListFromFile(self.modelDirectory+"/neuralNetworkOutputs.list")) + self.configuration = parseConfigurationInputJointMap(self.configuration,self.inputs) + #------------------------------------------------------------------------------- + self.inputReadyForTF = np.empty([2, 1]) + self.NSRM = np.empty([2, 2]) + #------------------------------------------------------------------------------- + self.emptyList = [0.0] * len(self.inputsWithNSRM) + self.emptyInput = np.asarray([self.emptyList],dtype=np.float32) + self.emptyList = [0.0] * len(self.outputs) + self.emptyOutput = np.asarray([self.emptyList],dtype=np.float32) + #------------------------------------------------------------------------------- + self.outputScalars = [1.0] * len(self.outputs) + self.outputOffsets = [0.0] * len(self.outputs) + self.outputMinima = [-6000.0] * len(self.outputs) #huge limit that essentially doesn't limit anything + self.outputMaxima = [6000.0] * len(self.outputs) #huge limit that essentially doesn't limit anything + #------------------------------------------------------------------------------- + self.outputOffsets = parseOutputNormalization(self.modelDirectory,"/outputOffsets.csv",self.outputs,self.outputOffsets) + self.outputScalars = parseOutputNormalization(self.modelDirectory,"/outputScalarsFraction.csv",self.outputs,self.outputScalars) + self.outputMinima = parseOutputNormalization(self.modelDirectory,"/outputMinima.csv",self.outputs,self.outputMinima) + self.outputMaxima = parseOutputNormalization(self.modelDirectory,"/outputMaxima.csv",self.outputs,self.outputMaxima) + #------------------------------------------------------------------------------- + if (self.outputs[0]=="depth"): + self.outputs[0]="hip_zposition" + #------------------------------------------------------------------------------- + print("Output Mapping :") + for jointID in range(0,len(self.outputs)): + #self.outputScalars[jointID] = 1 / float(self.outputScalars[jointID]) + #print(" - Output ",self.outputs[jointID]," limits [",self.outputMinima[jointID],",",self.outputMaxima[jointID],"] scalar ",self.outputScalars[jointID]," offset ",self.outputOffsets[jointID]) + print("Out %s|Min %0.2f|Max %0.2f|Scalar %0.2f|Offset %0.2f"%(self.outputs[jointID],self.outputMinima[jointID],self.outputMaxima[jointID],self.outputScalars[jointID],self.outputOffsets[jointID])) + #------------------------------------------------------------------------------- + self.incompleteInput = 1 + #------------------------------------------------------------------------------- + self.simulated = False + #------------------------------------------------------------------------------- + self.output = dict() + self.outputMinimumValue = dict() + self.outputMaximumValue = dict() + #------------------------------------------------------------------------------- + self.disablePCACode = completelyDisablePCACode + if (not self.disablePCACode): + self.decompositionEngine = initializeDecompositionForExecutionEngine(self.configuration,self.modelDirectory,self.partName,disablePCACode=self.disablePCACode) + #------------------------------------------------------------------------------- + #The default compatibility setting is the BMVC2019 2channel NSDM, however nowadays we use NSRM + numberOfChannelsPerNSDMElement=2 + if (self.configuration['NSDMAlsoUseAlignmentAngles']==1): + numberOfChannelsPerNSDMElement=1 + print("Number of Channels Per NSDM element ",numberOfChannelsPerNSDMElement) + #------------------------------------------------------------------------------- + if ("eigenPoses" in self.configuration): + if (self.configuration['eigenPoses']==1): + self.configuration['eigenPoseData'] = readGroundTruthFile( + self.configuration, + "Eigenposes", + "%s/2d_%s_eigenposes.csv" % (os.path.dirname(self.modelPath),self.partName), + "%s/%s_%s_eigenposes.csv" % (os.path.dirname(self.modelPath),self.configuration['outputMode'],self.partName), #configuration['outputMode'] is either bvh or 3d + 1.0, + numberOfChannelsPerNSDMElement, + int(self.configuration['useRadians']),#useRadians, + 0,#useHalfFloats + externalDecomposition=self.decompositionEngine + ) + #------------------------------------------------------------------------------- + print("\n\n") + print("Inputs :",self.inputs) + print("Outputs :",self.outputs) + #------------------------------------------------------------------------------- + + def getModel(self): + return self.model + + def getModelFlops(self): + print("ONNX has no flops calculator") + return 0 + + def getModelParameters(self): + print("ONNX has no model parameters calculator") + return 0 + + def test(self): + #------------------------------------------- + thisInputONNX = { self.inputName : self.emptyInput} + output_names_onnx = [otp.name for otp in self.model.get_outputs()] + predictions = self.model.run(output_names_onnx,thisInputONNX)[0][0] + #------------------------------------------- + return 1 + + def prepareInput(self,input2D :dict,configuration : dict): + from readCSV import prepareInputG + thisFullInput, self.NSRM, thisInput, angleToRotate = prepareInputG(input2D,configuration,self.inputs,self.inputsWithNSRM,self.part,self.decompositionEngine,self.disablePCACode) + #appendCSVToFile(self.inputName+".csv",thisFullInput,fID=self.frameNumber) # <----------------- + inputReadyForTF = np.asarray([thisFullInput],dtype=np.float32) + return inputReadyForTF + + def logProbabilisticOutput(self,outputFromNN,resolution=60,increment=6.0,numberOfJoints=30): + xs=list() + #---------------------------------- + value = -180.0 + inc = increment + for r in range(0,resolution): + xs.append(value) + value=value+inc + #---------------------------------- + + ys=list() + #---------------------------------- + for j in range(0,numberOfJoints): + rs=list() + for r in range(0,resolution): + rs.append(outputFromNN[(j*resolution) + r]) + ys.append(rs) + #---------------------------------- + + # Importing packages + import matplotlib.pyplot as plt + + #plt.figure(figsize=(80,80)) + plt.clf() + plt.title("Output Distributions %s "%(self.partName)) + + # Define data values + #print("Should plot %u lines"%numberOfJoints) + for j in range(0,numberOfJoints): + plt.plot(xs, ys[j], label='%s (#%u)' %(self.outputs[j],j)) + #print("Plot %u"%j) + + plt.legend() + #plt.show() #<-This blocks + plt.draw() + plt.pause(0.01) + + + + + + """ + Convert a dictionary of 2D inputs to MocapNET output + (Whatever that may be [it is listed in self.inputs and self.outputs]) + """ + def castProbabilisticOutputToDiscreteOutput(self, outputFromNN): + if ('probabilisticOutput' in self.configuration) and (self.configuration['probabilisticOutput']==1): + print(bcolors.OKGREEN,"DOING PROBABILISTIC OUTPUT",bcolors.ENDC) + + #Resolution incrementation + inc = 10.0 + #------------------------- + minV = -180.0 + maxV = 180.0 + resolution = 0 #<- gets automatically calculated as a function of inc.. + #------------------------- + i = minV + while(ibestValue): + bestValue = outputFromNN[(j*resolution + r)] + bestChoice = value + value=value+inc + pickedOutput.append(bestChoice) + + #pickedOutput[0] = 0 + #pickedOutput[1] = 0 + pickedOutput[2] = -160 + pickedOutput[3] = 0 + pickedOutput[4] = 0 + pickedOutput[5] = 0 + return pickedOutput + return outputFromNN + + + + """ + Convert a dictionary of 2D inputs to MocapNET output + (Whatever that maybe [it is listed in self.inputs and self.outputs]) + """ + def predict(self, input2D:dict): + #print("Predict ",self.partName) + self.inputReadyForTF = self.prepareInput(input2D,self.configuration) + + #Turns out on some decompositions like FastICA there are a lot of zeros! + #----------------------------------------------- + #Save cycles by not executing an empty data blob + #----------------------------------------------- + self.incompleteInput = 0 #<- This needs to be set to 0 to mark input is received..! + + #Cast and then run input through MocapNET + thisInputONNX = { self.inputName : self.inputReadyForTF } + output_names_onnx = [otp.name for otp in self.model.get_outputs()] + predictions = self.model.run(output_names_onnx,thisInputONNX)[0][0] + #predictions = self.model(self.inputReadyForTF,training=False) + + #PROBABILISTIC MODE + if ('probabilisticOutput' in self.configuration) and (self.configuration['probabilisticOutput']==1): + predictions = self.castProbabilisticOutputToDiscreteOutput(predictions) + + self.output = dict() + if (len(predictions)!=len(self.outputs)): + print(bcolors.FAIL,"Something bad happened.. the network regressed a different number of parameters (",len(predictions),") than what we expected (",len(self.outputs),") ",bcolors.ENDC) + raise IOError + #Go on with it + return self.output + + #Values to list.. + outputValueList = list() + + for i in range (len(self.outputs)): + outputValueList.append(float(predictions[i])) + + #============================================================================================================== + # THIS SHOULD BE COMMON IN TENSORFLOW/TF-LITE/ONNX + #============================================================================================================== + #Gather our numpy array output in the form of a labeled dictionary + if (self.useOutputLimits): + #Take into account output offsets/scaling + for i in range (len(self.outputs)): + #This should be the exact oposite of the operation in readCSV.py line 550 + recoveredValue = (float(outputValueList[i]) * float(self.outputScalars[i])) + float(self.outputOffsets[i]) + #--------------------------------------------------------------- + if (recoveredValue > self.outputMaxima[i]): + recoveredValue = self.outputMaxima[i] + if (recoveredValue < self.outputMinima[i]): + recoveredValue = self.outputMinima[i] + #--------------------------------------------------------------- + element = self.outputs[i] + self.output[element] = recoveredValue + self.outputMinimumValue[element] = float(self.outputMinima[i]) + self.outputMaximumValue[element] = float(self.outputMaxima[i]) + #--------------------------------------------------------------- + else: + #Not using limits + for i in range (len(self.outputs)): + element = self.outputs[i] + self.output[element] = float(outputValueList[i]) + self.outputMinimumValue[element] = float(self.outputMinima[i]) + self.outputMaximumValue[element] = float(self.outputMaxima[i]) + #============================================================================================================== + #============================================================================================================== + + + + self.frameNumber = self.frameNumber + 1 + return self.output +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +class MocapNETONNX(): + def __init__(self ): + print(""" + ██████╗ ███╗ ██╗███╗ ██╗██╗ ██╗ +██╔═══██╗████╗ ██║████╗ ██║╚██╗██╔╝ +██║ ██║██╔██╗ ██║██╔██╗ ██║ ╚███╔╝ +██║ ██║██║╚██╗██║██║╚██╗██║ ██╔██╗ +╚██████╔╝██║ ╚████║██║ ╚████║██╔╝ ██╗ + ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═══╝╚═╝ ╚═╝""") + self.sess_options = ort.SessionOptions() + self.sess_options.log_severity_level = 3 #<- log_level + self.sess_options.intra_op_num_threads = 4 + #self.sess_options.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL + self.sess_options.execution_mode = ort.ExecutionMode.ORT_PARALLEL + self.sess_options.inter_op_num_threads = 4 + self.sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL + + +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +if __name__ == '__main__': + mnet = MocapNETONNX() + print("Survived Test!") +#------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/src/python/mnet4/MocapNETTFLite.py b/src/python/mnet4/MocapNETTFLite.py new file mode 100755 index 0000000..8f81551 --- /dev/null +++ b/src/python/mnet4/MocapNETTFLite.py @@ -0,0 +1,326 @@ +#!/usr/bin/python3 + +""" +Author : "Ammar Qammaz" +Copyright : "2022 Foundation of Research and Technology, Computer Science Department Greece, See license.txt" +License : "FORTH" +""" + +import tensorflow as tf +import os +import sys +import time + +#Depending on where the scripts get run +#attempt to import from the correct directory +checkPaths = [ "./" , "../" , "../../" ] +for potentialPath in checkPaths: + if os.path.exists(potentialPath+"bonseyes_aiasset_automnet/"): + print("We appear to be running from the `",potentialPath,"` path ") + sys.path.append(os.path.abspath(potentialPath+'bonseyes_aiasset_automnet/train/')) + sys.path.append(potentialPath+'bonseyes_aiasset_automnet/data/datatool_api') + sys.path.append(potentialPath+'bonseyes_aiasset_automnet/data') + sys.path.append(potentialPath+'bonseyes_aiasset_automnet/utils') + sys.path.append(potentialPath+'bonseyes_aiasset_automnet/algorithm') + print("We cd `",potentialPath,"` to run from root directory ") + os.chdir(potentialPath) + break + +#------------------------------------------------------------------------------------------- +from readCSV import parseConfiguration,parseConfigurationInputJointMap,transformNetworkInput,initializeDecompositionForExecutionEngine,readGroundTruthFile,readCSVFile,parseOutputNormalization +from NSDM import NSDMLabels,createNSDMUsingRules,inputIsEnoughToCreateNSDM,performNSRMAlignment +from EDM import EDMLabels,createEDMUsingRules +from tools import bcolors,checkIfFileExists,readListFromFile,convertListToLowerCase,secondsToHz,getEntryIndexInList +#------------------------------------------------------------------------------------------- +from BVH.bvhConverter import BVH +#------------------------------------------------------------------------------------------- +#from Smooth.smoothing import Smooth +#------------------------------------------------------------------------------------------- +from principleComponentAnalysis import PCA +#------------------------------------------------------------------------------------------- + +import numpy as np + +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +class MocapNETTFLiteSubProblem(): + def __init__(self, + context, + configPath:str, + modelPath:str, + partName:str, + completelyDisablePCACode = 0 + ): + #------------------------------------------------------------------------------- + self.useOutputLimits = True #Careful, this should always be on! + self.partName = partName + self.configPath = configPath + self.configuration = parseConfiguration(configPath) + self.part = self.configuration["OutputDirectory"] + self.inputName = "input_all" + self.modelPath = modelPath + self.modelDirectory= os.path.dirname(self.modelPath) + self.frameNumber = 0 + #------------------------------------------------------------------------------- + #The default compatibility setting is the BMVC2019 2channel NSDM, however nowadays we use NSRM + numberOfChannelsPerNSDMElement=2 + if (self.configuration['NSDMAlsoUseAlignmentAngles']==1): + numberOfChannelsPerNSDMElement=1 + print("Number of Channels Per NSDM element ",numberOfChannelsPerNSDMElement) + if ("eigenPoses" in self.configuration): + if (self.configuration['eigenPoses']==1): + self.configuration['eigenPoseData'] = readGroundTruthFile( + self.configuration, + "Eigenposes", + "%s/2d_%s_eigenposes.csv" % (os.path.dirname(self.modelPath),self.partName ), + "%s/%s_%s_eigenposes.csv" % (os.path.dirname(self.modelPath),self.configuration['outputMode'],self.partName), #configuration['outputMode'] is either bvh or 3d + 1.0, + numberOfChannelsPerNSDMElement, + 0,#useRadians, + 0,#useHalfFloats + externalDecomposition=self.decompositionEngine + ) + #------------------------------------------------------------------------------- + self.model = tf.lite.Interpreter( + model_path=self.modelPath, + num_threads=8 + ) + + self.model.allocate_tensors() + self.input_details = self.model.get_input_details() + self.output_details = self.model.get_output_details() + + # check the type of the input tensor + self.floating_model = self.input_details[0]['dtype'] == np.float32 + #------------------------------------------------------------------------------- + self.inputsWithNSRM = convertListToLowerCase(readListFromFile(self.modelDirectory+"/neuralNetworkInputs.list")) + self.inputs = convertListToLowerCase(readListFromFile(self.modelDirectory+"/neuralNetworkJoints.list")) + self.outputs = convertListToLowerCase(readListFromFile(self.modelDirectory+"/neuralNetworkOutputs.list")) + self.configuration = parseConfigurationInputJointMap(self.configuration,self.inputs) + #------------------------------------------------------------------------------- + self.inputReadyForTF = np.empty([2, 1]) + self.NSRM = np.empty([2, 2]) + #------------------------------------------------------------------------------- + self.outputScalars = [1.0] * len(self.outputs) + self.outputOffsets = [0.0] * len(self.outputs) + self.outputMinima = [-6000.0] * len(self.outputs) #huge limit that essentially doesn't limit anything + self.outputMaxima = [6000.0] * len(self.outputs) #huge limit that essentially doesn't limit anything + #------------------------------------------------------------------------------- + self.outputOffsets = parseOutputNormalization(self.modelDirectory,"/outputOffsets.csv",self.outputs,self.outputOffsets) + #self.outputScalars = parseOutputNormalization(self.modelDirectory,"/outputScalars.csv",self.outputs,self.outputScalars) + #for jointID in range(0,len(self.outputs)): + # self.outputScalars[jointID] = 1 / float(self.outputScalars[jointID]) + self.outputScalars = parseOutputNormalization(self.modelDirectory,"/outputScalarsFraction.csv",self.outputs,self.outputScalars) + self.outputMinima = parseOutputNormalization(self.modelDirectory,"/outputMinima.csv",self.outputs,self.outputMinima) + self.outputMaxima = parseOutputNormalization(self.modelDirectory,"/outputMaxima.csv",self.outputs,self.outputMaxima) + #------------------------------------------------------------------------------- + if (self.outputs[0]=="depth"): + self.outputs[0]="hip_zposition" + #------------------------------------------------------------------------------- + print("Output Mapping :") + for jointID in range(0,len(self.outputs)): + #self.outputScalars[jointID] = 1 / float(self.outputScalars[jointID]) + #print("Output ",self.outputs[jointID]," min ",self.outputMinima[jointID]," max ",self.outputMaxima[jointID]," scalar ",self.outputScalars[jointID]," offset ",self.outputOffsets[jointID]) + print("Out %s|Min %0.2f|Max %0.2f|Scalar %0.2f|Offset %0.2f"%(self.outputs[jointID],self.outputMinima[jointID],self.outputMaxima[jointID],self.outputScalars[jointID],self.outputOffsets[jointID])) + #------------------------------------------------------------------------------- + self.incompleteInput = 1 + #------------------------------------------------------------------------------- + self.simulated = False + #------------------------------------------------------------------------------- + self.output = dict() + self.outputMinimumValue = dict() + self.outputMaximumValue = dict() + #------------------------------------------------------------------------------- + self.disablePCACode = completelyDisablePCACode + if (not self.disablePCACode): + self.decompositionEngine = initializeDecompositionForExecutionEngine(self.configuration,self.modelDirectory,self.partName,disablePCACode=self.disablePCACode) + #------------------------------------------------------------------------------- + print("\n\n") + print("Model Dir :",self.modelDirectory) + print("Inputs :",self.inputs) + print("Outputs :",self.outputs) + #------------------------------------------------------------------------------- + + def getModel(self): + return self.model + + def getModelFlops(self): + print("TF-Lite has no flops calculator") + return 0 + + def getModelParameters(self): + model = self.model + #concrete_func = model.signatures["serving_default"] + #print( concrete_func.inputs[0] ) + #print( concrete_func.inputs[0].shape ) + #inputShape = str(concrete_func.inputs[0].shape) + #inputShape = inputShape.strip("() ") + #inputShape = inputShape.replace(",", "x") + #inputShape = inputShape.replace("None", "1") + #inputShape = inputShape.strip(' ') + #print("Input Shape is : ",inputShape) + #------------------------------------------ + totalParameters = 0 + try: + trainableParams = np.sum([np.prod(v.get_shape()) for v in model.trainable_weights]) + totalParameters = int(totalParameters + nonTrainableParams) + except: + print("Could not get model trainable parameters for TF-Lite model..!") + + try: + nonTrainableParams = np.sum([np.prod(v.get_shape()) for v in model.non_trainable_weights]) + totalParameters = int(totalParameters + nonTrainableParams) + except: + print("Could not get model non-trainable parameters for TF-Lite model..!") + + + return totalParameters + + + def test(self): + #------------------------------------------- + emptyList = [0.0] * len(self.inputsWithNSRM) + emptyInput =np.asarray([emptyList],dtype=np.float32) + #------------------------------------------- + #print("Running zeros ") + self.model.set_tensor(self.input_details[0]['index'],emptyInput) + self.model.invoke() + predictions = self.model.get_tensor(self.output_details[0]['index']) + #------------------------------------------- + return 1 + + def prepareInput(self,input2D :dict,configuration : dict): + from readCSV import prepareInputG + thisFullInput, self.NSRM, thisInput, angleToRotate = prepareInputG(input2D,configuration,self.inputs,self.inputsWithNSRM,self.part,self.decompositionEngine,self.disablePCACode) + + inputReadyForTF = np.asarray([thisFullInput],dtype=np.float32) + return inputReadyForTF + + + """ + Convert a dictionary of 2D inputs to MocapNET output + (Whatever that maybe [it is listed in self.inputs and self.outputs]) + """ + def predict(self,input2D :dict): + + self.inputReadyForTF = self.prepareInput(input2D,self.configuration) + + #Turns out on some decompositions like FastICA there are a lot of zeros! + #----------------------------------------------- + #Save cycles by not executing an empty data blob + #----------------------------------------------- + self.incompleteInput = 0 #<- This needs to be set to 0 to mark input is received..! + #totalData = 1 + #zeroData = 0 + #for element in self.inputReadyForTF[0].tolist(): + # #print("ELEMENT ",element) + # totalData = totalData + 1 + # if ( (element<0.005) and (element>-0.005) ): + # zeroData=zeroData + 1 + #missingRatio = zeroData/totalData + #print("Missing Ratio : ",missingRatio) + #if (missingRatio>0.4): + # print(bcolors.FAIL,"Not executing NN with empty data ",bcolors.ENDC) + # #Reset armature..! + # for k in self.output.keys(): + # self.output[k]=0.0 + # self.incompleteInput = 1 + # return self.output + #----------------------------------------------- + + self.model.set_tensor(self.input_details[0]['index'],self.inputReadyForTF) + self.model.invoke() + predictions = self.model.get_tensor(self.output_details[0]['index']) + + self.output = dict() + if (len(predictions[0])!=len(self.outputs)): + print(bcolors.FAIL,"Something bad happened.. the ",self.partName," network regressed a different number of parameters (",len(predictions[0]),") than what we expected (",len(self.outputs),") ",bcolors.ENDC) + raise IOError + return self.output + + + #Values to list.. + outputValueList = list() + + for i in range (len(self.outputs)): + outputValueList.append(float(predictions[0][i])) + + #============================================================================================================== + # THIS SHOULD BE COMMON IN TENSORFLOW/TF-LITE/ONNX + #============================================================================================================== + #Gather our numpy array output in the form of a labeled dictionary + if (self.useOutputLimits): + #Take into account output offsets/scaling + for i in range (len(self.outputs)): + #This should be the exact oposite of the operation in readCSV.py line 550 + recoveredValue = (float(outputValueList[i]) * float(self.outputScalars[i])) + float(self.outputOffsets[i]) + #--------------------------------------------------------------- + if (recoveredValue > self.outputMaxima[i]): + recoveredValue = self.outputMaxima[i] + if (recoveredValue < self.outputMinima[i]): + recoveredValue = self.outputMinima[i] + #--------------------------------------------------------------- + element = self.outputs[i] + self.output[element] = recoveredValue + self.outputMinimumValue[element] = float(self.outputMinima[i]) + self.outputMaximumValue[element] = float(self.outputMaxima[i]) + #--------------------------------------------------------------- + else: + #Not using limits + for i in range (len(self.outputs)): + element = self.outputs[i] + self.output[element] = float(outputValueList[i]) + self.outputMinimumValue[element] = float(self.outputMinima[i]) + self.outputMaximumValue[element] = float(self.outputMaxima[i]) + #============================================================================================================== + #============================================================================================================== + + self.frameNumber = self.frameNumber + 1 + return self.output + + +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- + +class MocapNETTFLite(): + def __init__(self,): + print(""" +████████╗███████╗ ██╗ ██╗████████╗███████╗ +╚══██╔══╝██╔════╝ ██║ ██║╚══██╔══╝██╔════╝ + ██║ █████╗█████╗██║ ██║ ██║ █████╗ + ██║ ██╔══╝╚════╝██║ ██║ ██║ ██╔══╝ + ██║ ██║ ███████╗██║ ██║ ███████╗ + ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ╚══════╝""") + #------------------------------------------------------------------------------- + #do nothing :P + #------------------------------------------------------------------------------- + +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +if __name__ == '__main__': + mnet = MocapNETONNX() + print("Survived Test!") +#------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/src/python/mnet4/MocapNETTensorflow.py b/src/python/mnet4/MocapNETTensorflow.py new file mode 100755 index 0000000..0c1efa4 --- /dev/null +++ b/src/python/mnet4/MocapNETTensorflow.py @@ -0,0 +1,437 @@ +#!/usr/bin/python3 + +""" +Author : "Ammar Qammaz" +Copyright : "2022 Foundation of Research and Technology, Computer Science Department Greece, See license.txt" +License : "FORTH" +""" + +#------------------------------------------------------------------------------------------- +from readCSV import parseConfiguration,parseConfigurationInputJointMap,transformNetworkInput,initializeDecompositionForExecutionEngine,readGroundTruthFile,readCSVFile,parseOutputNormalization +from NSDM import NSDMLabels,createNSDMUsingRules,inputIsEnoughToCreateNSDM,performNSRMAlignment +from EDM import EDMLabels,createEDMUsingRules +from tools import bcolors,checkIfFileExists,readListFromFile,convertListToLowerCase,secondsToHz,getEntryIndexInList +#------------------------------------------------------------------------------------------- +from BVH.bvhConverter import BVH +#------------------------------------------------------------------------------------------- +from Smooth.smoothing import Smooth +#------------------------------------------------------------------------------------------- +from principleComponentAnalysis import PCA +#------------------------------------------------------------------------------------------- + +import time +import os +import numpy as np + +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +class MocapNETTensorflowSubProblem(): + def __init__(self, + context, + configPath:str, + modelPath:str, + partName:str, + device:str="/device:GPU:0", + doPerformanceProfiling = False, + tensorboard = 0, + completelyDisablePCACode = 0 + ): + #------------------------------------------------------------------------------- + self.useOutputLimits = True #Careful, this should always be on! + self.partName = partName + self.configPath = configPath + from readCSV import parseConfiguration + self.configuration = parseConfiguration(configPath) + self.part = partName#self.configuration["OutputDirectory"] + self.modelPath = modelPath + self.modelDirectory= os.path.dirname(self.modelPath) + from DNNModel import loadNewModel + self.model = loadNewModel(modelPath) + self.device = device + self.frameNumber = 0 + #------------------------------------------------------------------------------- + #The default compatibility setting is the BMVC2019 2channel NSDM, however nowadays we use NSRM + numberOfChannelsPerNSDMElement=2 + if (self.configuration['NSDMAlsoUseAlignmentAngles']==1): + numberOfChannelsPerNSDMElement=1 + print("Number of Channels Per NSDM element ",numberOfChannelsPerNSDMElement) + if ("eigenPoses" in self.configuration): + if (self.configuration['eigenPoses']==1): + self.configuration['eigenPoseData'] = readGroundTruthFile( + self.configuration, + "Eigenposes", + "%s/2d_%s_eigenposes.csv" % (os.path.dirname(self.modelPath),self.partName ), + "%s/%s_%s_eigenposes.csv" % (os.path.dirname(self.modelPath),self.configuration['outputMode'],self.partName), #configuration['outputMode'] is either bvh or 3d + 1.0, + numberOfChannelsPerNSDMElement, + 0,#useRadians, + 0,#useHalfFloats + externalDecomposition=self.decompositionEngine + ) + #------------------------------------------------------------------------------- + import tensorflow as tf + rmsprop=tf.keras.optimizers.RMSprop(learning_rate=0.002, rho=0.9, epsilon=tf.keras.backend.epsilon()) + self.model.compile( + optimizer=rmsprop, + loss='mse', + metrics=['mae', 'acc'], + jit_compile=True #<- this may cause trouble on non-XLA builds? + ) + self.modelKeras = self.model + #print("MocapNET Model for ",partName," has the following signatures ",self.model.signatures) + self.model = self.model.signatures['serving_default'] + + self.profile = doPerformanceProfiling + self.tensorboard = tensorboard + #------------------------------------------------------------------------------- + self.inputsWithNSRM = convertListToLowerCase(readListFromFile(self.modelDirectory+"/neuralNetworkInputs.list")) + self.inputs = convertListToLowerCase(readListFromFile(self.modelDirectory+"/neuralNetworkJoints.list")) + self.outputs = convertListToLowerCase(readListFromFile(self.modelDirectory+"/neuralNetworkOutputs.list")) + self.configuration = parseConfigurationInputJointMap(self.configuration,self.inputs) + #------------------------------------------------------------------------------- + self.inputReadyForTF = np.empty([2, 1]) + self.NSRM = np.empty([2, 2]) + #------------------------------------------------------------------------------- + self.outputScalars = [1.0] * len(self.outputs) + self.outputOffsets = [0.0] * len(self.outputs) + self.outputMinima = [-6000.0] * len(self.outputs) #huge limit that essentially doesn't limit anything + self.outputMaxima = [6000.0] * len(self.outputs) #huge limit that essentially doesn't limit anything + #------------------------------------------------------------------------------- + self.outputOffsets = parseOutputNormalization(self.modelDirectory,"/outputOffsets.csv",self.outputs,self.outputOffsets) + #self.outputScalars = parseOutputNormalization(self.modelDirectory,"/outputScalars.csv",self.outputs,self.outputScalars) + #for jointID in range(0,len(self.outputs)): + # self.outputScalars[jointID] = 1 / float(self.outputScalars[jointID]) + self.outputScalars = parseOutputNormalization(self.modelDirectory,"/outputScalarsFraction.csv",self.outputs,self.outputScalars) + self.outputMinima = parseOutputNormalization(self.modelDirectory,"/outputMinima.csv",self.outputs,self.outputMinima) + self.outputMaxima = parseOutputNormalization(self.modelDirectory,"/outputMaxima.csv",self.outputs,self.outputMaxima) + #------------------------------------------------------------------------------- + if (self.outputs[0]=="depth"): + self.outputs[0]="hip_zposition" + #------------------------------------------------------------------------------- + print("Output Mapping :") + for jointID in range(0,len(self.outputs)): + #self.outputScalars[jointID] = 1 / float(self.outputScalars[jointID]) + #print("Output ",self.outputs[jointID]," min ",self.outputMinima[jointID]," max ",self.outputMaxima[jointID]," scalar ",self.outputScalars[jointID]," offset ",self.outputOffsets[jointID]) + print("Out %s|Min %0.2f|Max %0.2f|Scalar %0.2f|Offset %0.2f"%(self.outputs[jointID],self.outputMinima[jointID],self.outputMaxima[jointID],self.outputScalars[jointID],self.outputOffsets[jointID])) + #------------------------------------------------------------------------------- + self.networkInputList = [0.0] * len(self.inputsWithNSRM) + self.networkInput = np.asarray([self.networkInputList],dtype=np.float32) + #------------------------------------------------------------------------------- + self.incompleteInput = 1 + #------------------------------------------------------------------------------- + self.simulated = False + #------------------------------------------------------------------------------- + self.output = dict() + self.outputMinimumValue = dict() + self.outputMaximumValue = dict() + #------------------------------------------------------------------------------- + self.disablePCACode = completelyDisablePCACode + if (not self.disablePCACode): + self.decompositionEngine = initializeDecompositionForExecutionEngine(self.configuration,self.modelDirectory,self.partName,disablePCACode=self.disablePCACode) + #------------------------------------------------------------------------------- + print("\n\n") + print("Inputs :",self.inputs) + print("Outputs :",self.outputs) + #------------------------------------------------------------------------------- + + def getModel(self): + return self.modelKeras + + def getModelFlops(self): + import tensorflow as tf + from tensorflow.python.framework.convert_to_constants import convert_variables_to_constants_v2_as_graph + model = self.modelKeras + concrete = tf.function(lambda inputs: model(inputs)) + concrete_func = concrete.get_concrete_function( + [tf.TensorSpec([1, *inputs.shape[1:]]) for inputs in model.inputs]) + frozen_func, graph_def = convert_variables_to_constants_v2_as_graph(concrete_func) + with tf.Graph().as_default() as graph: + tf.graph_util.import_graph_def(graph_def, name='') + run_meta = tf.compat.v1.RunMetadata() + opts = tf.compat.v1.profiler.ProfileOptionBuilder.float_operation() + flops = tf.compat.v1.profiler.profile(graph=graph, run_meta=run_meta, cmd="op", options=opts) + + totalFLOPS = int(flops.total_float_ops) + return totalFLOPS + return 0 + + def getModelParameters(self): + model = self.modelKeras + #------------------------------------------------------------------------------------------ + trainableParams = np.sum([np.prod(v.get_shape()) for v in model.trainable_weights]) + nonTrainableParams = np.sum([np.prod(v.get_shape()) for v in model.non_trainable_weights]) + totalParameters = int(trainableParams + nonTrainableParams) + return totalParameters + + def test(self): + #------------------------------------------- + emptyList = [0.0] * len(self.inputsWithNSRM) + emptyInput =np.asarray([emptyList],dtype=np.float32) + #------------------------------------------- + #print(bcolors.FAIL,"Running zeros ",bcolors.ENDC) + import tensorflow as tf + outputs = self.model(tf.cast(emptyInput,dtype=tf.float32)) #,training=False + #print("Outputs : ",outputs) + #print("Output Keys", outputs.keys()) + outKey = list(outputs.keys())[0] + outputsRaw = outputs[outKey] + predictions = outputsRaw + + #predictions = self.model(emptyInput,training=False) + #print("MocapNET result = ",predictions) + #------------------------------------------- + return 1 + + def prepareInput(self,input2D :dict,configuration : dict): + from readCSV import prepareInputG + thisFullInput, self.NSRM, thisInput, angleToRotate = prepareInputG(input2D,configuration,self.inputs,self.inputsWithNSRM,self.part,self.decompositionEngine,self.disablePCACode) + + #i=0 + #for value in thisFullInput: + # self.networkInput[i]=value + # i=i+1 + #import tensorflow as tf + #self.networkInput = tf.convert_to_tensor(self.networkInputNumpy) + self.networkInput = np.asarray([thisFullInput],dtype=np.float32) + return self.networkInput + #----------------------------- + #inputReadyForTF = np.asarray([thisFullInput],dtype=np.float32) + #return inputReadyForTF + + + """ + Convert a dictionary of 2D inputs to MocapNET output + (Whatever that maybe [it is listed in self.inputs and self.outputs]) + """ + def predict(self,input2D :dict): + + #This call works @ 400Hz + #-------------------------------------------------------------------------------------- + self.inputReadyForTF = self.prepareInput(input2D,self.configuration) + + #Turns out on some decompositions like FastICA there are a lot of zeros! + #----------------------------------------------- + #Save cycles by not executing an empty data blob + #----------------------------------------------- + self.incompleteInput = 0 #<- This needs to be set to 0 to mark input is received..! + #totalData = 1 + #zeroData = 0 + #for element in self.inputReadyForTF[0].tolist(): + # #print("ELEMENT ",element) + # totalData = totalData + 1 + # if ( (element<0.005) and (element>-0.005) ): + # zeroData=zeroData + 1 + #missingRatio = zeroData/totalData + #print("Missing Ratio : ",missingRatio) + #if (missingRatio>0.4): + # print(bcolors.FAIL,"Not executing NN with empty data ",bcolors.ENDC) + # #Reset armature..! + # for k in self.output.keys(): + # self.output[k]=0.0 + # + # self.incompleteInput = 1 + # return self.output + #-------------------------------------------------------------------------------------- + predictions = list() + self.output = dict() + + + #-------------------------------------------------------------------------------------- + if (self.profile): + #Run input through MocapNET and Profile code (slower) + print(bcolors.WARNING,"WARNING: Profiling NN enabled, execution will be slower",bcolors.ENDC) + predictions = self.modelKeras.predict(self.inputReadyForTF,callbacks = [self.tensorboard]) + else: + #As stated in https://github.com/keras-team/keras/blob/v2.8.0/keras/engine/training.py#L1825-L2012 : + # and https://keras.io/getting_started/faq/#whats-the-difference-between-model-methods-predict-and-call + import tensorflow as tf + with tf.device(self.device): + #with tf.device('/device:CPU:0'): + inferenceOutputs = self.model(tf.cast(self.inputReadyForTF,dtype=tf.float32)) # ,training=False We shouldn't run predict to get as fast results as possible + #print("Outputs : ",outputs) + #print("Output Keys", outputs.keys()) + outKey = list(inferenceOutputs.keys())[0] + outputsRaw = inferenceOutputs[outKey] + #outputsRaw = outputs['result_all'] + predictions = outputsRaw + #-------------------------------------------------------------------------------------- + + + if (len(predictions)>0): + if(len(predictions[0])!=len(self.outputs)): + print(bcolors.FAIL,"Something bad happened.. the network regressed a different number of parameters than what we expected",bcolors.ENDC) + raise IOError + return self.output + + + #Values to list.. + outputValueList = list() + + for i in range (len(self.outputs)): + outputValueList.append(float(predictions[0][i])) + + #============================================================================================================== + # THIS SHOULD BE COMMON IN TENSORFLOW/TF-LITE/ONNX + #============================================================================================================== + #Gather our numpy array output in the form of a labeled dictionary + if (self.useOutputLimits): + #Take into account output offsets/scaling + for i in range (len(self.outputs)): + #This should be the exact oposite of the operation in readCSV.py line 550 + recoveredValue = (float(outputValueList[i]) * float(self.outputScalars[i])) + float(self.outputOffsets[i]) + #--------------------------------------------------------------- + if (recoveredValue > self.outputMaxima[i]): + recoveredValue = self.outputMaxima[i] + if (recoveredValue < self.outputMinima[i]): + recoveredValue = self.outputMinima[i] + #--------------------------------------------------------------- + element = self.outputs[i] + self.output[element] = recoveredValue + self.outputMinimumValue[element] = float(self.outputMinima[i]) + self.outputMaximumValue[element] = float(self.outputMaxima[i]) + #--------------------------------------------------------------- + else: + #Not using limits + for i in range (len(self.outputs)): + element = self.outputs[i] + self.output[element] = float(outputValueList[i]) + self.outputMinimumValue[element] = float(self.outputMinima[i]) + self.outputMaximumValue[element] = float(self.outputMaxima[i]) + #============================================================================================================== + #============================================================================================================== + + self.frameNumber = self.frameNumber + 1 + return self.output + + +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- + +def get_available_devices(): + from tensorflow.python.client import device_lib + local_device_protos = device_lib.list_local_devices() + return [x.name for x in local_device_protos if x.device_type == 'GPU' or x.device_type == 'CPU'] + +class MocapNETTensorflow(): + def __init__(self, + doPerformanceProfiling = False, + ): + print(""" +████████╗███████╗███╗ ██╗███████╗ ██████╗ ██████╗ ███████╗██╗ ██████╗ ██╗ ██╗ +╚══██╔══╝██╔════╝████╗ ██║██╔════╝██╔═══██╗██╔══██╗██╔════╝██║ ██╔═══██╗██║ ██║ + ██║ █████╗ ██╔██╗ ██║███████╗██║ ██║██████╔╝█████╗ ██║ ██║ ██║██║ █╗ ██║ + ██║ ██╔══╝ ██║╚██╗██║╚════██║██║ ██║██╔══██╗██╔══╝ ██║ ██║ ██║██║███╗██║ + ██║ ███████╗██║ ╚████║███████║╚██████╔╝██║ ██║██║ ███████╗╚██████╔╝╚███╔███╔╝ + ╚═╝ ╚══════╝╚═╝ ╚═══╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚══════╝ ╚═════╝ ╚══╝╚══╝""") + self.doPerformanceProfiling = doPerformanceProfiling + + #Tensorflow attempt to be reasonable + #------------------------------------------ + import gc + gc.collect() #Do garbage collection before allocating TF stuff + import os + os.environ['TF_ENABLE_GPU_GARBAGE_COLLECTION']='false' + #Make sure CUDA cache is not disabled! + os.environ['CUDA_CACHE_DISABLE'] = '0' + #Try to presist cudnn + os.environ['TF_USE_CUDNN_BATCHNORM_SPATIAL_PERSISTENT'] = '1' + #Try to allocate as little memory as possible + os.environ['TF_FORCE_GPU_ALLOW_GROWTH'] = 'true' + #Use seperate threads so execution is not throttled by CPU + os.environ['TF_GPU_THREAD_MODE'] = 'gpu_private' + #0 = all messages are logged (default behavior) + #1 = INFO messages are not printed + #2 = INFO and WARNING messages are not printed + #3 = INFO, WARNING, and ERROR messages are not printed + os.environ['TF_CPP_MIN_LOG_LEVEL'] = '0' + #improve the stability of the auto-tuning process used to select the fastest convolution algorithms + os.environ['TF_AUTOTUNE_THRESHOLD'] = '1' + #------------------------------------------ + import tensorflow as tf + devices = get_available_devices() + print("Available Tensorflow devices are : ",devices) + self.device = '/device:CPU:0' + for device in devices: + if (device.find("GPU")!=-1): + self.device = device + print("Selecting device : ",self.device) + + #If enabled, an op will be placed on CPU if any of the following are true + #1 - there's no GPU implementation for the OP + #2 - no GPU devices are known or registered + #3 - need to co-locate with reftype input(s) which are from CPU + tf.config.set_soft_device_placement(True) + + #Only give the warning when not profiling otherwise we will get an error! + if (not doPerformanceProfiling): + tf.config.experimental.set_device_policy('explicit') + + try: + tf.config.run_functions_eagerly(True) + tf.config.experimental.set_synchronous_execution(False) + except: + #Invalid device or cannot modify virtual devices once initialized. + pass + + try: + physical_devices = tf.config.list_physical_devices('CPU') + tf.config.experimental.set_memory_growth(physical_devices[0], True) + physical_devices = tf.config.list_physical_devices('GPU') + tf.config.experimental.set_memory_growth(physical_devices[0], True) + except: + #Invalid device or cannot modify virtual devices once initialized. + pass + + try: + tf.config.threading.set_intra_op_parallelism_threads(8) + tf.config.threading.set_inter_op_parallelism_threads(8) + except: + #Most probably : RuntimeError: Intra op parallelism cannot be modified after initialization + pass + + if (doPerformanceProfiling): + import tensorflow as tf + self.tensorboard = tf.keras.callbacks.TensorBoard(log_dir = "profiling",histogram_freq = 1) + #tensorboard --bind_all --logdir profiling + from DNNModel import startProfiling + startProfiling() + else: + self.tensorboard=0 + + + def __del__(self): + if (self.doPerformanceProfiling): + from DNNModel import stopProfiling + stopProfiling() + print("To see profile results \nUse :\n tensorboard --logdir profiling ") + print('TFLite stopped.') + +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------- +if __name__ == '__main__': + mnet = MocapNETTensorflow() + print("Survived Test!") +#------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/src/python/mnet4/MocapNETVisualization.py b/src/python/mnet4/MocapNETVisualization.py new file mode 100755 index 0000000..22b927c --- /dev/null +++ b/src/python/mnet4/MocapNETVisualization.py @@ -0,0 +1,808 @@ +#!/usr/bin/python3 +""" +Author : "Ammar Qammaz" +Copyright : "2022 Foundation of Research and Technology, Computer Science Department Greece, See license.txt" +License : "FORTH" +""" + +def getColor(i): + if i>107: + i = i % 108 + + + if (i==0): + return (247,252,253) + elif (i==1): + return (224,236,244) + elif (i==2): + return (191,211,230) + elif (i==3): + return (158,188,218) + elif (i==4): + return (140,150,198) + elif (i==5): + return (140,107,177) + elif (i==6): + return (136,65,157) + elif (i==7): + return (129,15,124) + elif (i==8): + return (77,0,75) + elif (i==9): + return (247,252,253) + elif (i==10): + return (229,245,249) + elif (i==11): + return (204,236,230) + elif (i==12): + return (153,216,201) + elif (i==13): + return (102,194,164) + elif (i==14): + return (65,174,118) + elif (i==15): + return (35,139,69) + elif (i==16): + return (0,109,44) + elif (i==17): + return (0,68,27) + elif (i==18): + return (247,252,240) + elif (i==19): + return (224,243,219) + elif (i==20): + return (204,235,197) + elif (i==21): + return (168,221,181) + elif (i==22): + return (123,204,196) + elif (i==23): + return (78,179,211) + elif (i==24): + return (43,140,190) + elif (i==25): + return (8,104,172) + elif (i==26): + return (8,64,129) + elif (i==27): + return (255,247,236) + elif (i==28): + return (254,232,200) + elif (i==29): + return (253,212,158) + elif (i==30): + return (253,187,132) + elif (i==31): + return (252,141,89) + elif (i==32): + return (239,101,72) + elif (i==33): + return (215,48,31) + elif (i==34): + return (179,0,0) + elif (i==35): + return (127,0,0) + elif (i==36): + return (255,247,251) + elif (i==37): + return (236,231,242) + elif (i==38): + return (208,209,230) + elif (i==39): + return (166,189,219) + elif (i==40): + return (116,169,207) + elif (i==41): + return (54,144,192) + elif (i==42): + return (5,112,176) + elif (i==43): + return (4,90,141) + elif (i==44): + return (2,56,88) + elif (i==45): + return (255,247,251) + elif (i==46): + return (236,226,240) + elif (i==47): + return (208,209,230) + elif (i==48): + return (166,189,219) + elif (i==49): + return (103,169,207) + elif (i==50): + return (54,144,192) + elif (i==51): + return (2,129,138) + elif (i==52): + return (1,108,89) + elif (i==53): + return (1,70,54) + elif (i==54): + return (247,244,249) + elif (i==55): + return (231,225,239) + elif (i==56): + return (212,185,218) + elif (i==57): + return (201,148,199) + elif (i==58): + return (223,101,176) + elif (i==59): + return (231,41,138) + elif (i==60): + return (206,18,86) + elif (i==61): + return (152,0,67) + elif (i==62): + return (103,0,31) + elif (i==63): + return (255,247,243) + elif (i==64): + return (253,224,221) + elif (i==65): + return (252,197,192) + elif (i==66): + return (250,159,181) + elif (i==67): + return (247,104,161) + elif (i==68): + return (221,52,151) + elif (i==69): + return (174,1,126) + elif (i==70): + return (122,1,119) + elif (i==71): + return (73,0,106) + elif (i==72): + return (255,255,229) + elif (i==73): + return (247,252,185) + elif (i==74): + return (217,240,163) + elif (i==75): + return (173,221,142) + elif (i==76): + return (120,198,121) + elif (i==77): + return (65,171,93) + elif (i==78): + return (35,132,67) + elif (i==79): + return (0,104,55) + elif (i==80): + return (0,69,41) + elif (i==81): + return (255,255,217) + elif (i==82): + return (237,248,177) + elif (i==83): + return (199,233,180) + elif (i==84): + return (127,205,187) + elif (i==85): + return (65,182,196) + elif (i==86): + return (29,145,192) + elif (i==87): + return (34,94,168) + elif (i==88): + return (37,52,148) + elif (i==89): + return (8,29,88) + elif (i==90): + return (255,255,229) + elif (i==91): + return (255,247,188) + elif (i==92): + return (254,227,145) + elif (i==93): + return (254,196,79) + elif (i==94): + return (254,153,41) + elif (i==95): + return (236,112,20) + elif (i==96): + return (204,76,2) + elif (i==97): + return (153,52,4) + elif (i==98): + return (102,37,6) + elif (i==99): + return (255,255,204) + elif (i==100): + return (255,237,160) + elif (i==101): + return (254,217,118) + elif (i==102): + return (254,178,76) + elif (i==103): + return (253,141,60) + elif (i==104): + return (252,78,42) + elif (i==105): + return (227,26,28) + elif (i==106): + return (189,0,38) + elif (i==107): + return (128,0,38) + + return (255,255,255) + +def drawMissingInput(image): + import cv2 + width = image.shape[1] + height = image.shape[0] + color = (0,0,255) + cv2.line(image, pt1=(0,0), pt2=(width,height), color=color, thickness=12) + cv2.line(image, pt1=(0,0+height), pt2=(width,0), color=color, thickness=12) + font = cv2.FONT_HERSHEY_SIMPLEX + org = (int(width/2)-300,int(height/2)) + fontScale = 2 + color = (0,0,0) + thickness = 2 + message = 'Incomplete Input' + image = cv2.putText(image, message , org, font, fontScale, color, thickness, cv2.LINE_AA) + org = (int(width/2)+2-300,int(height/2)+2) + color = (255,255,255) + image = cv2.putText(image, message , org, font, fontScale, color, thickness, cv2.LINE_AA) + return image + +def drawMocapNETInput(input2D,image,flipX=False): + import cv2 + if (type(image)==type(None)): + print("Invalid Image given, can't do anything with it") + return image + width = image.shape[1] + height = image.shape[0] + #print("Drawing output to ",width,"x",height," cvmat") + + + font = cv2.FONT_HERSHEY_SIMPLEX + fontScale = 0.4 + thickness = 1 + color = (0,0,0) + + for jointRaw in input2D: + #print("Joint ",jointRaw) + jointSplit = jointRaw.lower().split("_",1) + if (len(jointSplit)>1): + joint = jointSplit[1].lower() + jointName2DX = "2dx_"+joint + jointName2DY = "2dy_"+joint + if ( jointName2DX in input2D ) and ( jointName2DY in input2D ): + if (flipX): + x2D = int((1.0-input2D[jointName2DX])*width) + else: + x2D = int(input2D[jointName2DX]*width) + y2D = int(input2D[jointName2DY]*height) + #print("IS Joint ",joint,x2D,y2D) + color=(0,255,255) + circleSize = 2 + if (len(joint)>0): + #We have a joint Name + if not 'head' in joint: + circleSize = 4 #body joints are bigger + + + if (len(joint)>1): + if (joint[len(joint)-2]=='.') and (joint[len(joint)-1]=='r'): #Right Joint + color=(0,255,0) #GREEN COLOR + if (joint[len(joint)-2]=='.') and (joint[len(joint)-1]=='l'): #Left Joint + color=(0,0,255) #RED COLOR + + if (joint[0]=='r'): #Right Joint + color=(0,255,0) #GREEN COLOR + elif (joint[0]=='l'): #Left Joint + color=(0,0,255) #RED COLOR + elif ("head_l" in joint): + color=(0,0,255) #RED COLOR + #image = cv2.putText(image, "%s" % (joint.replace("head_","")) , (x2D+2,y2D), font, fontScale, color, thickness, cv2.LINE_AA) + elif ("head_r" in joint): + color=(0,255,0) #GREEN COLOR + #image = cv2.putText(image, "%s" % (joint.replace("head_","")) , (x2D+2,y2D), font, fontScale, color, thickness, cv2.LINE_AA) + #else: + # image = cv2.putText(image, "%s" % (joint) , (x2D+2,y2D), font, fontScale+0.7, color, thickness, cv2.LINE_AA) + cv2.circle(image,(x2D,y2D),circleSize,color,cv2.FILLED) + #if ("lshoulder"==joint) or ("lelbow"==joint) or ("lhand"==joint): + # image = cv2.putText(image, "%s" % (joint) , (x2D+2,y2D), font, fontScale, color, thickness, cv2.LINE_AA) + if ("head_reye"==joint) or ("head_leye"==joint) or ("reye"==joint) or ("leye"==joint): + color=(255,0,0) + circleSize = 4 + image = cv2.putText(image, "%s" % (joint) , (x2D+2,y2D), font, fontScale, color, thickness, cv2.LINE_AA) + cv2.circle(image,(x2D,y2D),circleSize,color,cv2.FILLED) + + if ('__' in joint): + image = cv2.putText(image, joint , (x2D+2,y2D), font, fontScale, color, thickness, cv2.LINE_AA) + return image + +def drawMocapNETOutput(mnet,image,xOffset=0): #set xOffset to -400 to make visualization more clean by seperating 2D/3D + import cv2 + if (type(image)==type(None)): + print("Invalid Image given, can't do anything with it") + return image + width = image.shape[1] + height = image.shape[0] + #print("Drawing output to ",width,"x",height," cvmat") + + jointID = 0 + for joint in mnet.bvhJointList: + #---------------------------------------------------------------------------------------------- + jointParentID = mnet.bvhJointParentList[jointID] + jointParent = mnet.bvhJointList[jointParentID] + #print("Joint ",joint) + #print("Joint Parent",jointParent) + + #Enforce Joint LowerCase + joint = joint.lower() + jointParent = mnet.bvhJointList[jointParentID].lower() + + doThisDraw = 1 + #---------------------------------------------------------------------------------------------- + jointName2DX = "2DX_"+joint + jointName2DY = "2DY_"+joint + if ( not jointName2DX in mnet.output2D ) or ( not jointName2DY in mnet.output2D ): + doThisDraw = 0 + elif (mnet.output2D[jointName2DX]==0.0) and (mnet.output2D[jointName2DY]==0.0): + doThisDraw = 0 + else: + xA2D = xOffset + int((1.0-mnet.output2D[jointName2DX])*width) + yA2D = int(mnet.output2D[jointName2DY]*height) + #---------------------------------------------------------------------------------------------- + jointParentName2DX = "2DX_"+jointParent + jointParentName2DY = "2DY_"+jointParent + if ( not jointParentName2DX in mnet.output2D ) or ( not jointParentName2DY in mnet.output2D ): + doThisDraw = 0 + elif (mnet.output2D[jointParentName2DX]==0.0) and (mnet.output2D[jointParentName2DY]==0.0): + doThisDraw = 0 + else: + xB2D = xOffset + int((1.0-mnet.output2D[jointParentName2DX])*width) + yB2D = int(mnet.output2D[jointParentName2DY]*height) + #---------------------------------------------------------------------------------------------- + if (doThisDraw): + color=(255,0,0) #BLUE COLOR + if (joint[0]=='l'): + color=(0,0,255) #RED COLOR + if (joint[0]=='r'): + color=(0,255,0) #GREEN COLOR + cv2.line(image, pt1=(xA2D,yA2D), pt2=(xB2D,yB2D), color=color, thickness=12) + #---------------------------------------------------------------------------------------------- + jointID = jointID + 1 + #---------------------------------------------------------------------------------------------- + + for joint in mnet.bvhJointList: + #print("Joint ",joint) + joint = joint.lower() + jointName2DX = "2DX_"+joint + jointName2DY = "2DY_"+joint + if ( jointName2DX in mnet.output2D ) and ( jointName2DY in mnet.output2D ): + if (mnet.output2D[jointName2DX]!=0.0) or (mnet.output2D[jointName2DY]!=0.0): + x2D = xOffset + int((1.0-mnet.output2D["2DX_"+joint])*width) + y2D = int(mnet.output2D["2DY_"+joint]*height) + color=(0,255,255) + cv2.circle(image,(x2D,y2D),2,color) + #---------------------------------------------------------------------------------------------- + return image + + + + +def drawDescriptor(name,elements,image,x,y,w,h): + #------------------------------------ + if (elements.shape[1]==0): + return image + #------------------------------------ + import cv2 + block = int(w / elements.shape[1]) + #------------------------------------ + if (block==0): + return image + #------------------------------------ + #print("WIDTH ",w," BLOCK",block," ELEMENTS ",elements.shape[1]) + eI = 0 + for xI in range(x,x+w-block,block): + xA2D=xI + yA2D=y + xB2D=xI+block + yB2D=y+h + #---------------------------------------------- + val = elements[0][eI] + #---------------------------------------------- + greenValue = 0.0 + blueValue = 0.0 + #---------------------------------------------- + if (val<0.0): + blueValue=abs(val) + else: + greenValue=val + #---------------------------------------------- + color=( + min(255,int(255.0 * blueValue)), + min(255,int(255.0 * greenValue)), + min(255,int(25.5 * greenValue)) + ) + #---------------------------------------------- + if (xA2D!=0.0) and (yA2D!=0.0) and (xB2D!=0.0) and (yB2D!=0.0): + cv2.line(image, pt1=(xA2D,yA2D), pt2=(xB2D,yB2D), color=color, thickness=12) + eI +=1 + eI = min(eI,elements.shape[1]-1) + #---------------------------------------------- + font = cv2.FONT_HERSHEY_SIMPLEX + fontScale = 0.5 + thickness = 1 + org = (x+10,y+int(h/2)+5) + color = (0,0,0) + image = cv2.putText(image, name , org, font, fontScale, color, thickness, cv2.LINE_AA) + org = (x+8,y+int(h/2)+3) + color = (255,255,255) + image = cv2.putText(image, name , org, font, fontScale, color, thickness, cv2.LINE_AA) + + return image + + + +def printNSDM(nsdm): + import math + from tools import bcolors + NSRMDimension = int(math.sqrt(len(nsdm))) + eI = 0 + for yI in range(0,NSRMDimension): + for xI in range(0,NSRMDimension): + #------------------------- + val = nsdm[eI] + eI +=1 + if (val==0.0): + print(bcolors.FAIL,end=" ") + elif (val<0.0): + print(bcolors.OKBLUE,end="") + else: + print(bcolors.OKGREEN,end=" ") + print("%0.2f " % val,end="") + print(bcolors.ENDC,end="") + print(" ") + + +def drawNSRM(name,elements,image,x,y,w,h): + import cv2 + import math + #print("Draw NSRM with ",len(elements)," elements ") + NSRMDimension = int(math.sqrt(len(elements))) + blockX = int(w/NSRMDimension) + blockY = int(h/NSRMDimension) + + #print("WIDTH ",w," BLOCK",block," ELEMENTS ",elements.shape[1]) + if (NSRMDimension<4): + print("drawNSRM not drawing matrix with len(elements) = ",len(elements)) + return image + + eI = 0 + for yI in range(0,NSRMDimension): + for xI in range(0,NSRMDimension): + xA2D=x + xI*blockX + yA2D=y + yI*blockY + xB2D=xA2D+blockX + yB2D=yA2D+blockY + #------------------------- + val = elements[eI] + eI +=1 + #------------------------- + redValue = 0 + greenValue = 0 + blueValue = 0 + #------------------------- + if (val==0.0): + redValue = 1 + elif (val<0.0): + blueValue = abs(val)#/2 + else: + greenValue = val#/2 + #------------------------- + color=( + int(255.0 * blueValue), #B + int(255.0 * greenValue), #G + int(255.0 * redValue) #R + ) + #------------------------- + cv2.rectangle(image, pt1=(xA2D,yA2D), pt2=(xB2D,yB2D), color=color, thickness=-1) + #----------------------------------- + font = cv2.FONT_HERSHEY_SIMPLEX + fontScale = 0.5 + thickness = 1 + org = (x,y-10) + color = (0,0,0) + image = cv2.putText(image, name , org, font, fontScale, color, thickness, cv2.LINE_AA) + org = (x-2,y-8) + color = (255,255,255) + image = cv2.putText(image, name , org, font, fontScale, color, thickness, cv2.LINE_AA) + + return image + + + + +def drawMAE2DError(name,mae,image,x,y,w,h): + if (mae<=0.0): + return + import cv2 + #----------------------------------------- + color = (123,123,123) + if (mae<127): + color = (0,255-(mae*2),0) #B G R + elif (mae<255): + color = (0,mae,mae) #B G R + else: + color = (0,0,min(255,mae-255)) #B G R + #----------------------------------------- + xA2D=x + yA2D=y + xB2D=x+w + yB2D=y+h + cv2.rectangle(image, pt1=(xA2D,yA2D), pt2=(xB2D,yB2D), color=color, thickness=-1) + #----------------------------------------- + font = cv2.FONT_HERSHEY_SIMPLEX + fontScale = 0.4 + thickness = 1 + #----------------------------------------- + yOffset=15 + message = '%s ' % (name) + org = (x+2,y+2+yOffset) + color = (0,0,0) + image = cv2.putText(image, message , org, font, fontScale, color, thickness, cv2.LINE_AA) + org = (x,y+yOffset) + color = (255,255,255) + image = cv2.putText(image, message , org, font, fontScale, color, thickness, cv2.LINE_AA) + #----------------------------------------- + message = '%0.2f' % (mae) + color = (0,0,0) + org = (x,y+20+yOffset) + image = cv2.putText(image, message , org, font, fontScale, color, thickness, cv2.LINE_AA) + color = (255,255,255) + org = (x+2,y+22+yOffset) + image = cv2.putText(image, message , org, font, fontScale, color, thickness, cv2.LINE_AA) + #----------------------------------------- + + +def calculateRelativeValue(y,h,value,minimum,maximum): + if (maximum==minimum): + return int(y + (h/2)) + #------------------------------------------------- + #TODO IMPROVE THIS! + vRange = (maximum - minimum) + return int( y + (h/2) - ( value / vRange ) * (h/2) ) + + +def drawMocapNETSinglePlot(history,plotNumber,itemName,image,x,y,w,h,minimumValue,maximumValue): + import cv2 + color=getColor(plotNumber) + if (minimumValue==maximumValue): + color = (40,40,40) + + cv2.line(image, pt1=(x,y+h), pt2=(x+w,y+h), color=color, thickness=1) + cv2.line(image, pt1=(x,y), pt2=(x,y+h), color=color, thickness=1) + + font = cv2.FONT_HERSHEY_SIMPLEX + org = (x,y) + fontScale = 0.3 + tColor = (123,123,123) + thickness = 1 + message = '%s #%u ' % (itemName,plotNumber) + image = cv2.putText(image, message , org, font, fontScale, color, thickness, cv2.LINE_AA) + message = 'Max %0.2f ' % (maximumValue) + org = (x,y+10) + image = cv2.putText(image, message , org, font, fontScale, color, thickness, cv2.LINE_AA) + message = 'Min %0.2f ' % (minimumValue) + org = (x,y+h+10) + image = cv2.putText(image, message , org, font, fontScale, color, thickness, cv2.LINE_AA) + + + for frameID in range(1,len(history)): + #Old code + #previousValue = int(y+history[frameID-1][itemName] + h/2) + #nextValue = int(y+history[frameID][itemName] + h/2) + #------------------------------------------------------------------------------------- + previousValue = calculateRelativeValue(y,h,history[frameID-1][itemName],minimumValue,maximumValue) + nextValue = calculateRelativeValue(y,h,history[frameID][itemName],minimumValue,maximumValue) + #------------------------------------------------------------------------------------- + jointPointPrev = (int(x+ frameID-1), previousValue ) + jointPointNext = (int(x+ frameID), nextValue ) + #cv::Scalar usedColor = getColorFromIndex(joint); + if (itemName=="hip_yrotation"): + color=(0,0,255) + + cv2.line(image, pt1=jointPointPrev, pt2=jointPointNext, color=color, thickness=1) + + #old code + #org = (int(x+len(history)),int(y+history[len(history)-1][itemName] + h/2)) + org = (int(x+len(history)), calculateRelativeValue(y,h,history[len(history)-1][itemName],minimumValue,maximumValue) ) + message = '%0.2f' % (history[len(history)-1][itemName]) + image = cv2.putText(image, message , org, font, fontScale, color, thickness, cv2.LINE_AA) +#--------------------------------------------------------------------------------------------- + + +def drawMocapNETSinglePlotValueList(valueList,plotNumber,itemName,image,x,y,w,h,minimumValue,maximumValue): + import cv2 + color=getColor(plotNumber) + if (minimumValue==maximumValue): + color = (40,40,40) #Dead plot + + cv2.line(image, pt1=(x,y+h), pt2=(x+w,y+h), color=color, thickness=1) + cv2.line(image, pt1=(x,y), pt2=(x,y+h), color=color, thickness=1) + + font = cv2.FONT_HERSHEY_SIMPLEX + org = (x,y) + fontScale = 0.3 + tColor = (123,123,123) + thickness = 1 + message = '%s #%u ' % (itemName,plotNumber) + image = cv2.putText(image, message , org, font, fontScale, color, thickness, cv2.LINE_AA) + message = 'Max %0.2f ' % (maximumValue) + org = (x,y+10) + image = cv2.putText(image, message , org, font, fontScale, color, thickness, cv2.LINE_AA) + message = 'Min %0.2f ' % (minimumValue) + org = (x,y+h+10) + image = cv2.putText(image, message , org, font, fontScale, color, thickness, cv2.LINE_AA) + + + for frameID in range(1,len(valueList)): + #Old code + #previousValue = int(y+history[frameID-1][itemName] + h/2) + #nextValue = int(y+history[frameID][itemName] + h/2) + #------------------------------------------------------------------------------------- + previousValue = calculateRelativeValue(y,h,valueList[frameID-1],minimumValue,maximumValue) + nextValue = calculateRelativeValue(y,h,valueList[frameID],minimumValue,maximumValue) + #------------------------------------------------------------------------------------- + jointPointPrev = (int(x+ frameID-1), previousValue ) + jointPointNext = (int(x+ frameID), nextValue ) + #cv::Scalar usedColor = getColorFromIndex(joint); + if (itemName=="hip_yrotation"): + color=(0,0,255) + + cv2.line(image, pt1=jointPointPrev, pt2=jointPointNext, color=color, thickness=1) + + #old code + #org = (int(x+len(valueList)),int(y+valueList[len(valueList)-1] + h/2)) + org = (int(x+len(valueList)), calculateRelativeValue(y,h,valueList[len(valueList)-1],minimumValue,maximumValue) ) + message = '%0.2f' % (valueList[len(valueList)-1]) + image = cv2.putText(image, message , org, font, fontScale, color, thickness, cv2.LINE_AA) +#--------------------------------------------------------------------------------------------- + + + +def drawMocapNETAllPlots(history,width,height,minimumLimits=dict(),maximumLimits=dict()): + import cv2 + import numpy as np +#------------------------------------------------- + imageForPlot = np.zeros([height,width,3],dtype=np.uint8) +#------------------------------------------------- + margin = 25 + x = 0 + y = margin + widthOfGraphs = 80 + heightOfGraphs = 80 +#------------------------------------------------- + plotNumber = 0 + if (len(history)>0): + for itemName in history[0].keys(): + minimumValue=-180.0 + maximumValue= 180.0 + if (itemName in minimumLimits) and (itemName in maximumLimits): + minimumValue=float(minimumLimits[itemName]) + maximumValue=float(maximumLimits[itemName]) + + if (minimumValue!=maximumValue): + drawMocapNETSinglePlot(history,plotNumber,itemName,imageForPlot,x,y,widthOfGraphs,heightOfGraphs,minimumValue,maximumValue) + plotNumber=plotNumber+1 + y = y + heightOfGraphs + margin + if (y + heightOfGraphs > height - heightOfGraphs): + y = margin + x = x + widthOfGraphs + margin + cv2.imshow('Motion History',imageForPlot) + return imageForPlot +#------------------------------------------------- + + + +def drawMocapNETFrequencyPlots(history): + import numpy as np + import matplotlib.pyplot as plt +#------------------------------------------------- + if (len(history)>0): + for itemName in history[0].keys(): + output="freq_%s.png" % itemName + + data=list() + for frameID in range(1,len(history)): + data.append(float(history[frameID][itemName])) + plt.cla() + plt.hist(data, bins=250) + # Add labels and title + plt.xlabel('Value') + plt.ylabel('Frequency') + plt.title('Histogram of %s of %u values'%(itemName,len(history))) + # Save figure as PNG file + plt.savefig(output) + #fig.savefig(output) +#------------------------------------------------- + + + +def visualizeMocapNETEnsemble(mnet,annotated_image,plotBVHChannels=0,bvhAnglesForPlotting=list(),economic=False): + try: + #from MocapNETVisualization import drawMocapNETOutput,drawMocapNETAllPlots,drawMissingInput,drawDescriptor,drawNSRM,drawMAE2DError + #------------------------------------------------------------------------------------ + if ("upperbody" in mnet.ensemble): + drawMocapNETOutput(mnet,annotated_image) #only draw 3D ouput if upperbody is loaded and working.. + + drawMocapNETInput(mnet.input2D,annotated_image) + if (economic): + return annotated_image,annotated_image + #------------------------------------------------------------------------------------ + + width = annotated_image.shape[1] + height = annotated_image.shape[0] + + locY = 10 + if ("upperbody" in mnet.ensemble) and (mnet.ensemble["upperbody"].configuration["decompositionType"]!=""): + dcmp = mnet.ensemble["upperbody"].configuration["decompositionType"] + drawDescriptor("%s upperbody" % dcmp,mnet.ensemble["upperbody"].inputReadyForTF,annotated_image,10,locY,annotated_image.shape[1]-20,5); locY+=15 + if ("lowerbody" in mnet.ensemble) and (mnet.ensemble["lowerbody"].configuration["decompositionType"]!=""): + dcmp = mnet.ensemble["lowerbody"].configuration["decompositionType"] + drawDescriptor("%s lowerbody"% dcmp,mnet.ensemble["lowerbody"].inputReadyForTF,annotated_image,10,locY,annotated_image.shape[1]-20,5); locY+=15 + if ("face" in mnet.ensemble) and (mnet.ensemble["face"].configuration["decompositionType"]!=""): + dcmp = mnet.ensemble["face"].configuration["decompositionType"] + drawDescriptor("%s face"% dcmp,mnet.ensemble["face"].inputReadyForTF,annotated_image,10,locY,annotated_image.shape[1]-20,5); locY+=15 + if ("reye" in mnet.ensemble) and (mnet.ensemble["reye"].configuration["decompositionType"]!=""): + dcmp = mnet.ensemble["reye"].configuration["decompositionType"] + drawDescriptor("%s reye"% dcmp,mnet.ensemble["reye"].inputReadyForTF,annotated_image,10,locY,annotated_image.shape[1]-20,5); locY+=15 + dcmp = mnet.ensemble["leye"].configuration["decompositionType"] + drawDescriptor("%s leye"% dcmp,mnet.ensemble["leye"].inputReadyForTF,annotated_image,10,locY,annotated_image.shape[1]-20,5); locY+=15 + if ("mouth" in mnet.ensemble) and (mnet.ensemble["mouth"].configuration["decompositionType"]!=""): + dcmp = mnet.ensemble["mouth"].configuration["decompositionType"] + drawDescriptor("%s mouth"% dcmp,mnet.ensemble["mouth"].inputReadyForTF,annotated_image,10,locY,annotated_image.shape[1]-20,5); locY+=15 + if ("lhand" in mnet.ensemble) and (mnet.ensemble["lhand"].configuration["decompositionType"]!=""): + dcmp = mnet.ensemble["lhand"].configuration["decompositionType"] + drawDescriptor("%s lhand"% dcmp,mnet.ensemble["lhand"].inputReadyForTF,annotated_image,10,locY,annotated_image.shape[1]-20,5); locY+=15 + dcmp = mnet.ensemble["rhand"].configuration["decompositionType"] + drawDescriptor("%s rhand"% dcmp,mnet.ensemble["rhand"].inputReadyForTF,annotated_image,10,locY,annotated_image.shape[1]-20,5); locY+=15 + #-------------------------------------------------------------------------------------------------------------- + NSRM_Y = 120 + NSRM_Body_Y = NSRM_Y + if ("upperbody" in mnet.ensemble): + drawNSRM("NSRM Up",mnet.ensemble["upperbody"].NSRM,annotated_image,10,NSRM_Y,100,100); NSRM_Y+=130 + if ("lowerbody" in mnet.ensemble): + drawNSRM("NSRM Down",mnet.ensemble["lowerbody"].NSRM,annotated_image,120,NSRM_Body_Y,100,100);# NSRM_Y+=130 + if ("face" in mnet.ensemble): + drawNSRM("NSRM Face",mnet.ensemble["face"].NSRM,annotated_image,10,NSRM_Y,100,100); NSRM_Y+=130 + if ("leye" in mnet.ensemble): + drawNSRM("NSRM LEye",mnet.ensemble["leye"].NSRM,annotated_image,120,NSRM_Y,100,100); + if ("reye" in mnet.ensemble): + drawNSRM("NSRM REye",mnet.ensemble["reye"].NSRM,annotated_image,10,NSRM_Y,100,100); NSRM_Y+=130 + if ("mouth" in mnet.ensemble): + drawNSRM("NSRM Mouth",mnet.ensemble["mouth"].NSRM,annotated_image,10,NSRM_Y,100,100); NSRM_Y+=130 + if ("lhand" in mnet.ensemble): + drawNSRM("NSRM LHand",mnet.ensemble["lhand"].NSRM,annotated_image,10,NSRM_Y,100,100); + drawNSRM("NSRM RHand",mnet.ensemble["rhand"].NSRM,annotated_image,120,NSRM_Y,100,100); NSRM_Y+=130 + #-------------------------------------------------------------------------------------------------------------- + drawMAE2DError("2D M.A.E.",mnet.lastMAEErrorInPixels,annotated_image,width-70,height-120,width-10,height-90) + #-------------------------------------------------------------------------------------------------------------- + + if (len(mnet.history_hz_NN)>0): + drawMocapNETSinglePlotValueList(mnet.history_hz_NN,1,"NN FPS",annotated_image,width-70,120,70,70,0.0,60.0) + + if (len(mnet.history_hz_HCD)>0): + drawMocapNETSinglePlotValueList(mnet.history_hz_HCD,1,"HCD FPS",annotated_image,width-70,220,70,70,0.0,60.0) + + #if (mnet.incompleteUpperbodyInput and mnet.incompleteLowerbodyInput): + # drawMissingInput(annotated_image) + if (plotBVHChannels==1): + plotImage = drawMocapNETAllPlots(bvhAnglesForPlotting,1920,920,minimumLimits=mnet.outputBVHMinima,maximumLimits=mnet.outputBVHMaxima) + return annotated_image,plotImage + except: + print("Failed visualizing") + return annotated_image,annotated_image + + + + +if __name__ == '__main__': + print("MocapNETVisualization.py is a library and cannot run standalone") diff --git a/src/python/mnet4/NSDM.py b/src/python/mnet4/NSDM.py new file mode 100755 index 0000000..2c8efa0 --- /dev/null +++ b/src/python/mnet4/NSDM.py @@ -0,0 +1,521 @@ +#!/usr/bin/python3 + +""" +Author : "Ammar Qammaz" +Copyright : "2022 Foundation of Research and Technology, Computer Science Department Greece, See license.txt" +License : "FORTH" +""" + +import numpy as np +from enum import Enum + +goFromDegreesToRad=np.float32(np.pi/180.0) +goFromRadToDegrees=np.float32(180.0/np.pi) + +def getJoint2DDistancePoints(aX,aY,bX,bY): + xDistance=np.float32(bX-aX) + yDistance=np.float32(bY-aY) + return np.float32(np.sqrt( (xDistance*xDistance) + (yDistance*yDistance) )) + +def getNumberOfSquaredCompressedSquaredJoints(): + countList=len(getBodyNoHandsList()) + numberOfSquaredCompressedJoints=countList*countList*2 + return numberOfSquaredCompressedJoints + + +def getAngleToAlignToZero(iX,iY,jX,jY,NSRMNormalizeAngles=0): + #--------------------------------------------- + if ( (iX==jX) and (iY==jY) ): + return np.float32(0.0) + #--------------------------------------------- + #We have points a, b and c and we want to calculate angle b + aX= iX*100 + aY= iY*100 + #--------------------------------------------- + bX= jX*100 + bY= jY*100 + #--------------------------------------------- + lengthBetweenAAndB = getJoint2DDistancePoints(aX,aY,bX,bY) + #--------------------------------------------- + cX= bX + cY= bY - lengthBetweenAAndB + #--------------------------------------------- + #fprintf(stderr,"We want to align A(%0.2f,%0.2f) to C(%0.2f,%0.2f) with pivot B(%0.2f,%0.2f)\n",aX,aY,cX,cY,bX,bY) + #fprintf(stderr,"length AB = %0.2f\n",lengthBetweenAAndB); + #fprintf(stderr,"bY = %0.2f\n",bY); + #fprintf(stderr,"cY = %0.2f = %0.2f - %0.2f\n",cY,bY,lengthBetweenAAndB); + #Calulate vector a->b + abX = bX - aX + abY = bY - aY + #calculate vector c->b + cbX = bX - cX + cbY = bY - cY + #--------------------------------------------- + dot = np.float32( (abX * cbX) + (abY * cbY) ) # dot product + cross = np.float32( (abX * cbY) - (abY * cbX) ) # cross product + #--------------------------------------------- + alpha = np.arctan2(cross,dot) #arctan2 returns a value in the range [-pi, pi] + #--------------------------------------------- + #fprintf(stderr,"Angle is %0.2f rad or %0.2f degrees \n",alpha,alpha*goFromRadToDegrees); + if (NSRMNormalizeAngles==2): + return np.float32( (2.0*alpha) / np.pi) # Normalize output in range [-1..1] + if (NSRMNormalizeAngles==1): + #This should actually be (2*alpha) / np.pi + #arctan(R) -> [ -pi/2 , pi/2] + return np.float32(alpha / np.pi) # Normalize output in range [-1/2..1/2] + else: + return np.float32(alpha) # Output in range [-pi..pi] + + +""" + This is the new function to make resolving 2D coordinates on a vector easier and safer (but slower :P) +""" +def getJoint2DXYV(rules,positions,jointName): + #------------------------------------------------------------------------------------------ + x = np.float32(0.0) + y = np.float32(0.0) + visibility = np.float32(0.0) + #------------------------------------------------------------------------------------------ + positionDataLength = len(positions) + if (positionDataLength==0): + print("getJoint2DXYV cannot resolve positions for joint ",jointName," with no vector data") + return x,y,visibility + + if (not rules['inputJointMap'].checkJointListDimensions(positions)): + print("getJoint2DXYV cannot resolve positions for joint ",jointName," input joint map has a different dimension size..") + return x,y,visibility + + + + #Get correct indexes for jointName + #-------------------------------------------------------- + jID_X = rules['inputJointMap'].getJointID_2DX(jointName) + jID_Y = rules['inputJointMap'].getJointID_2DY(jointName) + jID_Vis = rules['inputJointMap'].getJointID_Visibility(jointName) + #-------------------------------------------------------- + if ( (positionDataLength<=jID_X) or (positionDataLength<=jID_Y) or (positionDataLength<=jID_Vis) ): + print("getJoint2DXYV cannot get positions for joint ",jointName," with a vector data of only ",positionDataLength) + return x,y,visibility + #-------------------------------------------------------- + if ( (jID_X==-1) or (jID_Y==-1) or (jID_Vis==-1) ): + print("getJoint2DXYV could not resolve joint ",jointName," with vector data of ",positionDataLength) + return x,y,visibility + #-------------------------------------------------------- + #print("getJoint2DXYV(%s->%u/%u/%u) length %u"%(jointName,jID_X,jID_Y,jID_Vis,positionDataLength)) + #Pedantic behavior on missing data + if (positions[jID_X]==0.0) or (positions[jID_Y]==0.0) or (positions[jID_Vis]==0.0): + return x,y,visibility + #------------------------------------ + x = np.float32(positions[jID_X]) + y = np.float32(positions[jID_Y]) + visibility = np.float32(positions[jID_Vis]) + #------------------------------------ + return x,y,visibility + + + +def rotate2DPointsTest(cx,cy,jX,jY,angleToRotateInRadians): + #----------------------------------------------- + s = np.float32( np.sin(angleToRotateInRadians) ) + c = np.float32( np.cos(angleToRotateInRadians) ) + #----------------------------------------------- + jX = np.float32(jX - cx) + jY = np.float32(jY - cy) + #----------------------------------------------- + xnew = np.float32( (jX * c) - (jY * s) ) + ynew = np.float32( (jX * s) + (jY * c) ) + #----------------------------------------------- + return xnew + cx , ynew + cy + + +def rotate2DPointsBasedOnJointAsCenter(rules,positions,angleToRotateInRadians,jointNameCenter): + if (len(positions)==0): + print("rotate2DPointsBasedOnJointAsCenter cannot work without input.. \n") + return positions + + s = np.float32( np.sin(angleToRotateInRadians) ) + c = np.float32( np.cos(angleToRotateInRadians) ) + + #-------------------------------------------------------- + cx,cy,cVisibility = getJoint2DXYV(rules,positions,jointNameCenter) + #-------------------------------------------------------- + + if (cVisibility==0.0): + print("rotate2DPointsBasedOnJointAsCenter: cannot work without pivot joint .. \n") + return positions + + + result = positions + + for jID in range(0,int(len(rules['NSDM'])) ): + #-------------------------------------------------------- + jointName=rules['NSDM'][jID]['joint'] + jX,jY,jVisibility = getJoint2DXYV(rules,positions,jointName) + #-------------------------------------------------------- + #printf("Rotating point %0.2f,%0.2f using pivot %0.2f,%0.2f by %0.2f deg -> "%(jX,jY,cx,cy,angle)) + + #Translate point back to origin: + jX = np.float32(jX - cx) + jY = np.float32(jY - cy) + + #Rotate point + xnew = np.float32( (jX * c) - (jY * s) ) + ynew = np.float32( (jX * s) + (jY * c) ) + + #Translate point back: + jID_X = rules['inputJointMap'].getJointID_2DX(jointName) + jID_Y = rules['inputJointMap'].getJointID_2DY(jointName) + jID_Vis = rules['inputJointMap'].getJointID_Visibility(jointName) + #---------------------------------------------------------------- + result[jID_X] = np.float32(xnew + cx) + result[jID_Y] = np.float32(ynew + cy) + result[jID_Vis] = np.float32(jVisibility) + + #printf("%0.2f,%0.2f\n"%(result[jID*3+0],result[jID*3+1])); + + return result + + + + +def performNSRMAlignment(thisInput,configuration): + angleToRotateInRadians = 0.0 + NSRMUseAlignmentToPivot = configuration['eNSRM'] + NSRMNormalizeAngles = 0 + if ("NSRMNormalizeAngles" in configuration) and (configuration["NSRMNormalizeAngles"]==1): + NSRMNormalizeAngles = 1 + if (NSRMUseAlignmentToPivot==1): + pivotPoint = configuration['Alignment'][0]['jointStart'] + referencePoint = configuration['Alignment'][0]['jointEnd'] + #----------------------------------------------------------------------------------------------- + if (pivotPoint!=referencePoint): + pivotX,pivotY,pivotVisibility = getJoint2DXYV(configuration,thisInput,pivotPoint) + #-------------------------------------------------------------------------------------------- + referenceX,referenceY,referenceVisibility = getJoint2DXYV(configuration,thisInput,referencePoint) + #-------------------------------------------------------------------------------------------- + if ((pivotVisibility!=0) and (referenceVisibility!=0)): + angleToRotateInRadians = getAngleToAlignToZero(pivotX,pivotY,referenceX,referenceY,NSRMNormalizeAngles) + rotatedInput = rotate2DPointsBasedOnJointAsCenter(configuration,thisInput,angleToRotateInRadians,pivotPoint) + return angleToRotateInRadians,rotatedInput + else: + print("Pivot Point ",pivotPoint," and Reference Point ",referencePoint," are the same\n") + #----------------------------------------------------------------------------------------------- + return angleToRotateInRadians,thisInput + + + + +def getCompositeLabel(jointA,jointB,xOffset,yOffset,virtualPointType): + labelI=jointA + labelIX="" + labelIY="" + if (virtualPointType==1): + if (xOffset<0): + labelIX="minus" + elif (xOffset>0): + labelIX="plus" + #---------------------------------- + if (yOffset<0): + labelIY="minus" + elif (yOffset>0): + labelIY="plus" + #---------------------------------- + labelI="virtual_"+jointA+"_x_"+labelIX+str(xOffset).replace('.','_').replace('-','_')+"_y_"+labelIY+str(yOffset).replace('.','_').replace('-','_') + elif (virtualPointType==2): + #---------------------------------- + labelI="halfway_"+jointA+"_and_"+jointB + return labelI + + + + + + +def NSDMLabels(rules): + result=list() + + useXY=1 + useAngles=0 + if (rules['NSDMAlsoUseAlignmentAngles']==1): + useXY=0 + useAngles=1 + + numberOfNSDMRules=len(rules['NSDM']) + print("Rules Number ",numberOfNSDMRules) + + for i in range(0,numberOfNSDMRules): + for j in range(0,numberOfNSDMRules): + if (i==j): + if (i==0): + result.append("NSRM-angleUsedFor2DRotation_%u"%(i)) + else: + result.append("NSRM-scaleBetween_"+rules['NSDM'][i]['joint']+"_and_"+rules['NSDM'][i]['joint']) + else: + #----------------------------------------------------------------- + labelI = getCompositeLabel( + rules['NSDM'][i]['joint'], + rules['NSDM'][i]['halfWayFromThisAnd'], + rules['NSDM'][i]['xOffset'], + rules['NSDM'][i]['yOffset'], + rules['NSDM'][i]['isVirtual'] + ) + #----------------------------------------------------------------- + labelJ = getCompositeLabel( + rules['NSDM'][j]['joint'], + rules['NSDM'][j]['halfWayFromThisAnd'], + rules['NSDM'][j]['xOffset'], + rules['NSDM'][j]['yOffset'], + rules['NSDM'][j]['isVirtual'] + ) + #----------------------------------------------------------------- + if (useXY): + result.append("NSDM-%sX-%sX"%(labelI,labelJ)) + result.append("NSDM-%sY-%sY"%(labelI,labelJ)) + if (useAngles): + result.append("NSRM-%sY-%sY-Angle"%(labelI,labelJ)) + #----------------------------------------------------------------- + + #print("NSDM matrix will look like this ",result) + return result; + +def inputIsEnoughToCreateNSDM(rules,thisInput): + numberOfNSDMRules=len(rules['NSDM']) + numberOfJointIDs =len(thisInput) + for i in range(0,numberOfNSDMRules): + jointName=rules['NSDM'][i]['joint'] + if (not rules['inputJointMap'].getJointID_Exists(jointName)): + return False + return True + +def getListOfMissingNSRMJoints(rules,thisInput): + missingList=list() + numberOfNSDMRules=len(rules['NSDM']) + numberOfJointIDs =len(thisInput) + for i in range(0,numberOfNSDMRules): + jointName=rules['NSDM'][i]['joint'] + if (not rules['inputJointMap'].getJointID_Exists(jointName)): + missingList.append(jointName) + else: + iX,iY,iVisibility = getJoint2DXYV(rules,thisInput,jointName) + if (iVisibility==0.0): + missingList.append(jointName) + return missingList + + + +def getCompositePoint(rules,i,thisInput): + #----------------------------------------------------------- + if (len(thisInput)==0): + print("getCompositePoint called with no input for element ",i) + return np.float32(0.0),np.float32(0.0),np.float32(0.0),1 + #-------------------------------------------------------- + invalidPoint = 0 + jointName = rules['NSDM'][i]['joint'] + #----------------------------------------------------------- + iX,iY,iVisibility = getJoint2DXYV(rules,thisInput,jointName) + #----------------------------------------------------------- + if ( iX!=0.0 and iY!=0.0 and iVisibility!=0.0 ): + #--------------------------------------------------------------------------- + # Synthetic Points + #--------------------------------------------------------------------------- + if (rules['NSDM'][i]['isVirtual']==1): + iX=iX+rules['NSDM'][i]['xOffset'] + iY=iY+rules['NSDM'][i]['yOffset'] + elif (rules['NSDM'][i]['isVirtual']==2): + secondTargetJointName=rules['NSDM'][i]['halfWayFromThisAnd'] + secondTargetX,secondTargetY,secondTargetVisibility = getJoint2DXYV(rules,thisInput,secondTargetJointName) + if ((secondTargetX!=0.0) or (secondTargetY!=0.0)): + iX=np.float32((iX+secondTargetX)/2) + iY=np.float32((iY+secondTargetY)/2) + else: + invalidPoint = 1 + iX = np.float32(0.0) + iY = np.float32(0.0) + iVisibility = np.float32(0.0) + #--------------------------------------------------------------------------- + else: + #Added : Fixed bug! 11/5/2023 + #If either of X,Y is zero we treat the point as completely invisible + invalidPoint = 1 + iX = np.float32(0.0) + iY = np.float32(0.0) + iVisibility = np.float32(0.0) + #----------------------------------------------------------- + return iX,iY,iVisibility,invalidPoint + + + + +def createNSDMUsingRules(rules,thisInput,angleUsedToRotateInput): + result=list() + #----------------------------------------------------------------------------------------------------- + if (len(thisInput)==0): + print("createNSDMUsingRules called with no input") + return result + + + if (not rules['inputJointMap'].checkJointListDimensions(thisInput)): + print("createNSDMUsingRules called with incorrect input size ") + return thisInput + #----------------------------------------------------------------------------------------------------- + #----------------------------------------------------------------------------------------------------- + NSRMNormalizeAngles = 0 + if ("NSRMNormalizeAngles" in rules) and (rules["NSRMNormalizeAngles"]==1): + NSRMNormalizeAngles = 1 + + doNormalization = (rules['NSDMNormalizationMasterSwitch']==1) + useXY = True + useAngles = False + if (rules['eNSRM']==1) or (rules['NSDMAlsoUseAlignmentAngles']==1): + useXY = False + useAngles = True + doNormalization = False + #----------------------------------------------------------------------------------------------------- + #----------------------------------------------------------------------------------------------------- + #----------------------------------------------------------------------------------------------------- + + #----------------------------------------------------------------------------------------------------- + # ..Main NSRM parameters .. + #----------------------------------------------------------------------------------------------------- + numberOfNSDMRules=len(rules['NSDM']) + for i in range(0,numberOfNSDMRules): + #--------------------------------------------------------------------------- + iX,iY,iVisibility,iInvalidPoint = getCompositePoint(rules,i,thisInput) + #--------------------------------------------------------------------------- + for j in range(0,numberOfNSDMRules): + #--------------------------------------------------------------------------- + jX,jY,jVisibility,jInvalidPoint = getCompositePoint(rules,j,thisInput) + #--------------------------------------------------------------------------- + if (iInvalidPoint or jInvalidPoint): + #If any of the two joints is invalid, invalidate all output + if (useXY): + result.append(np.float32(0.0)) + result.append(np.float32(0.0)) + if (useAngles): + result.append(np.float32(0.0)) + else: + if (useXY): + result.append(np.float32(iX-jX)) + result.append(np.float32(iY-jY)) + if (useAngles): + result.append(getAngleToAlignToZero(iX,iY,jX,jY,NSRMNormalizeAngles)) + #--------------------------------------------------------------------------- + + #----------------------------------------------------------------------------------------------------- + #----------------------------------------------------------------------------------------------------- + # ..New eNSRM diagonal parameters .. + #----------------------------------------------------------------------------------------------------- + if (rules['eNSRM']==1): + elementID=0 + #------------------------------------------------------------ + iJointName=rules['NSDM'][0]['joint'] #Pivot point + iX,iY,iVisibility = getJoint2DXYV(rules,thisInput,iJointName) + #------------------------------------------------------------ + for j in range(0,numberOfNSDMRules): + elementID = j * numberOfNSDMRules + j # Calculate diagonal elementID based on j + #--------------------------------------------------------------- + jJointName = rules['NSDM'][j]['joint'] + jX,jY,jVisibility = getJoint2DXYV(rules,thisInput,jJointName) + #--------------------------------------------------------------- + if (jVisibility!=0.0) and (iVisibility!=0.0): + #Populate diagonal elements with distance from our pivot point + result[elementID] = getJoint2DDistancePoints(iX,iY,jX,jY) + else: + result[elementID] = np.float32(0.0) + #--------------------------------------------------------------- + #Overwrite first (null) element of NSRM matrix with the angle used to rotate the input + result[0]=np.float32(angleUsedToRotateInput); + #----------------------------------------------------------------------------------------------------- + #----------------------------------------------------------------------------------------------------- + #----------------------------------------------------------------------------------------------------- + + if (doNormalization): + #normalization is made for NSDM not xNSRM + #Normalizing results.. --------------------------------------- + numberOfNSDMScalingRules=len(rules['NormalizeNSDMBasedOn']) + if (numberOfNSDMScalingRules>0): + numberOfDistanceSamples=0 + sumOfDistanceSamples=0.0 + for i in range(0,numberOfNSDMScalingRules): + #------------------------------------------------------------------------ + iJointName=rules['NormalizeNSDMBasedOn'][i]['jointStart'] + iX,iY,iVisibility = getJoint2DXYV(rules,thisInput,iJointName) + #------------------------------------------------------------------------ + jJointName=rules['NormalizeNSDMBasedOn'][i]['jointEnd'] + jX,jY,jVisibility = getJoint2DXYV(rules,thisInput,jJointName) + #------------------------------------------------------------------------ + if (iJointName==jJointName): + print("Error: Normalization Rule ",i," points to same start/end joint ",iJointID," == ",jJointID) + distance = getJoint2DDistancePoints(iX,iY,jX,jY) + if (distance>0.0): + numberOfDistanceSamples=numberOfDistanceSamples+1 + sumOfDistanceSamples=sumOfDistanceSamples+distance + + #------------------------------------------------------------------------------------------------- + scaleDistance=1.0 + #------------------------------------------------------------------------------------------------- + if (numberOfDistanceSamples>0): + scaleDistance=sumOfDistanceSamples/numberOfDistanceSamples + #------------------------------------------------------------------------------------------------- + #print("NSDM Scale = ",scaleDistance," \n") + if (scaleDistance!=1.0): + for i in range(0,len(result)): + result[i]=np.float32(result[i]/scaleDistance) + #------------------------------------------------------------------------------------------------- + + #----------------------------------------------------------------------------------------------------- + #----------------------------------------------------------------------------------------------------- + #----------------------------------------------------------------------------------------------------- + if (useXY): + for i in range(0,len(result)): + if (result[i]!=0.0): + result[i]=np.float32(0.5+result[i]) + #------------------------------------------------------------------------------------------------- + #print(result) + #print("Result has ", len(result), " elements " ) + #print("Result should have ", int( 2 * len(bodyWithoutHands) * len(bodyWithoutHands) ), " elements " ) + return result; + + + +if __name__ == '__main__': + print("NSDM.py is a library it cannot be run standalone") + + sign = 1.0 #This is positive +1.0 + + iX=0.5; iY=0.5; jX=1.0; jY=1.0 + a = getAngleToAlignToZero(iX,iY,jX,jY) + print("Points A(%0.2f,%0.2f) -> B(%0.2f,%0.2f) => angle %0.4f" %(iX,iY,jX,jY,a)) + jX,jY = rotate2DPointsTest(iX,iY,jX,jY,sign * a) + print("Rotated it goes to -> B(%0.2f,%0.2f) => angle to align %0.4f" %(jX,jY,getAngleToAlignToZero(iX,iY,jX,jY))) + + iX=0.5; iY=0.5; jX=0.5; jY=1.0 + a = getAngleToAlignToZero(iX,iY,jX,jY) + print("Points A(%0.2f,%0.2f) -> B(%0.2f,%0.2f) => angle %0.4f" %(iX,iY,jX,jY,a)) + jX,jY = rotate2DPointsTest(iX,iY,jX,jY,sign * a) + print("Rotated it goes to -> B(%0.2f,%0.2f) => angle to align %0.4f" %(jX,jY,getAngleToAlignToZero(iX,iY,jX,jY))) + + iX=0.5; iY=0.5; jX=0.0; jY=1.0 + a = getAngleToAlignToZero(iX,iY,jX,jY) + print("Points A(%0.2f,%0.2f) -> B(%0.2f,%0.2f) => angle %0.4f" %(iX,iY,jX,jY,a)) + jX,jY = rotate2DPointsTest(iX,iY,jX,jY,sign * a) + print("Rotated it goes to -> B(%0.2f,%0.2f) => angle to align %0.4f" %(jX,jY,getAngleToAlignToZero(iX,iY,jX,jY))) + + iX=0.5; iY=0.5; jX=1.0; jY=0.5 + a = getAngleToAlignToZero(iX,iY,jX,jY) + print("Points A(%0.2f,%0.2f) -> B(%0.2f,%0.2f) => angle %0.4f" %(iX,iY,jX,jY,a)) + jX,jY = rotate2DPointsTest(iX,iY,jX,jY,sign * a) + print("Rotated it goes to -> B(%0.2f,%0.2f) => angle to align %0.4f" %(jX,jY,getAngleToAlignToZero(iX,iY,jX,jY))) + + iX=0.5; iY=0.5; jX=1.0; jY=0.0 + a = getAngleToAlignToZero(iX,iY,jX,jY) + print("Points A(%0.2f,%0.2f) -> B(%0.2f,%0.2f) => angle %0.4f" %(iX,iY,jX,jY,a)) + jX,jY = rotate2DPointsTest(iX,iY,jX,jY,sign * a) + print("Rotated it goes to -> B(%0.2f,%0.2f) => angle to align %0.4f" %(jX,jY,getAngleToAlignToZero(iX,iY,jX,jY))) + + iX=0.5; iY=0.5; jX=0.0; jY=0.0 + a = getAngleToAlignToZero(iX,iY,jX,jY) + print("Points A(%0.2f,%0.2f) -> B(%0.2f,%0.2f) => angle %0.4f" %(iX,iY,jX,jY,a)) + jX,jY = rotate2DPointsTest(iX,iY,jX,jY,sign * a) + print("Rotated it goes to -> B(%0.2f,%0.2f) => angle to align %0.4f" %(jX,jY,getAngleToAlignToZero(iX,iY,jX,jY))) + diff --git a/src/python/mnet4/PoseNET.py b/src/python/mnet4/PoseNET.py new file mode 100755 index 0000000..b90faf9 --- /dev/null +++ b/src/python/mnet4/PoseNET.py @@ -0,0 +1,891 @@ +#!/usr/bin/python3 + +""" +Author : "Ammar Qammaz" +Copyright : "2022 Foundation of Research and Technology, Computer Science Department Greece, See license.txt" +License : "FORTH" +""" + +#https://tfhub.dev/google/movenet/singlepose/lightning/4 +#https://tfhub.dev/google/lite-model/movenet/singlepose/lightning/tflite/int8/4 +#wget -q -O lite-model_movenet_singlepose_lightning_tflite_int8_4.tflite https://storage.googleapis.com/tfhub-lite-models/google/lite-model/movenet/singlepose/lightning/tflite/int8/4.tflite + +#zip movenet.zip movenet/* movenet/*/* + +trainingWidth=1920 +trainingHeight=1080 + +def img_resizeWithPadding(img,targetWidth,targetHeight): + import cv2 + width = targetWidth + height = targetHeight + h, w = img.shape[:2] + pad_bottom, pad_right = 0, 0 + ratio = w / h + + if h > height or w > width: + # shrinking image algorithm + interp = cv2.INTER_AREA + else: + # stretching image algorithm + interp = cv2.INTER_CUBIC + + w = width + h = round(w / ratio) + if h > height: + h = height + w = round(h * ratio) + pad_top = int(abs(height - h)/2) + pad_bottom = int(abs(height - h)/2) + pad_left = int(abs(width - w)/2) + pad_right = int(abs(width - w)/2) + + scaled_img = cv2.resize(img, (w, h), interpolation=interp) + padded_img = cv2.copyMakeBorder(scaled_img,pad_top,pad_bottom,pad_left,pad_right,borderType=cv2.BORDER_CONSTANT,value=[0,0,0]) + return padded_img + + +def img_resizeWithCrop(img, targetWidth,targetHeight): + import cv2 + import numpy as np + interpolation=cv2.INTER_AREA + h, w = img.shape[:2] + min_size = np.amin([h,w]) + + # Centralize and crop + crop_img = img[int(h/2-min_size/2):int(h/2+min_size/2), int(w/2-min_size/2):int(w/2+min_size/2)] + resized = cv2.resize(crop_img, (targetWidth,targetHeight), interpolation=interpolation) + return resized + +def normalizedCoordinatesAdaptForVerticalImage(sourceWidth,sourceHeight,targetWidth,targetHeight,nX,nY): + import numpy as np + h = sourceHeight + w = sourceWidth + min_size = np.amin([h,w]) + + nY = int(h/2-min_size/2) + int(nY*min_size) + nX = int(w/2-min_size/2) + int(nX*min_size) + + return float(nX/sourceWidth),float(nY/sourceHeight) + + + +def normalizedCoordinatesAdaptToResizedCrop(sourceWidth,sourceHeight,trainingWidth,trainingHeight,nX,nY): + if (sourceHeight>sourceWidth): + print("PoseNET.py normalizedCoordinatesAdaptToResizedCrop: FIX VERTICAL IMAGE") + import numpy as np + + return nX,nY + + +def getPoseNETBodyNameList(): + #nose, left eye, right eye, left ear, right ear, left shoulder, right shoulder, left elbow, right elbow, left wrist, right wrist, left hip, right hip, left knee, right knee, left ankle, right ankle + bn=list() + #--------------------------------------------------- + bn.append("head") #0 - nose + bn.append("endsite_eye.l") #1 - left_eye eye.l endsite_eye.l + bn.append("endsite_eye.r") #2 - right_eye eye.r endsite_eye.r + bn.append("lear") #3 - __temporalis02.l - left_ear ear.l + bn.append("rear") #4 - __temporalis02.r right_ear ear.r + bn.append("lshoulder") #5 - left_shoulder + bn.append("rshoulder") #6 - right_shoulder + bn.append("lelbow") #7 - left_elbow + bn.append("relbow") #8 - right_elbow + bn.append("lhand") #9 - left_wrist + bn.append("rhand") #10 - right_wrist + bn.append("lhip") #11 - left_hip + bn.append("rhip") #12 - right_hip + bn.append("lknee") #13 - left_knee + bn.append("rknee") #14 - right_knee + bn.append("lfoot") #15 - left_ankle + bn.append("rfoot") #16 - right_ankle + return bn +#--------------------------------------------------- + +def getBody25NameList(): + bn=list() + #--------------------------------------------------- + bn.append("head") #0 + bn.append("neck") #1 + bn.append("rshoulder") #2 + bn.append("relbow") #3 + bn.append("rhand") #4 + bn.append("lshoulder") #5 + bn.append("lelbow") #6 + bn.append("lhand") #7 + bn.append("hip") #8 + bn.append("rhip") #9 + bn.append("rknee") #10 + bn.append("rfoot") #11 + bn.append("lhip") #12 + bn.append("lknee") #13 + bn.append("lfoot") #14 + bn.append("endsite_eye.r") #15 eye.r endsite_eye.r + bn.append("endsite_eye.l") #16 eye.l endsite_eye.l + bn.append("lear") #17 __temporalis02.l ear.l + bn.append("rear") #18 __temporalis02.r ear.r + bn.append("endsite_toe1-2.l") #19 + bn.append("endsite_toe5-3.l") #20 + bn.append("lheel") #21 + bn.append("endsite_toe1-2.r") #22 + bn.append("endsite_toe5-3.r") #23 + bn.append("rheel") #24 + bn.append("bkg") #25 + return bn +#--------------------------------------------------- + +doFrameDumpingForTiles=0 +dumpedFrameForTiles=0 + +def dumpPoseNETInputTile(pose2D): + global dumpedFrameForTiles + jointLabels = getBody25NameList() + f = open("Input2DTile_%u.json" % dumpedFrameForTiles, "w") + f.write("{\n") + f.write("\"data\":[\n") + + i=0 + for joint in jointLabels: + if (i!=0): + f.write(",") + #------------------------ + if (('2dx_'+joint in pose2D) and ('2dy_'+joint in pose2D) and ('visible_'+joint in pose2D)): + f.write(str(pose2D['2dx_'+joint])) + f.write(",") + f.write(str(pose2D['2dy_'+joint])) + f.write(",") + f.write(str(pose2D['visible_'+joint])) + else: + f.write("0,0,0") + #------------------------ + i=i+1 + f.write("\n] }\n") + + + f.close() + dumpedFrameForTiles=dumpedFrameForTiles+1 + +def drawPoseNETLandmarks(predictions,image,threshold=0.25): + import cv2 + sourceWidth = image.shape[1] + sourceHeight = image.shape[0] + width = image.shape[1] + height = image.shape[0] + jointLabels = getPoseNETBodyNameList() # getBody25NameList() + jID = 0 + for joint in predictions: + #print("Joint ",joint) + y2D = int(joint[0]*sourceHeight) + x2D = int(joint[1]*sourceWidth) + vis2D = float(joint[2]) + color=(0,255,255) + if (threshold>vis2D): + color=(0,0,255) + + cv2.circle(image,(x2D,y2D),2,color) + + font = cv2.FONT_HERSHEY_SIMPLEX + org = (x2D,y2D) + fontScale = 0.4 + thickness = 1 + message = '%s|%0.4f' % (jointLabels[jID],vis2D) + image = cv2.putText(image, message , org, font, fontScale, color, thickness, cv2.LINE_AA) + jID += 1 + return image + + +def processPoseNETLandmarks(correctLabels,poseNETPose,currentAspectRatio,trainedAspectRatio,threshold=0.25,doFlipX=False): + itemNumber = 0 + mnetPose2D = dict() + aspectRatioFix = trainedAspectRatio / currentAspectRatio + if poseNETPose is not None: + for item in poseNETPose: + thisLandmarkName = correctLabels[itemNumber].lower() + if (thisLandmarkName!=''): + confidence = float(item[2]) + #------------------------------- + x2D = 0.0 + y2D = 0.0 + visible = 0.0 + #------------------------------- + if (confidence>threshold): + #print("Confidence ",confidence," > ",threshold) + #First of all for some reason mediapipe images get flipped so we undo this + if (doFlipX): + x2D = float(1.0-item[1]) #Do Flip X + else: + x2D = float(item[1]) #Dont Flip X + #second the camera used is different that the trained one so we need to fix the aspect ratio + #x2D = trainedAspectRatio * ( x2D / currentAspectRatio) + x2D = aspectRatioFix * x2D + y2D = float(item[0]) + if (x2D==0) and (y2D==0): + visible=0.0 + else: + visible=1.0 + #------------------------------- + labelX = "2dx_"+thisLandmarkName + mnetPose2D[labelX]=x2D # <- we store the corrected 2D point + labelY = "2dy_"+thisLandmarkName + mnetPose2D[labelY]=y2D + labelV = "visible_"+thisLandmarkName + mnetPose2D[labelV]=visible + if (x2D>1.0): + print("Normalization Error(!) | Joint ",thisLandmarkName,"(",itemNumber,") x=",x2D," y=",y2D," v=",visible) + itemNumber = itemNumber +1 + + if ("2dx_rshoulder" in mnetPose2D) and ("2dy_rshoulder" in mnetPose2D) and ("visible_rshoulder" in mnetPose2D) and ("2dx_lshoulder" in mnetPose2D) and ("2dy_lshoulder" in mnetPose2D) and ("visible_lshoulder" in mnetPose2D) : + #--------------------------------------------- + rX = float(mnetPose2D["2dx_rshoulder"]) + rY = float(mnetPose2D["2dy_rshoulder"]) + rV = float(mnetPose2D["visible_rshoulder"]) + #--------------------------------------------- + lX = float(mnetPose2D["2dx_lshoulder"]) + lY = float(mnetPose2D["2dy_lshoulder"]) + lV = float(mnetPose2D["visible_lshoulder"]) + #--------------------------------------------- + if (rV>0.0) and (lV>0.0): + mnetPose2D["2dx_neck"] = (rX+lX)/2 + mnetPose2D["2dy_neck"] = (rY+lY)/2 + mnetPose2D["visible_neck"] = 1.0 + + if (('visible_rhip' in mnetPose2D) and ('visible_lhip' in mnetPose2D)): + if (float(mnetPose2D["visible_rhip"])>0.0 and float(mnetPose2D["visible_lhip"])>0.0): + mnetPose2D["2dx_hip"]=(float(mnetPose2D["2dx_rhip"])+float(mnetPose2D["2dx_lhip"]))/2.0 + mnetPose2D["2dy_hip"]=(float(mnetPose2D["2dy_rhip"])+float(mnetPose2D["2dy_lhip"]))/2.0 + mnetPose2D["visible_hip"]=1.0 + + if (('visible_rshoulder' in mnetPose2D) and ('visible_lshoulder' in mnetPose2D)): + if (float(mnetPose2D["visible_rshoulder"])>0.0 and float(mnetPose2D["visible_lshoulder"])>0.0): + mnetPose2D["2dx_neck"]=(float(mnetPose2D["2dx_rshoulder"])+float(mnetPose2D["2dx_lshoulder"]))/2.0 + mnetPose2D["2dy_neck"]=(float(mnetPose2D["2dy_rshoulder"])+float(mnetPose2D["2dy_lshoulder"]))/2.0 + mnetPose2D["visible_neck"]=1.0 + + #----------------------------------------------------------------- + if ('visible_rfoot' in mnetPose2D) : + mnetPose2D["2dx_endsite_toe1-2.r"]=mnetPose2D["2dx_rfoot"] + mnetPose2D["2dy_endsite_toe1-2.r"]=mnetPose2D["2dy_rfoot"] + mnetPose2D["visible_endsite_toe1-2.r"]=mnetPose2D["visible_rfoot"] + mnetPose2D["2dx_endsite_toe5-3.r"]=mnetPose2D["2dx_rfoot"] + mnetPose2D["2dy_endsite_toe5-3.r"]=mnetPose2D["2dy_rfoot"] + mnetPose2D["visible_endsite_toe5-3.r"]=mnetPose2D["visible_rfoot"] + #----------------------------------------------------------------- + if ('visible_lfoot' in mnetPose2D) : + mnetPose2D["2dx_endsite_toe1-2.l"]=mnetPose2D["2dx_lfoot"] + mnetPose2D["2dy_endsite_toe1-2.l"]=mnetPose2D["2dy_lfoot"] + mnetPose2D["visible_endsite_toe1-2.l"]=mnetPose2D["visible_lfoot"] + mnetPose2D["2dx_endsite_toe5-3.l"]=mnetPose2D["2dx_lfoot"] + mnetPose2D["2dy_endsite_toe5-3.l"]=mnetPose2D["2dy_lfoot"] + mnetPose2D["visible_endsite_toe5-3.l"]=mnetPose2D["visible_lfoot"] + + #Deactivate to stop dumping tiles + global doFrameDumpingForTiles + if (doFrameDumpingForTiles==1): + dumpPoseNETInputTile(mnetPose2D) + + return mnetPose2D +#--------------------------------------------------- + + + +class PoseNETTFLite(): + def __init__( + self, + modelPath:str="movenet/lite-model_movenet_singlepose_lightning_tflite_int8_4.tflite", + ): + #Tensorflow attempt to be reasonable + #------------------------------------------ + self.jointNames = getPoseNETBodyNameList() + #------------------------------------------ + import tensorflow as tf + # Initialize the TFLite interpreter + self.interpreter = tf.lite.Interpreter(model_path=modelPath) + self.interpreter.allocate_tensors() + #------------------------------------------ + self.output = dict() + self.hz = 0.0 + #------------------------------------------ + + def get2DOutput(self): + return self.output + + def convertImageToMocapNETInput(self,image,doFlipX=False,threshold=0.25): + import tensorflow as tf + import numpy as np + import time + import cv2 + sourceWidth = image.shape[1] + sourceHeight = image.shape[0] + targetWidth = 192 + targetHeight = 192 + currentAspectRatio=targetWidth/targetHeight + trainedAspectRatio=trainingWidth/trainingHeight + + #Do resize on OpenCV end + #---------------------------------------------------------------- + imageTransformed = img_resizeWithCrop(image,192,192) + #imageTransformed = img_resizeWithPadding(image,192,192) + imageTransformed = cv2.cvtColor(imageTransformed,cv2.COLOR_BGR2RGB) + #---------------------------------------------------------------- + + #Prepare image for Tensorflow + #---------------------------------------------------------------- + imageTF = np.expand_dims(imageTransformed, axis=0).astype('int32') + #---------------------------------------------------------------- + + # TF Lite format expects tensor type of float32. + input_image = tf.cast(imageTF, dtype=tf.uint8) # tf.float32 + input_details = self.interpreter.get_input_details() + output_details = self.interpreter.get_output_details() + #------------------------------------------------------------------- + + + start = time.time() + #------------------------------------------------------------------- + #------------------------------------------------------------------- + self.interpreter.set_tensor(input_details[0]['index'], input_image.numpy()) + self.interpreter.invoke() + + keypoints_with_scores = self.interpreter.get_tensor(output_details[0]['index']) # Output is a [1, 1, 17, 3] numpy array. + predictions = keypoints_with_scores[0][0] + #------------------------------------------------------------------- + #------------------------------------------------------------------- + seconds = time.time() - start + self.hz = 1 / (seconds+0.0001) + print("MoveNET TFLite Framerate : ",round(self.hz,2)," fps ") + + + currentAspectRatio=sourceWidth/sourceHeight #We "change" aspect ratio by restoring points + for pointID in range(0,len(predictions)): + #Joints have y,x,acc order + nX,nY = normalizedCoordinatesAdaptToResizedCrop(sourceWidth,sourceHeight,targetWidth,targetHeight,predictions[pointID][1],predictions[pointID][0]) + nX,nY = normalizedCoordinatesAdaptForVerticalImage(sourceWidth,sourceHeight,trainingWidth,trainingHeight,nX,nY) + predictions[pointID][1]=nX + predictions[pointID][0]=nY + + self.output = processPoseNETLandmarks(self.jointNames,predictions,currentAspectRatio,trainedAspectRatio,threshold=threshold,doFlipX=doFlipX) + #------------------------------------------------ + self.image = drawPoseNETLandmarks(predictions,image,threshold=threshold) + #------------------------------------------------ + + return self.output,image +#---------------------------------------------------------------------------------------------------------------------------- +#---------------------------------------------------------------------------------------------------------------------------- +#---------------------------------------------------------------------------------------------------------------------------- +#---------------------------------------------------------------------------------------------------------------------------- +#---------------------------------------------------------------------------------------------------------------------------- + + + + + +class PoseNETONNX(): + def __init__( + self, + modelPath:str="movenet/model.onnx", + ): + #Tensorflow attempt to be reasonable + #------------------------------------------ + self.jointNames = getPoseNETBodyNameList() + #------------------------------------------ + import onnxruntime as ort + import onnx + onnxModelForCheck = onnx.load(modelPath) + onnx.checker.check_model(onnxModelForCheck) + print("ONNX devices available : ", ort.get_device()) + providers = ['CPUExecutionProvider'] + #providers = ['CUDAExecutionProvider'] + self.options = ort.SessionOptions() + self.model = ort.InferenceSession(modelPath, providers=providers, sess_options=self.options) + for i in range(0,len(self.model.get_inputs())): + print("ONNX INPUTS ",self.model.get_inputs()[i].name) + self.inputName = self.model.get_inputs()[i].name + + self.model_input_name = self.model.get_inputs() + #------------------------------------------ + self.output = dict() + self.hz = 0.0 + #------------------------------------------ + + def get2DOutput(self): + return self.output + + def convertImageToMocapNETInput(self,image,doFlipX=False,threshold=0.25): + import tensorflow as tf + import numpy as np + import time + import cv2 + sourceWidth = image.shape[1] + sourceHeight = image.shape[0] + targetWidth = 192 + targetHeight = 192 + currentAspectRatio=targetWidth/targetHeight + trainedAspectRatio=trainingWidth/trainingHeight + #Do resize on OpenCV end + #---------------------------------------------------------------- + imageTransformed = img_resizeWithCrop(image,targetWidth,targetHeight) + #imageTransformed = img_resizeWithPadding(image,targetWidth,targetHeight) + imageTransformed = cv2.cvtColor(imageTransformed,cv2.COLOR_BGR2RGB) + #---------------------------------------------------------------- + + #Hand image to Tensorflow + #---------------------------------------------------------------- + imageONNX = np.expand_dims(imageTransformed, axis=0) + #------------------------------------------------------------------- + + + start = time.time() + #------------------------------------------------------------------- + #------------------------------------------------------------------- + thisInputONNX = { self.inputName : imageONNX.astype('int32')} + #Run input through MocapNET + output_names_onnx = [otp.name for otp in self.model.get_outputs()] + keypoints_with_scores = self.model.run(output_names_onnx,thisInputONNX)[0][0] + predictions = keypoints_with_scores[0] + #------------------------------------------------------------------- + #------------------------------------------------------------------- + seconds = time.time() - start + self.hz = 1 / (seconds+0.0001) + print("MoveNET ONNX Framerate : ",round(self.hz,2)," fps ") + + + currentAspectRatio=sourceWidth/sourceHeight #We "change" aspect ratio by restoring points + for pointID in range(0,len(predictions)): + #Joints have y,x,acc order + nX,nY = normalizedCoordinatesAdaptToResizedCrop(sourceWidth,sourceHeight,targetWidth,targetHeight,predictions[pointID][1],predictions[pointID][0]) + nX,nY = normalizedCoordinatesAdaptForVerticalImage(sourceWidth,sourceHeight,trainingWidth,trainingHeight,nX,nY) + predictions[pointID][1]=nX + predictions[pointID][0]=nY + + self.output = processPoseNETLandmarks(self.jointNames,predictions,currentAspectRatio,trainedAspectRatio,threshold=threshold,doFlipX=doFlipX) + #---------------------------------------------------------------- + self.image = drawPoseNETLandmarks(predictions,image,threshold=threshold) + #---------------------------------------------------------------- + + return self.output,image +#---------------------------------------------------------------------------------------------------------------------------- +#---------------------------------------------------------------------------------------------------------------------------- +#---------------------------------------------------------------------------------------------------------------------------- +#---------------------------------------------------------------------------------------------------------------------------- +#---------------------------------------------------------------------------------------------------------------------------- + + + + + + +class PoseNET(): + def __init__( + self, + modelPath:str="movenet/", + ): + #Tensorflow attempt to be reasonable + #------------------------------------------ + self.jointNames = getPoseNETBodyNameList() + import tensorflow as tf + self.model = tf.saved_model.load(modelPath) + self.movenet = self.model.signatures['serving_default'] + #------------------------------------------ + self.output = dict() + self.hz = 0.0 + #------------------------------------------ + def get2DOutput(self): + return self.output + + def convertImageToMocapNETInput(self,image,doFlipX=False,threshold=0.25): + import tensorflow as tf + import numpy as np + import time + import cv2 + sourceWidth = image.shape[1] + sourceHeight = image.shape[0] + targetWidth = 192 + targetHeight = 192 + currentAspectRatio=targetWidth/targetHeight + trainedAspectRatio=trainingWidth/trainingHeight + #Do resize on OpenCV end + #---------------------------------------------------------------- + imageTransformed = img_resizeWithCrop(image,192,192) + #imageTransformed = img_resizeWithPadding(image,192,192) + imageTransformed = cv2.cvtColor(imageTransformed,cv2.COLOR_BGR2RGB) + #---------------------------------------------------------------- + + #Prepare image for Tensorflow + #---------------------------------------------------------------- + imageTF = np.expand_dims(imageTransformed, axis=0).astype('int32') + #---------------------------------------------------------------- + + + + start = time.time() + # TF Lite format expects tensor type of float32. + #------------------------------------------------------------------- + #------------------------------------------------------------------- + outputs = self.movenet(tf.cast(imageTF, dtype=tf.int32)) + keypoints_with_scores = outputs['output_0'] + predictionsRaw = keypoints_with_scores[0][0] + #------------------------------------------------------------------- + #------------------------------------------------------------------- + seconds = time.time() - start + self.hz = 1 / (seconds+0.0001) + print("MoveNET Framerate : ",round(self.hz,2)," fps ") + + + currentAspectRatio=sourceWidth/sourceHeight #We "change" aspect ratio by restoring points + predictions=list() + for pointID in range(0,len(predictionsRaw)): + #Joints have y,x,acc order + thisPoint = list() + thisPoint.append(float(predictionsRaw[pointID][0])) #y + thisPoint.append(float(predictionsRaw[pointID][1])) #x + thisPoint.append(float(predictionsRaw[pointID][2])) #score + nX,nY = normalizedCoordinatesAdaptToResizedCrop(sourceWidth,sourceHeight,targetWidth,targetHeight,thisPoint[1],thisPoint[0]) + nX,nY = normalizedCoordinatesAdaptForVerticalImage(sourceWidth,sourceHeight,trainingWidth,trainingHeight,nX,nY) + thisPoint[0]= nY #Just update coords + thisPoint[1]= nX #Just update coords + predictions.append(thisPoint) + #------------------------------------------------------------------- + + self.output = processPoseNETLandmarks(self.jointNames,predictions,currentAspectRatio,trainedAspectRatio,threshold=threshold,doFlipX=doFlipX) + + #Actually see what was handed to the tensorflow stuff..! + #----------------------------------------------------------------- + #import matplotlib.pyplot as plt + #plt.title("TensorFlow Logo with shape {}".format(imageTF.shape)) + #plt.imshow(tf.squeeze(imageTF)) + #plt.show(block = False) + #plt.pause(0.001) + #----------------------------------------------------------------- + + self.image = drawPoseNETLandmarks(predictions,image,threshold=threshold) + #------------------------------------------------ + + return self.output,image +#---------------------------------------------------------------------------------------------------------------------------- +#---------------------------------------------------------------------------------------------------------------------------- +#---------------------------------------------------------------------------------------------------------------------------- +#---------------------------------------------------------------------------------------------------------------------------- +#---------------------------------------------------------------------------------------------------------------------------- + + + + + +def runPoseNETSerial(): + #Parse command line arguments + #----------------------------------------- + import sys + import cv2 + import time + videoFilePath = "webcam" + doProfiling = False + doFlipX = False + engine = "onnx" + doNNEveryNFrames = 3 # 3 + bvhScale = 1.0 + doHCDPostProcessing = 1 + threshold = 0.25 + plotBVHChannels = False + bvhAnglesForPlotting = list() + bvhAllAnglesForPlotting = list() + + + if (len(sys.argv)>1): + #print('Argument List:', str(sys.argv)) + for i in range(0, len(sys.argv)): + if (sys.argv[i]=="--flipx"): + doFlipX = True + if (sys.argv[i]=="--nonn"): + doNNEveryNFrames = 1000 + if (sys.argv[i]=="--noik"): + doHCDPostProcessing = 0 + doNNEveryNFrames = 1 + if (sys.argv[i]=="--scale"): + bvhScale=float(sys.argv[i+1]) + if (sys.argv[i]=="--from"): + videoFilePath=sys.argv[i+1] + if (sys.argv[i]=="--engine"): + engine=sys.argv[i+1] + if (sys.argv[i]=="--plot"): + plotBVHChannels=True + + # For webcam input: + #----------------------------------------- + frameNumber = 0 + if (videoFilePath=="esp"): + from espStream import ESP32CamStreamer + cap = ESP32CamStreamer() + elif (videoFilePath=="webcam"): + cap = cv2.VideoCapture(0) + cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) + cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) + else: + cap = cv2.VideoCapture(videoFilePath) + #----------------------------------------- + #python3 -m tf2onnx.convert --saved-model movenet --opset 14 --output movenet/model.onnx + #zip movenet.zip movenet/* movenet/*/* + #----------------------------------------- + + bvhAnglesForPlotting = list() + + # Initialize the PoseNET + if (engine=="tensorflow"): + poseNET = PoseNET(modelPath="movenet/") + elif (engine=="onnx"): + poseNET = PoseNETONNX(modelPath="movenet/model.onnx") + elif (engine=="tflite"): + poseNET = PoseNETTFLite() + else: + print("Unknown engine (",engine,") for MoveNET") + sys.exit(1) + + + #Select a MocapNET class from tensorflow/tensorrt/onnx/tf-lite engines + from MocapNET import easyMocapNETConstructor + mnet = easyMocapNETConstructor( + engine, + doProfiling=doProfiling, + doBody = False, #<- override whole body + doUpperbody = True, + doLowerbody = True, + doHCDPostProcessing=doHCDPostProcessing, + bvhScale=bvhScale + ) + mnet.test() + + + #------------------------------------------------ + #------------------------------------------------ + #------------------------------------------------ + while cap.isOpened(): + success, annotated_image = cap.read() + if not success: + print("Ignoring empty camera frame.") + break + # If loading a video, use 'break' instead of 'continue'. + #continue + #print(image.type) + + start = time.time() + #Our 2D Joint Estimation + #------------------------------------------------------------------------------------ + mocapNETInput,annotated_image = poseNET.convertImageToMocapNETInput(annotated_image,doFlipX=doFlipX,threshold=threshold) + #------------------------------------------------------------------------------------ + + #Our 3D Joint Estimation + #------------------------------------------------------------------------------------ + doNN = (frameNumber%doNNEveryNFrames)==0 + mocapNET3DOutput = mnet.predict3DJoints(mocapNETInput,runNN=doNN,runHCD=True) + bvhAnglesForPlotting.append(mnet.outputBVH) + bvhAllAnglesForPlotting.append(mnet.outputBVH) + if (len(bvhAnglesForPlotting)>100): + bvhAnglesForPlotting.pop(0) + #------------------------------------------------------------------------------------ + from MocapNETVisualization import visualizeMocapNETEnsemble + visualizeMocapNETEnsemble(mnet,annotated_image,plotBVHChannels=plotBVHChannels,bvhAnglesForPlotting=bvhAnglesForPlotting,economic=True) + #------------------------------------------------------------------------------------ + + + seconds = time.time() - start + fps = 1 / (seconds+0.0001) + print("\r PoseNET+MocepNET aggregate Framerate : ",round(fps,2)," fps \r", end="", flush=True) + print("\n", end="", flush=True) + + font = cv2.FONT_HERSHEY_SIMPLEX + org = (50, 50) + fontScale = 1 + color = (0,0,0) + thickness = 2 + + message = 'MNET4+ ST/%s/NN:%u/%0.2f fps (2DNN %0.2f/3DNN %0.2f/3DHCD %0.2f)' % (engine,doNN,fps,poseNET.hz,mnet.hz_NN,mnet.hz_HCD) + annotated_image = cv2.putText(annotated_image, message , org, font, fontScale, color, thickness, cv2.LINE_AA) + org = (52, 52) + color = (255,255,255) + annotated_image = cv2.putText(annotated_image, message , org, font, fontScale, color, thickness, cv2.LINE_AA) + + #cv2.imwrite('mediapipe_%05u.jpg'%frameNumber, annotated_image) + frameNumber = frameNumber + 1 + + cv2.imshow('PoseNET + MocapNET', annotated_image) + if cv2.waitKey(1) & 0xFF == 27: + break + cap.release() + + + + +def runPoseNETParallel(): + #Parse command line arguments + #----------------------------------------- + import sys + import cv2 + import time + import threading + + videoFilePath = "webcam" + doProfiling = False + doFlipX = False + engine = "onnx" + doNNEveryNFrames = 3 + plotBVHChannels = 1 + bvhScale = 1.0 + threshold = 0.25 + + + if (len(sys.argv)>1): + #print('Argument List:', str(sys.argv)) + for i in range(0, len(sys.argv)): + if (sys.argv[i]=="--flipx"): + doFlipX = True + if (sys.argv[i]=="--scale"): + bvhScale=float(sys.argv[i+1]) + if (sys.argv[i]=="--noplot"): + plotBVHChannels=0 + if (sys.argv[i]=="--from"): + videoFilePath=sys.argv[i+1] + if (sys.argv[i]=="--engine"): + engine=sys.argv[i+1] + if (sys.argv[i]=="--dump"): + global doFrameDumpingForTiles + doFrameDumpingForTiles=1 + + + # For webcam input: + #----------------------------------------- + frameNumber = 0 + if (videoFilePath=="esp"): + from espStream import ESP32CamStreamer + cap = ESP32CamStreamer() + elif (videoFilePath=="webcam"): + cap = cv2.VideoCapture(0) + cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) + cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) + else: + cap = cv2.VideoCapture(videoFilePath) + #----------------------------------------- + #python3 -m tf2onnx.convert --saved-model movenet --opset 14 --output movenet/model.onnx + #zip movenet.zip movenet/* movenet/*/* + #----------------------------------------- + + from MocapNETVisualization import drawMocapNETOutput,drawMocapNETAllPlots,drawMissingInput + #It is important for MocapNET to be the first to be initialized! + #So the tensorflow configuration will be set by it ( if tensorflow engine is selected ) + #Select a MocapNET class from tensorflow/tensorrt/onnx/tf-lite engines + from MocapNET import easyMocapNETConstructor + mnet = easyMocapNETConstructor(engine,doProfiling=doProfiling,bvhScale=bvhScale) + mnet.test() + + bvhAnglesForPlotting = list() + + # Initialize the PoseNET + if (engine=="tensorflow"): + poseNET = PoseNET(modelPath="movenet/") + elif (engine=="onnx"): + poseNET = PoseNETONNX(modelPath="movenet/model.onnx") + elif (engine=="tflite"): + poseNET = PoseNETTFLite() + else: + print("Unknown engine (",engine,") for MoveNET") + sys.exit(1) + + if cap.isOpened(): + success, previous_image = cap.read() + if not success: + print("Could not grab first frame!.") + sys.exit(0) + mocapNETInput,previous_image = poseNET.convertImageToMocapNETInput(previous_image,doFlipX=doFlipX,threshold=threshold) + + #------------------------------------------------ + #------------------------------------------------ + #------------------------------------------------ + while cap.isOpened(): + success, next_image = cap.read() + if not success: + print("Ignoring empty camera frame.") + break + # If loading a video, use 'break' instead of 'continue'. + #continue + #print(image.type) + + start = time.time() + #Our 2D Joint Estimation AND 3D Joint Estimation happening in parallel + #------------------------------------------------------------------------------------ + doNN = (frameNumber%doNNEveryNFrames)==0 + t1 = threading.Thread(name='predict3DJoints', target=mnet.predict3DJoints, args=(mocapNETInput,),kwargs={'runNN': doNN , 'runHCD' : True}) + t2 = threading.Thread(name='convertImageToMocapNETInput', target=poseNET.convertImageToMocapNETInput, args=(next_image,)) + #------------------------------------------------------------------------------------ + t1.start() + t2.start() + # All threads running in parallel, now we wait + # ... + t1.join() + t2.join() + #------------------------------------------------------------------------------------ + mocapNET3DOutput = mnet.output3D + mocapNETBVHOutput = mnet.outputBVH + bvhAnglesForPlotting.append(mocapNETBVHOutput) + if (len(bvhAnglesForPlotting)>100): + bvhAnglesForPlotting.pop(0) + + #------------------------------------------------------------------------------------ + from MocapNETVisualization import visualizeMocapNETEnsemble + visualizeMocapNETEnsemble(mnet,previous_image,plotBVHChannels=plotBVHChannels,bvhAnglesForPlotting=bvhAnglesForPlotting,economic=True) + #------------------------------------------------------------------------------------ + + mocapNETInput = poseNET.output + next_image = poseNET.image + #------------------------------------------------------------------------------------ + seconds = time.time() - start + fps = 1 / (seconds+0.0001) + print("\r MoveNET+MocepNET MT aggregate Framerate : ",round(fps,2)," fps \r", end="", flush=True) + print("\n", end="", flush=True) + + + frameNumber = frameNumber + 1 + + + #drawMocapNETOutput(mnet,previous_image) + font = cv2.FONT_HERSHEY_SIMPLEX + org = (50, 50) + fontScale = 1 + color = (0,0,0) + thickness = 2 + + message = 'MNET4+ MT/%s/NN:%u/%0.2f fps (2DNN %0.2f/3DNN %0.2f/3DHCD %0.2f)' % (engine,doNN,fps,poseNET.hz,mnet.hz_NN,mnet.hz_HCD) + previous_image = cv2.putText(previous_image, message , org, font, fontScale, color, thickness, cv2.LINE_AA) + org = (52, 52) + color = (255,255,255) + previous_image = cv2.putText(previous_image, message , org, font, fontScale, color, thickness, cv2.LINE_AA) + #------------------------------------------------------------------------------------------------------------ + + cv2.imshow('MoveNET + MocapNET', previous_image) + previous_image = next_image + if cv2.waitKey(1) & 0xFF == 27: + break + cap.release() + + + + + + + + + + + +if __name__ == '__main__': + doSerialRun = True + import sys + if (len(sys.argv)>1): + for i in range(0, len(sys.argv)): + if (sys.argv[i]=="--mt"): + doSerialRun = False + runPoseNETParallel() + + if (doSerialRun): + runPoseNETSerial() + # + + + diff --git a/src/python/mnet4/PoseNETServer.py b/src/python/mnet4/PoseNETServer.py new file mode 100755 index 0000000..eba5864 --- /dev/null +++ b/src/python/mnet4/PoseNETServer.py @@ -0,0 +1,232 @@ +import argparse +import asyncio +import json +import logging +import os +import ssl +import uuid + +import cv2 +from aiohttp import web +from av import VideoFrame + +from aiortc import MediaStreamTrack, RTCPeerConnection, RTCSessionDescription +from aiortc.contrib.media import MediaBlackhole, MediaPlayer, MediaRecorder, MediaRelay + +ROOT = os.path.dirname(__file__) + +logger = logging.getLogger("pc") +pcs = set() +relay = MediaRelay() + +requests = 0 + +# Initialize the PoseNET +from PoseNET import PoseNET,PoseNETONNX +#poseNET = PoseNET(modelPath="movenet/") +poseNET = PoseNETONNX(modelPath="movenet/model.onnx") + +from MocapNETVisualization import drawMocapNETOutput,drawMocapNETAllPlots,drawMissingInput + +#Select a MocapNET class from tensorflow/tensorrt/onnx/tf-lite engines +doProfiling = False +engine = "onnx" +from MocapNET import easyMocapNETConstructor +mnet = easyMocapNETConstructor(engine,doProfiling=doProfiling) +mnet.test() + + +class VideoTransformTrack(MediaStreamTrack): + """ + A video stream track that transforms frames from an another track. + """ + + kind = "video" + + def __init__(self, track, transform): + super().__init__() # don't forget this! + self.track = track + self.transform = transform + + async def recv(self): + frame = await self.track.recv() + + if self.transform == "cartoon": + img = frame.to_ndarray(format="bgr24") + + # prepare color + img_color = cv2.pyrDown(cv2.pyrDown(img)) + for _ in range(6): + img_color = cv2.bilateralFilter(img_color, 9, 9, 7) + img_color = cv2.pyrUp(cv2.pyrUp(img_color)) + + # prepare edges + img_edges = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) + img_edges = cv2.adaptiveThreshold( + cv2.medianBlur(img_edges, 7), + 255, + cv2.ADAPTIVE_THRESH_MEAN_C, + cv2.THRESH_BINARY, + 9, + 2, + ) + img_edges = cv2.cvtColor(img_edges, cv2.COLOR_GRAY2RGB) + + # combine color and edges + img = cv2.bitwise_and(img_color, img_edges) + + # rebuild a VideoFrame, preserving timing information + new_frame = VideoFrame.from_ndarray(img, format="bgr24") + new_frame.pts = frame.pts + new_frame.time_base = frame.time_base + return new_frame + elif self.transform == "edges": + # perform edge detection + img = frame.to_ndarray(format="bgr24") + img = cv2.cvtColor(cv2.Canny(img, 100, 200), cv2.COLOR_GRAY2BGR) + + # rebuild a VideoFrame, preserving timing information + new_frame = VideoFrame.from_ndarray(img, format="bgr24") + new_frame.pts = frame.pts + new_frame.time_base = frame.time_base + return new_frame + elif self.transform == "mocapnet": + # rotate image + img = frame.to_ndarray(format="bgr24") + + mocapNETInput,annotated_image = poseNET.convertImageToMocapNETInput(img) + mocapNET3DOutput = mnet.predict3DJoints(mocapNETInput,runNN=1,runHCD=True) + drawMocapNETOutput(mnet,annotated_image) + + + # rebuild a VideoFrame, preserving timing information + new_frame = VideoFrame.from_ndarray(annotated_image, format="bgr24") + new_frame.pts = frame.pts + new_frame.time_base = frame.time_base + return new_frame + else: + return frame + + +async def index(request): + content = open(os.path.join(ROOT, "PoseNETServer.html"), "r").read() + return web.Response(content_type="text/html", text=content) + + +async def javascript(request): + content = open(os.path.join(ROOT, "client.js"), "r").read() + return web.Response(content_type="application/javascript", text=content) + + +async def offer(request): + global requests + requests = requests + 1 + params = await request.json() + offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"]) + + pc = RTCPeerConnection() + pc_id = "PeerConnection(%s)" % uuid.uuid4() + pcs.add(pc) + + def log_info(msg, *args): + logger.info(pc_id + " " + msg, *args) + + log_info("Created for %s", request.remote) + + # prepare local media + player = MediaPlayer(os.path.join(ROOT, "demo-instruct.wav")) + if args.record_to: + recorder = MediaRecorder("%s_%u.mp4"%(args.record_to,requests)) + else: + recorder = MediaBlackhole() + + @pc.on("datachannel") + def on_datachannel(channel): + @channel.on("message") + def on_message(message): + if isinstance(message, str) and message.startswith("ping"): + channel.send("pong" + message[4:]) + + @pc.on("connectionstatechange") + async def on_connectionstatechange(): + log_info("Connection state is %s", pc.connectionState) + if pc.connectionState == "failed": + await pc.close() + pcs.discard(pc) + + @pc.on("track") + def on_track(track): + log_info("Track %s received", track.kind) + + if track.kind == "audio": + pc.addTrack(player.audio) + recorder.addTrack(track) + elif track.kind == "video": + pc.addTrack( + VideoTransformTrack( + relay.subscribe(track), transform=params["video_transform"] + ) + ) + if args.record_to: + recorder.addTrack(relay.subscribe(track)) + + @track.on("ended") + async def on_ended(): + log_info("Track %s ended", track.kind) + await recorder.stop() + + # handle offer + await pc.setRemoteDescription(offer) + await recorder.start() + + # send answer + answer = await pc.createAnswer() + await pc.setLocalDescription(answer) + + return web.Response( + content_type="application/json", + text=json.dumps( + {"sdp": pc.localDescription.sdp, "type": pc.localDescription.type} + ), + ) + + +async def on_shutdown(app): + # close peer connections + coros = [pc.close() for pc in pcs] + await asyncio.gather(*coros) + pcs.clear() + + + +if __name__ == "__main__": + #openssl req --new --newkey rsa:4096 -x509 -sha256 --nodes --keyout apache.key --out apache-certificate.crt + parser = argparse.ArgumentParser(description="WebRTC audio / video / data-channels demo") + parser.add_argument("--cert-file", help="SSL certificate file (for HTTPS)") + parser.add_argument("--key-file", help="SSL key file (for HTTPS)") + parser.add_argument("--host", default="0.0.0.0", help="Host for HTTP server (default: 0.0.0.0)") + parser.add_argument("--port", type=int, default=8080, help="Port for HTTP server (default: 8080)") + parser.add_argument("--record-to", help="Write received media to a file."), + parser.add_argument("--verbose", "-v", action="count") + args = parser.parse_args() + + if args.verbose: + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(level=logging.INFO) + + + ssl_context = ssl.SSLContext() + ssl_context.load_cert_chain("apache-certificate.crt","apache.key") + #if args.cert_file: + # ssl_context = ssl.SSLContext() + # ssl_context.load_cert_chain(args.cert_file, args.key_file) + #else: + # ssl_context = None + + app = web.Application() + app.on_shutdown.append(on_shutdown) + app.router.add_get("/", index) + app.router.add_get("/client.js", javascript) + app.router.add_post("/offer", offer) + web.run_app(app, access_log=None, host=args.host, port=args.port, ssl_context=ssl_context) diff --git a/src/python/mnet4/align2DPoints.py b/src/python/mnet4/align2DPoints.py new file mode 100755 index 0000000..755c67b --- /dev/null +++ b/src/python/mnet4/align2DPoints.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +import h5py +import numpy as np +import csv +import os +import sys + +from scipy.spatial import procrustes +from scipy.linalg import orthogonal_procrustes + +import matplotlib +import matplotlib.pyplot as plt +import matplotlib.animation as animation + +#Taken from https://github.com/una-dinosauria/3d-pose-baseline/blob/master/src/procrustes.py +def compute_similarity_transform(X, Y, compute_optimal_scale=False): + """ + A port of MATLAB's `procrustes` function to Numpy. + Adapted from http://stackoverflow.com/a/18927641/1884420 + Args + X: array NxM of targets, with N number of points and M point dimensionality + Y: array NxM of inputs + compute_optimal_scale: whether we compute optimal scale or force it to be 1 + Returns: + d: squared error after transformation + Z: transformed Y + T: computed rotation + b: scaling + c: translation + """ + + muX = X.mean(0) + muY = Y.mean(0) + + X0 = X - muX + Y0 = Y - muY + + ssX = (X0**2.).sum() + ssY = (Y0**2.).sum() + + # centred Frobenius norm + normX = np.sqrt(ssX) + normY = np.sqrt(ssY) + + # scale to equal (unit) norm + X0 = X0 / normX + Y0 = Y0 / normY + + # optimum rotation matrix of Y + A = np.dot(X0.T, Y0) + U,s,Vt = np.linalg.svd(A,full_matrices=False) + V = Vt.T + T = np.dot(V, U.T) + + # Make sure we have a rotation + detT = np.linalg.det(T) + V[:,-1] *= np.sign( detT ) + s[-1] *= np.sign( detT ) + T = np.dot(V, U.T) + + traceTA = s.sum() + + if compute_optimal_scale: # Compute optimum scaling of Y. + b = traceTA * normX / normY + d = 1 - traceTA**2 + Z = normX*traceTA*np.dot(Y0, T) + muX + else: # If no scaling allowed + b = 1 + d = 1 + ssY/ssX - 2 * traceTA * normY / normX + Z = normY*np.dot(Y0, T) + muX + + c = muX - b*np.dot(muY, T) + + return d, Z, T, b, c + + +def pointListReturnXYZListForScatterPlot(A): + numberOfPoints=A.shape[0] + xs=list() + ys=list() + zs=list() + for i in range(0,numberOfPoints): + xs.append(A[i][0]) + ys.append(A[i][1]) + zs.append(A[i][2]) + return xs,ys,zs + + + +def pointListsReturnAvgDistance(A,B): + numberOfPoints=A.shape[0] + if (A.shape[0]!=B.shape[0]): + print("Error comparing point lists of different length") + return inf + + distance=0 + for i in range(0,numberOfPoints): + #--------- + xA=A[i][0] + yA=A[i][1] + zA=A[i][2] + #--------- + xB=B[i][0] + yB=B[i][1] + zB=B[i][2] + #--------- + xAB=xA-xB + yAB=yA-yB + zAB=zA-zB + + #Pythagorean theorem for 3 dimensions + #distance = squareRoot( xAB^2 + yAB^2 + zAB^2 ) + distance+=np.sqrt(xAB*xAB+yAB*yAB+zAB*zAB) + return distance/numberOfPoints + + + + + diff --git a/src/python/mnet4/align3DPoints.py b/src/python/mnet4/align3DPoints.py new file mode 100755 index 0000000..5212750 --- /dev/null +++ b/src/python/mnet4/align3DPoints.py @@ -0,0 +1,280 @@ +#!/usr/bin/env python3 +#Written by Ammar Qammaz a.k.a AmmarkoV - 2020 + +import h5py +import numpy as np +import csv +import os +import sys + + +class bcolors: + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKGREEN = '\033[92m' + WARNING = '\033[93m' + FAIL = '\033[91m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + + +#Taken from https://github.com/una-dinosauria/3d-pose-baseline/blob/master/src/procrustes.py +def compute_similarity_transform(X, Y, compute_optimal_scale=False): + """ + A port of MATLAB's `procrustes` function to Numpy. + Adapted from http://stackoverflow.com/a/18927641/1884420 + Args + X: array NxM of targets, with N number of points and M point dimensionality + Y: array NxM of inputs + compute_optimal_scale: whether we compute optimal scale or force it to be 1 + Returns: + d: squared error after transformation + Z: transformed Y + T: computed rotation + b: scaling + c: translation + """ + + #We create a normalized version of X and Y called X0,Y0 + #that is centered on 0 + muX = X.mean(0) + muY = Y.mean(0) + + X0 = X - muX + Y0 = Y - muY + + ssX = (X0**2.).sum() + ssY = (Y0**2.).sum() + + # centred Frobenius norm + normX = np.sqrt(ssX) + normY = np.sqrt(ssY) + + # scale to equal (unit) norm + X0 = X0 / normX + Y0 = Y0 / normY + + #For reference this is the SciPy version : + #https://github.com/scipy/scipy/blob/v1.9.3/scipy/spatial/_procrustes.py#L15-L130 + #https://github.com/scipy/scipy/blob/v1.9.3/scipy/linalg/_procrustes.py#L12-L89 + + # optimum rotation matrix of Y + A = np.dot(X0.T, Y0) + + #full_matrices => bool, optional + #If True (default), u and vh have the shapes (..., M, M) and (..., N, N), respectively. + #Otherwise, the shapes are (..., M, K) and (..., K, N), respectively, where K = min(M, N). + U,s,Vt = np.linalg.svd(A,full_matrices=False) + V = Vt.T + T = np.dot(V, U.T) + + # Make sure we have a rotation + detT = np.linalg.det(T) + V[:,-1] *= np.sign( detT ) + s[-1] *= np.sign( detT ) + T = np.dot(V, U.T) + #------------------------- + traceTA = s.sum() + #------------------------- + if compute_optimal_scale: # Compute optimum scaling of Y. + b = traceTA * normX / normY + d = 1 - traceTA**2 + Z = normX*traceTA*np.dot(Y0, T) + muX + else: # If no scaling allowed + b = 1 + d = 1 + ssY/ssX - 2 * traceTA * normY / normX + Z = normY*np.dot(Y0, T) + muX + #------------------------- + c = muX - b*np.dot(muY, T) + #------------------------- + return d, Z, T, b, c + + + + +""" +Calculate Area Under Curve (AUC) +""" +def AUC(values,minValue,maxValue): + underCurve=0 + samples=len(values) + for value in values: + if ((minValue<=value) and (value<=maxValue) ): + underCurve=underCurve+1 + return 100*(underCurve/samples) + + +""" +Pythagorean theorem, get the 3D distance between two 3D points given their X,Y,Z coordinates +""" +def get3DDistance(jX,jY,jZ,pX,pY,pZ): + return np.sqrt( ((jX-pX)*(jX-pX)) + ((jY-pY)*(jY-pY)) + ((jZ-pZ)*(jZ-pZ)) ) + + +""" +Given two lists of 3D points A,B calculate their average distance +""" +def calculateAverageDistanceOf3DPoints(A,B): + numberOfPoints=A.shape[0] + + if (numberOfPoints==0): + print(bcolors.FAIL,"calculateAverageDistanceOf3DPoints(A,B), A has no points!",bcolors.ENDC) + return np.inf + elif (A.shape[0]!=B.shape[0]): + print(bcolors.FAIL,"Error comparing point lists of different length",bcolors.ENDC) + return np.inf + + distance=0.0 + for i in range(0,numberOfPoints): + #--------- + xA=A[i][0] + yA=A[i][1] + zA=A[i][2] + #--------- + xB=B[i][0] + yB=B[i][1] + zB=B[i][2] + #---------------------------------------------- + distance += get3DDistance(xA,yA,zA,xB,yB,zB) + #---------------------------------------------- + + return distance/numberOfPoints + + +""" +Return the jointID of a jointName in a list of labels without being case sensitive +""" +def findJointID(jointName,labels): + jointNameStreamlined=jointName.lower().strip() + for i in range(0,len(labels)): + labelStreamlined=labels[i].lower().strip() + if (jointNameStreamlined==labelStreamlined): + return i + print(bcolors.FAIL,"Cannot find joint `%s` between %u labels !"%(jointNameStreamlined,len(labels)),bcolors.ENDC) + #print(labels) + return -1 + + + +def compareGroundTruthToPrediction(configuration,groundTruth,prediction,doProcrustes=1,allowProcrustesToChangeScale=1,jointsToCompare=list(),useSciKitImplementation=False): + #print("GroundTruth : ",groundTruth) + #print("Prediction : ",prediction) + + #Automatically fill joints to compare if it is empty with whatever currently + #exists in configuration + numberOfJoints = len(configuration["hierarchy"]) + if (len(jointsToCompare)==0): + for jID in range (0,numberOfJoints): + jointsToCompare.append(configuration["hierarchy"][jID]["joint"].lower()) + else: + numberOfJoints = len(jointsToCompare) + #-------------------------------------------------------------------------- + + #Initialize our variables + #------------------------------- + comparedJoints = list() + groundTruth3DPoints = list() + mnet3DPoints = list() + #------------------------------- + scale = 1.0 + outputScale = 10.0 # We go from Centimeters to Millimeters! + numberOfJointsToCompare = 0 + #------------------------------- + + for jointName in jointsToCompare: + jointName = jointName.lower() #Make double sure if supplied as argument + + labelX = '3DX_%s' % jointName + labelY = '3DY_%s' % jointName + labelZ = '3DZ_%s' % jointName + + if ( + ( labelX in groundTruth ) and + ( labelY in groundTruth ) and + ( labelZ in groundTruth ) and + ( labelX in prediction ) and + ( labelY in prediction ) and + ( labelZ in prediction ) + ): + comparedJoints.append(jointName) + numberOfJointsToCompare = numberOfJointsToCompare + 1 + #-------------------------------------------- + # Grab ground truth for point + #-------------------------------------------- + x3DGT=scale*groundTruth[labelX] + y3DGT=scale*groundTruth[labelY] + z3DGT=scale*groundTruth[labelZ] + #-------------------------------------------- + groundTruth3DPoints.append([x3DGT,y3DGT,z3DGT]) + #-------------------------------------------- + + #-------------------------------------------- + # Grab MocapNET point + #-------------------------------------------- + x3DMNET=scale*prediction[labelX] + y3DMNET=scale*prediction[labelY] + z3DMNET=scale*prediction[labelZ] + #-------------------------------------------- + mnet3DPoints.append([x3DMNET,y3DMNET,z3DMNET]) + #-------------------------------------------- + else: + print(bcolors.WARNING,"Joint ",jointName," was not found this will influence results ") + + if (numberOfJointsToCompare==0): + print(bcolors.FAIL,"No joints where found ..",bcolors.ENDC) + + #We package our lists in numpy to be able to easily manipulate them + #------------------------------------------------------------------ + np_GTPointCloud = np.asarray(groundTruth3DPoints,dtype=np.float32) + np_OURPointCloud = np.asarray(mnet3DPoints,dtype=np.float32) + #------------------------------------------------------------------ + + #This is the main comparison after using procrustes and transforming the pointcloud + #to align it or when just doing plain old euclidean distance + #-------------------------------------------------------------------------------- + if (useSciKitImplementation): + from scipy.spatial import procrustes + mtx1, mtx2, disparity = procrustes(np_GTPointCloud,np_OURPointCloud) + np_GTPointCloud=mtx1 + np_OURPointCloud=mtx2 + elif (doProcrustes): + d, Z, T, b, c = compute_similarity_transform(np_GTPointCloud,np_OURPointCloud,compute_optimal_scale=allowProcrustesToChangeScale) + #disparity=np.sqrt(d) #d: squared error after transformation + #print("compute_similarity_transform : ",disparity) + + #Our point cloud is brought to the same translation and rotation as h36 point cloud + np_OURPointCloud = (b*np_OURPointCloud.dot(T))+c + disparity = outputScale * calculateAverageDistanceOf3DPoints(np_OURPointCloud,np_GTPointCloud) + else: + disparity = outputScale * calculateAverageDistanceOf3DPoints(np_OURPointCloud,np_GTPointCloud) + #-------------------------------------------------------------------------------- + #Here for each element in ground truth we want to get the same point from prediction.. + + #We want to calculate Mean Per Joint Position Error (MPJPE) + #to do so we have to calculate the position error of each of the joints in our point cloud + #sum it up and then divide it through the number of samples + totalError = 0.0 + totalSamples = 0 + #alljointDistances = list() + jointDistance = dict() + for jID in range(0,numberOfJointsToCompare): + #------------------------------------------------------------------ + #We use the np_ourPointCloud and np_h36PointCloud so that if procrustes analysis is enabled it will be used.. + perJointDisparity= outputScale * get3DDistance( + np_OURPointCloud[jID][0],np_OURPointCloud[jID][1],np_OURPointCloud[jID][2], + np_GTPointCloud[jID][0] ,np_GTPointCloud[jID][1] ,np_GTPointCloud[jID][2] + ) + totalError+=perJointDisparity + totalSamples+=1 + #We also keep every sample on a list to do an analysis in the end + #alljointDistances.append(perJointDisparity) + jointDistance[comparedJoints[jID]]=perJointDisparity + #------------------------------------------------------------------ + + jointDistance["meanAverageError"] = disparity + #print("Frame %u / Disparity %f " % (frameID,disparity)) + return jointDistance + + + diff --git a/src/python/mnet4/csvNET.py b/src/python/mnet4/csvNET.py new file mode 100644 index 0000000..994eb1b --- /dev/null +++ b/src/python/mnet4/csvNET.py @@ -0,0 +1,420 @@ +#!/usr/bin/python3 + +""" +Author : "Ammar Qammaz" +Copyright : "2022 Foundation of Research and Technology, Computer Science Department Greece, See license.txt" +License : "FORTH" +""" + +#pip install mediapipe pandas pillow matplotlib opencv-python + +import cv2 +import numpy as np +import time +import sys +import os + +from readCSV import parseConfiguration,zeroOutXYJointsThatAreInvisible,performNSRMAlignment,splitNumpyArray +from NSDM import NSDMLabels,createNSDMUsingRules +from MocapNET import MocapNET + +#------------------------------------------------ +#------------------------------------------------ +#------------------------------------------------ +class csvNET(): + def __init__(self, + datasetDirectory = "dataset/generated", + dataFile = "face", + filenamePostfix = "_all", + fromCSV = "", + mem = 1.0, + outputMode = "bvh", + height = 1080, + width = 1920 + ): + #------------------------------------------ + from readCSV import readCSVFile + if (fromCSV!=""): + self.data2D = readCSVFile("%s" % (fromCSV),mem,0) + self.dataBVH = dict() + else: + self.data2D = readCSVFile("%s/2d_%s%s.csv" % (datasetDirectory,dataFile,filenamePostfix),mem,0) + self.dataBVH = readCSVFile("%s/%s_%s%s.csv" % (datasetDirectory,outputMode,dataFile,filenamePostfix),mem,0) + #------------------------------------------ + self.height = height + self.width = width + self.output = dict() + #------------------------------------------ + self.error = dict() + self.error["body"] = list() + #------------------------------------------ + self.rSquared = dict() + self.rSquared["body"] = list() + #------------------------------------------ + self.responsesNN = dict() + self.responsesNN["body"] = list() + #------------------------------------------ + + def getNumberOfSamples(self): + numberOfSamples = self.data2D["body"].shape[0] + #numberOfOutputs = data["out"].shape[1] + return numberOfSamples + + def get2DOutput(self): + return self.output + + def registerNNResponse(self,outputToCompare,mocapNETResponse): + self.responsesNN["label"]=outputToCompare + thisRow = list() + for label in outputToCompare: + label = label.lower() # <- make it lowercase + thisRow.append(mocapNETResponse[label]) + self.responsesNN["body"].append(thisRow) + + def registerError(self,outputToCompare,mocapNETDifference): + self.error["label"]=outputToCompare + thisRow = list() + for label in outputToCompare: + label = label.lower() # <- make it lowercase + thisRow.append(mocapNETDifference[label]) + self.error["body"].append(thisRow) + + + def saveCSVDataAsCSVFile(self,labelprefix,data,filename): + if (data) and ("label" in data) and ("body" in data): + print("Writting encountered error at ",filename) + f = open(filename, 'w') + + #Write header + recordsWritten=0 + for item in data["label"]: + if (recordsWritten>0): + f.write(',') + f.write("%s_%s" % (labelprefix,item)) #We prepend loss to make sure this is not used the wrong way.. + recordsWritten=recordsWritten+1 + f.write('\n') + #-------------------------------------------------------------------------------------------------- + #Write body + for sample in range(0,len(data["body"])): + recordsWritten=0 + for value in range(0,len(data["body"][sample])): + if (recordsWritten>0): + f.write(',') + f.write("%f" % (data["body"][sample][value])) + recordsWritten=recordsWritten+1 + f.write('\n') + f.close() + print("Done writting ",filename) + #-------------------------------------------------------------------------------------------------- + + def saveErrorAsCSV(self,filename): + self.saveCSVDataAsCSVFile("loss",self.error,filename) + + def saveResponseAsCSV(self,filename): + self.saveCSVDataAsCSVFile("NNOutput",self.responsesNN,filename) + + def saveRSquared(self,filename): + if (self.dataBVH): + self.rSquared["label"] = self.dataBVH["label"] + + thisResult = list() + column = 0 + for columnName in self.dataBVH["label"]: + #We cut a single dimension as a 1 dim numpy array to make things easier.. + singleOutputDimensionGroundTruth = splitNumpyArray(self.dataBVH["body"],column,1,0) + meanGroundTruth = np.mean(singleOutputDimensionGroundTruth) + #print(columnName," -> mean ",meanGroundTruth) + + totalSumOfSquaresProportionalToTheVarianceOfData = 0.0 + for sample in range(0,singleOutputDimensionGroundTruth.shape[0]): + totalSumOfSquaresProportionalToTheVarianceOfData = totalSumOfSquaresProportionalToTheVarianceOfData + ((singleOutputDimensionGroundTruth[sample][0] - meanGroundTruth) ** 2) + #print("Total Sum of Squares = ",totalSumOfSquaresProportionalToTheVarianceOfData," samples = ",singleOutputDimensionGroundTruth.shape[0]) + + sumOfSquaresOfResiduals = 0.0 + for sample in range(0,len(self.error["body"])): + sumOfSquaresOfResiduals = sumOfSquaresOfResiduals + (self.error["body"][sample][column] ** 2) + #print("Sum of Squares of residuals = ",sumOfSquresOfResiduals," samples = ",len(self.error["body"])) + + if (totalSumOfSquaresProportionalToTheVarianceOfData!=0.0): + rsquared = float( 1.0 - sumOfSquaresOfResiduals/totalSumOfSquaresProportionalToTheVarianceOfData ) + print("R² for %s = %0.2f "%(columnName,rsquared)) + thisResult.append(rsquared) + else: + print("R² for %s = NaN "%(columnName)) + thisResult.append(float('nan')) + + column = column + 1 + results = list() + results.append(thisResult) + self.rSquared["body"] = results + self.saveCSVDataAsCSVFile("RSquared",self.rSquared,filename) + + + def compareToGroundTruth(self,fID,outputToCompare): + if (self.dataBVH): + count = 0 + mocapNETOutput = dict() + mocapNETDifference = dict() + #-------------------------------------------------------------------------------------------------- + for label in self.dataBVH["label"]: + label = label.lower() # <- make it lowercase + if (label in outputToCompare): + mocapNETOutput[label] = outputToCompare[label] + mocapNETDifference[label] = abs(self.dataBVH["body"][fID][count] - outputToCompare[label]) + #print("For ",label," we regressed ",outputToCompare[label]," gt was ",self.dataBVH["body"][fID][count]," | loss -> ",mocapNETDifference[label] ) + else: + print("Output ",label," missing") + mocapNETDifference[label] = abs(self.dataBVH["body"][fID][count]) + count = count + 1 + #-------- Automatically register error ---------------- + self.registerError(outputToCompare,mocapNETDifference) + self.registerNNResponse(outputToCompare,mocapNETOutput) + #------------------------------------------------------ + return mocapNETDifference + #-------------------------------------------------------------------------------------------------- + else: + return float("nan") + + + def getMocapNETInputForFrameID(self,fID): + count = 0 + mocapNETInput = dict() + for label in self.data2D["label"]: + label = label.lower() # <- make it lowercase + mocapNETInput[label] = self.data2D["body"][fID][count] + count = count + 1 + #-------------------------------------------------------------------------------------------------- + annotated_image = np.zeros((self.height,self.width,3), np.uint8) + #from MocapNETVisualization import drawMocapNETInput + #drawMocapNETInput(mocapNETInput,annotated_image) + #-------------------------------------------------------------------------------------------------- + #print("Frame : ",fID," -> ",mocapNETInput) + from holisticPartNames import guessLandmarks + mocapNETInput = guessLandmarks(mocapNETInput) + #-------------------------------------------------------------------------------------------- + self.output = mocapNETInput + #-------------------------------------------------------------------------------------------- + return mocapNETInput,annotated_image +#------------------------------------------------ +#------------------------------------------------ +#------------------------------------------------ + +def calculate_relative_magnitudes(edm, n): + row_magnitudes = [] + column_magnitudes = [] + for i in range(n): + row_magnitude = sum(edm[i*n:(i+1)*n]) + row_magnitudes.append(row_magnitude) + column_magnitude = sum(edm[i::n]) + column_magnitudes.append(column_magnitude) + central_element = edm[n*n // 2] + row_magnitudes_sum = sum(row_magnitudes) - central_element + column_magnitudes_sum = sum(column_magnitudes) - central_element + max_magnitude = max(row_magnitudes_sum, column_magnitudes_sum) + relative_row_magnitudes = [m / max_magnitude for m in row_magnitudes] + relative_column_magnitudes = [m / max_magnitude for m in column_magnitudes] + return relative_row_magnitudes, relative_column_magnitudes + + +def getNSRMInterest(mnet,part=""): + from tools import appendCSVToFile + if part in mnet.ensemble: + NSRM = mnet.ensemble[part].NSRM + appendCSVToFile("study.csv",NSRM,fID=mnet.framesProcessed-1) + #row,column = calculate_relative_magnitudes(NSRM,int(np.sqrt(len(NSRM)))) + #print("ROW ",row," COLUMN ",column) + #appendCSVToFile("study.csv",row,fID=mnet.framesProcessed-1) + + + +#------------------------------------------------ +#------------------------------------------------ +#------------------------------------------------ +def streamPosesFromCameraToMocapNET(): + engine = "onnx" + dataFile = "face" + doProfiling = False + doVisualization = True + csvFilePath = "" + saveVideo = False + doBody = True + doUpperbody = False, #<- These get auto activated if doBody=True + doLowerbody = False, #<- These get auto activated if doBody=True + doFace = False + doREye = False + doMouth = False + doHands = False + addNoise = 0.0 + aspectCorrection = 1.0 + scale = 1.0 + mem = 1.0 + windowDelay = 1 + doHCDPostProcessing = 1 + hcdLearningRate = 0.1 + hcdEpochs = 20 + hcdIterations = 15 + plotBVHChannels = False + bvhAnglesForPlotting = list() + bvhAllAnglesForPlotting = list() + study = "" + calibrationFile = "" + #python3 mediapipeHolisticWebcamMocapNET.py --from damien.avi --face --nobody --plot --save + #python3 -m csvNET --from ammarFaceFar.csv --study face --face --nobody + # python3 -m csvNET --from ammarFaceFar.csv --mouth --reye --nobody --plot --save + + if (len(sys.argv)>1): + #print('Argument List:', str(sys.argv)) + for i in range(0, len(sys.argv)): + if (sys.argv[i]=="--novisualization"): + doVisualization = False + if (sys.argv[i]=="--ik"): + hcdLearningRate = float(sys.argv[i+1]) + hcdEpochs = int(sys.argv[i+2]) + hcdIterations = int(sys.argv[i+3]) + if (sys.argv[i]=="--noik"): + doHCDPostProcessing = 0 + if (sys.argv[i]=="--aspectCorrection"): + aspectCorrection=float(sys.argv[i+1]) + if (sys.argv[i]=="--noise"): + addNoise=float(sys.argv[i+1]) + if (sys.argv[i]=="--scale"): + scale=float(sys.argv[i+1]) + if (sys.argv[i]=="--calib"): + calibrationFile = sys.argv[i+1] + if (sys.argv[i]=="--study"): + study = sys.argv[i+1] + if (sys.argv[i]=="--plot"): + plotBVHChannels=True + if (sys.argv[i]=="--nobody"): + doBody = False + doUpperbody = False + doLowerbody = False + if (sys.argv[i]=="--upperbody"): + doBody = False + doUpperbody = True + if (sys.argv[i]=="--lowerbody"): + doBody = False + doLowerbody = True + if (sys.argv[i]=="--face"): + doFace=True + dataFile="face" #Use 2d_face_all.csv as input if not --from is activated + if (sys.argv[i]=="--reye"): + doREye=True + dataFile="reye" #Use 2d_reye_all.csv as input if not --from is activated + if (sys.argv[i]=="--mouth"): + doMouth=True + dataFile="mouth" #Use 2d_mouth_all.csv as input if not --from is activated + if (sys.argv[i]=="--hands"): + doHands=True + if (sys.argv[i]=="--save"): + saveVideo=True + if (sys.argv[i]=="--engine"): + engine=sys.argv[i+1] + print("Selecting engine : ",engine) + if (sys.argv[i]=="--from"): + csvFilePath=sys.argv[i+1] + if (sys.argv[i]=="--profile"): + doProfiling=True + if (sys.argv[i]=="--mem"): + mem=int(sys.argv[i+1]) + if (sys.argv[i]=="--delay"): + windowDelay=int(sys.argv[i+1]) + + from MocapNETVisualization import drawMocapNETOutput,drawDescriptor,drawNSRM,drawMAE2DError,drawMocapNETAllPlots + + #Select a MocapNET class from tensorflow/tensorrt/onnx/tf-lite engines + from MocapNET import easyMocapNETConstructor + mnet = easyMocapNETConstructor( + engine, + doProfiling=doProfiling, + doHCDPostProcessing=doHCDPostProcessing, + hcdLearningRate=hcdLearningRate, + hcdEpochs = hcdEpochs, + hcdIterations = hcdIterations, + bvhScale=scale, + doBody=doBody, + doUpperbody=doUpperbody, + doLowerbody=doLowerbody, + doFace=doFace, + doREye=doREye, + doMouth=doMouth, + doHands=doHands, + addNoise=addNoise + ) + + if (calibrationFile!=""): + print("Enforcing Calibration file : ",calibrationFile) + mnet.bvh.configureRendererFromFile(calibrationFile) + + mnet.test() + mnet.recordBVH(True) + #Body only + mp = csvNET(mem=mem,dataFile=dataFile,fromCSV=csvFilePath) + + from MocapNETVisualization import visualizeMocapNETEnsemble + #------------------------------------------------ + #------------------------------------------------ + #------------------------------------------------ + for frameNumber in range(0, mp.getNumberOfSamples() ): + #-------------------------------------------------------------------------------------------------------------- + mocapNETInput,annotated_image = mp.getMocapNETInputForFrameID(frameNumber) + #-------------------------------------------------------------------------------------------------------------- + mocapNET3DOutput = mnet.predict3DJoints(mocapNETInput) + mocapNETBVHOutput = mnet.outputBVH + + getNSRMInterest(mnet,part=study) + + difference = mp.compareToGroundTruth(frameNumber,mocapNETBVHOutput) + #print("Frame ",frameNumber," difference ~=> ",difference) + print("Frame ",frameNumber,"/",mp.getNumberOfSamples()) + + bvhAnglesForPlotting.append(mocapNETBVHOutput) + bvhAllAnglesForPlotting.append(mocapNETBVHOutput) + if (len(bvhAnglesForPlotting)>100): + bvhAnglesForPlotting.pop(0) + + if (doVisualization): + #-------------------------------------------------------------------------------------------------------------- + annotated_image,plotImage = visualizeMocapNETEnsemble(mnet,annotated_image,plotBVHChannels=plotBVHChannels,bvhAnglesForPlotting=bvhAnglesForPlotting) + #-------------------------------------------------------------------------------------------------------------- + if (saveVideo): + cv2.imwrite('colorFrame_0_%05u.jpg'%(frameNumber), annotated_image) + if (plotBVHChannels): + cv2.imwrite('plotFrame_0_%05u.jpg'%(frameNumber), plotImage) + #-------------------------------------------------------------------------------------------------------------- + cv2.imshow('MocapNET + MediaPipe Holistic', annotated_image) + if cv2.waitKey(windowDelay) & 0xFF == 27: + break + #-------------------------------------------------------------------------------------------------------------- + if (csvFilePath == ""): + #This uses MATPLOTLIB and is very slow + #only emmit this when working with ground truth and not an ad hoc CSV file + from MocapNETVisualization import drawMocapNETFrequencyPlots + drawMocapNETFrequencyPlots(bvhAllAnglesForPlotting) + #-------------------------------------------------------------------------------------------------------------- + + #Finished + #========================== + mp.saveErrorAsCSV("loss.csv") + mp.saveResponseAsCSV("freq.csv") + mp.saveRSquared("rSquared.csv") + + if (doVisualization) and (saveVideo): + os.system("ffmpeg -framerate 30 -i colorFrame_0_%05d.jpg -s 1200x720 -y -r 30 -pix_fmt yuv420p -threads 8 livelastRun3DHiRes.mp4 && rm colorFrame_0_*.jpg") + if (plotBVHChannels): + os.system("ffmpeg -framerate 30 -i plotFrame_0_%05d.jpg -s 1200x720 -y -r 30 -pix_fmt yuv420p -threads 8 livelastPlot3DHiRes.mp4 && rm plotFrame_0_*.jpg") + + + del mnet #So that the out.bvh file gets created.. + if (csvFilePath != ""): + os.system("rm 2d_out.csv 3d_out.csv bvh_out.csv map_out.csv") + os.system("./GroundTruthDumper --from out.bvh --setPositionRotation -2.6 0 2000 0 0 0 --csv ./ out.csv 2d+bvh ") # Remove noise offsetPositionRotation + +#------------------------------------------------ +#------------------------------------------------ +#------------------------------------------------ + +#TEST +#python3 -m csvNET --nobody --face +if __name__ == '__main__': + streamPosesFromCameraToMocapNET() diff --git a/src/python/mnet4/dataDecomposition.py b/src/python/mnet4/dataDecomposition.py new file mode 100755 index 0000000..610fe73 --- /dev/null +++ b/src/python/mnet4/dataDecomposition.py @@ -0,0 +1,783 @@ +#!/usr/bin/python3 +""" +Author : "Ammar Qammaz" +Copyright : "2022 Foundation of Research and Technology, Computer Science Department Greece, See license.txt" +License : "FORTH" +""" + +import sys +import os +import numpy as np + +#python3 principleComponentAnalysisTester.py --config dataset/body_configuration.json --mem 1000 --highlight 4 --mode 3 +#python3 principleComponentAnalysisTool.py --config dataset/body_configuration.json --all body --mem 1000 --show --type sparsepca +#python3 principleComponentAnalysisTool.py --config dataset/upperbody_configuration.json --all upperbody --mem 1000 --show --type factoranalysis + +class EmptyDecomposition: + def __init__(self,): + self.decompositionType = "" + def ok(self): + return True + def fit(self,data): + pass + def transform(self,data,selectedPCADimensions=0): + return data + def save(self,filename): + return True + def load(self,filename): + return True + + +#------------------------------------------------------------------ +#------------------------------------------------------------------ +#------------------------------------------------------------------ +def getSKPCA_Decomposer(numberOfDimensions=20): + from sklearn.decomposition import PCA + engine = PCA(n_components=numberOfDimensions) + return engine + +def getTruncatedSVD_Decomposer(numberOfDimensions=20): + from sklearn.decomposition import TruncatedSVD + engine = TruncatedSVD(n_components=numberOfDimensions) + return engine + +def getLatentDirichletAllocation_Decomposer(numberOfDimensions=20): + from sklearn.decomposition import LatentDirichletAllocation + engine = LatentDirichletAllocation(n_components=numberOfDimensions) + return engine + +def getFactorAnalysis_Decomposer(numberOfDimensions=20): + from sklearn.decomposition import FactorAnalysis + engine = FactorAnalysis(n_components=numberOfDimensions) + return engine + +def getDictionaryLearning_Decomposer(numberOfDimensions=20): + from sklearn.decomposition import DictionaryLearning + engine = DictionaryLearning(n_components=numberOfDimensions) + return engine + +def getFastICA_Decomposer(numberOfDimensions=20): + from sklearn.decomposition import FastICA + engine = FastICA(n_components=numberOfDimensions) + return engine + +def getNMF_Decomposer(numberOfDimensions=20): + from sklearn.decomposition import NMF + engine = NMF(n_components=numberOfDimensions) + return engine + +def getIncrementalPCA_Decomposer(numberOfDimensions=20): + from sklearn.decomposition import IncrementalPCA + engine = IncrementalPCA(n_components=numberOfDimensions) + return engine + +def getSparsePCA_Decomposer(numberOfDimensions=20): + from sklearn.decomposition import SparsePCA + engine = SparsePCA(n_components=numberOfDimensions) + return engine + +def getTSNE_Decomposer(numberOfDimensions=20): + from sklearn.manifold import TSNE + engine = TSNE(n_components=numberOfDimensions,perplexity=100, learning_rate=100, verbose=2, n_iter=350) + return engine +#------------------------------------------------------------------ +#------------------------------------------------------------------ +#------------------------------------------------------------------ +class Decomposition(): + def __init__(self, + inputData:np.array=np.array([]), + savedFile:str="", + decompositionType:str="pca", + selectedPCADimensions:int=0 + ): + self.disabledDecomposition = False + self.numberOfSamplesFittedOn = 0 + self.decompositionType = decompositionType + self.noTransformFunction = 0 #Some methods dont have a transform function + self.trackedFiles = list() #<- all needed files should be tracked here + + if (selectedPCADimensions==0): + self.numberOfSamplesFittedOn = inputData.shape[0] + else: + self.numberOfSamplesFittedOn = selectedPCADimensions + + + import sklearn + print("Decomposition code using Sk-Learn : ",sklearn.__version__) + print("Will try to perform : ",self.decompositionType) + + #self.engine = getSKPCA_Decomposer(numberOfDimensions=self.numberOfSamplesFittedOn) + from sklearn.preprocessing import StandardScaler + self.scaler = StandardScaler() + + if (self.decompositionType==""): + print("Not Performing any decomposition") + self.disabledDecomposition = True + return; + elif (self.decompositionType=="skpca"): + self.engine = getSKPCA_Decomposer(numberOfDimensions=self.numberOfSamplesFittedOn) + elif (self.decompositionType=="sparsepca"): + self.engine = getSparsePCA_Decomposer(numberOfDimensions=self.numberOfSamplesFittedOn) + elif (self.decompositionType=="incrementalpca"): + self.engine = getIncrementalPCA_Decomposer(numberOfDimensions=self.numberOfSamplesFittedOn) + elif (self.decompositionType=="fastica"): + self.engine = getFastICA_Decomposer(numberOfDimensions=self.numberOfSamplesFittedOn) + elif (self.decompositionType=="nmf"): + self.engine = getNMF_Decomposer(numberOfDimensions=self.numberOfSamplesFittedOn) + elif (self.decompositionType=="dictionary"): + self.engine = getDictionaryLearning_Decomposer(numberOfDimensions=self.numberOfSamplesFittedOn) + elif (self.decompositionType=="factoranalysis"): + self.engine = getFactorAnalysis_Decomposer(numberOfDimensions=self.numberOfSamplesFittedOn) + elif (self.decompositionType=="dirichlet"): + self.engine = getLatentDirichletAllocation_Decomposer(numberOfDimensions=self.numberOfSamplesFittedOn) + elif (self.decompositionType=="svd"): + self.engine = getTruncatedSVD_Decomposer(numberOfDimensions=self.numberOfSamplesFittedOn) + elif (self.decompositionType=="tsne"): + self.engine = getTSNE_Decomposer(numberOfDimensions=self.numberOfSamplesFittedOn) + self.noTransformFunction = 1 + elif (self.decompositionType!="pca"): + print("Could not understand decomposition Type ",self.decompositionType) + sys.exit(1) + else: + print("Using SK code for decomposition Type ",self.decompositionType) + self.engine = getSKPCA_Decomposer(numberOfDimensions=self.numberOfSamplesFittedOn) + + if (savedFile!=""): + print("Loading existing fitted decomposition from ",savedFile,"..!") + self.load(savedFile) + elif (inputData.size!=0): + print("Generating new fit..!") + self.fit(inputData) + else: + print("No Decomposition input given..!") + + def ok(self): + return self.numberOfSamplesFittedOn!=0 + + def getNumberOfExpectedSamples(self): + return self.numberOfSamplesFittedOn + + def fit(self,data): + import time + startAt = time.time() + #---------------------------------------------------------------------------------------- + self.numberOfSamplesFittedOn = data.shape[0] + self.scaler.fit(data) + dataTransformed = self.scaler.transform(data) + outputData = self.engine.fit_transform(dataTransformed) + #---------------------------------------------------------------------------------------- + endAt = time.time() + print("Time required to fit ",self.decompositionType," was ",(endAt-startAt)/60," mins") + return outputData + + def transform(self,data,selectedPCADimensions=0): + if (self.disabledDecomposition): + return data + #if (selectedPCADimensions!=self.numberOfSamplesFittedOn): + # print("INCONSISTENT TRANSFORM") + + if (selectedPCADimensions==0): + if (self.noTransformFunction): + dataTransformed = self.scaler.fit_transform(data) + return self.engine.fit_transform(dataTransformed) + else: + dataTransformed = self.scaler.transform(data) + return self.engine.transform(dataTransformed) + else: + if (self.noTransformFunction): + dataTransformed = self.scaler.fit_transform(data) + return self.engine.fit_transform(dataTransformed)[:,:selectedPCADimensions] # ,n_components=selectedPCADimensions + else: + dataTransformed = self.scaler.transform(data) + return self.engine.transform(dataTransformed)[:,:selectedPCADimensions] # ,n_components=selectedPCADimensions + + def save(self,filename): + print("Saving Decomposition to ",filename) + OKGREEN = '\033[92m' + FAIL = '\033[91m' + ENDC = '\033[0m' + import pickle + self.trackedFiles = list() + successfulSaves = 0 + #--------------------------------------------------------------- + pathToEngine = "%s.%s" % (filename,self.decompositionType) + with open(pathToEngine,'wb') as engineFile: + self.trackedFiles.append(pathToEngine) + print(OKGREEN,"Saving Decomposition Engine to ",pathToEngine,ENDC) + pickle.dump(self.engine,engineFile) + successfulSaves = successfulSaves + 1 + #--------------------------------------------------------------- + pathToScaler = "%s.scaler.%s" % (filename,self.decompositionType) + with open(pathToScaler,'wb') as scalerFile: + self.trackedFiles.append(pathToScaler) + print(OKGREEN,"Saving Decomposition Scaler to ",pathToScaler,ENDC) + pickle.dump(self.scaler,scalerFile) + successfulSaves = successfulSaves + 1 + #--------------------------------------------------------------- + return (successfulSaves==2) + + def load(self,filename): + print("Loading Decomposition from ",filename) + OKGREEN = '\033[92m' + FAIL = '\033[91m' + ENDC = '\033[0m' + import pickle + self.trackedFiles = list() + successfulLoads = 0 + #--------------------------------------------------------------- + pathToEngine = "%s.%s" % (filename,self.decompositionType) + if (os.path.isfile(pathToEngine)): + self.trackedFiles.append(pathToEngine) + unpickleEngine = open(pathToEngine, 'rb') + print(OKGREEN,"Loaded Decomposition Engine from ",pathToEngine,ENDC) + self.engine = pickle.load(unpickleEngine) #, encoding='bytes' + successfulLoads = successfulLoads + 1 + else: + print(FAIL,"Failed Loading Decomposition Engine from ",pathToEngine,ENDC) + #--------------------------------------------------------------- + pathToScaler = "%s.scaler.%s" % (filename,self.decompositionType) + if (os.path.isfile(pathToEngine)): + self.trackedFiles.append(pathToScaler) + unpickleScaler = open(pathToScaler, 'rb') + print(OKGREEN,"Loading Decomposition Scaler from ",pathToScaler,ENDC) + self.scaler = pickle.load(unpickleScaler) #, encoding='bytes' + successfulLoads = successfulLoads + 1 + else: + print(FAIL,"Failed Loading Decomposition Scaler from ",pathToScaler,ENDC) + #--------------------------------------------------------------- + return (successfulLoads==2) + + def visualize(self,data,saveToFile="",onlyScreePlotNDimensions=0,label="PCA",colors=list(),colorLabel="Highlighting PC-4",viewAzimuth=45,viewElevation=45,showScree=0): + import matplotlib + import matplotlib.pyplot as plt + font = {'family' : 'normal', + 'weight' : 'bold', + 'size' : 28 } + plt.rc('font', **font) + plt.rc('xtick', labelsize=15) + plt.rc('ytick', labelsize=15) + # === Plot ========================================================================= + fig = plt.figure() + fig.set_size_inches(19.2, 10.8, forward=True) + ax1 = fig.add_subplot(1, 1, 1,projection='3d') + #=================================================================================== + + #Number of PCA components to plot on first plot (our plot is 3D so max is 4 if we dont have a color ..! ) + keepNDimensions = 3 + if (len(colors)==0): + keepNDimensions = 4 + + #Do transform of our input using the PCA dimensions as new basis + #=================================================================================== + transformedData = self.transform(data,selectedPCADimensions=keepNDimensions).real + #print("TRANSFORMED DATA : ",transformedData) + print("TRANSFORMED DATA") + print("Number of Samples : ",transformedData.shape[0]) + print("Number of Dimensions : ",transformedData.shape[1]) + #=================================================================================== + + if (len(colors)==0): + if (transformedData.shape[1]<4): + print("Not enough dimensions to plot color") + keepNDimensions = 3 + else: + colors = transformedData[:,3] + colorLabel = "highlighting PC-4" + else: + print("Using provided set of colorValues") + keepNDimensions = 3 + + ax1.view_init(viewAzimuth,viewElevation) + #=================================================================================== + if (len(colors)==0): + ax1.scatter(transformedData[:,0],transformedData[:,1],transformedData[:,2],c='red') + else: + scatter = ax1.scatter(transformedData[:,0],transformedData[:,1],transformedData[:,2],c=colors,label=colors) + ax1.legend(*scatter.legend_elements(num=5),loc="upper right", title="Values", fontsize="x-small", framealpha=0.4) + #=================================================================================== + + # Adding title, xlabel and ylabel + ax1.set_title('%s %s %s '%(self.decompositionType,label,colorLabel)) # Title of the plot + ax1.set_xlabel('PC-1',labelpad=30) # X-Label + ax1.set_ylabel('PC-2',labelpad=30) # Y-Label + ax1.set_zlabel('PC-3',labelpad=30) # Z-Label + #ax1.tick_params(axis='x', pad=5) #fine tune numbers of plot + plt.subplots_adjust(left=0.05, right=0.95, top=0.95, bottom=0.08) + + if (saveToFile!=""): + print("Saving Figure to output file %s" % saveToFile) + fig.savefig(saveToFile) + else: + plt.show() + + +if __name__ == '__main__': + print("principleComponentAnalysis.py is a library") + decomp = Decomposition(savedFile='dataset/combinedModel/mocapnet4/mode1/1.0/step1_upperbody_all/upperbody_all.pca',decompositionType='pca') + sample = list() + sample.append(0.470052) #0 + sample.append(0.452315) #1 + sample.append(1.000000) #2 + sample.append(0.479688) #3 + sample.append(0.239352) #4 + sample.append(1.000000) #5 + sample.append(0.520312) #6 + sample.append(0.185185) #7 + sample.append(1.000000) #8 + sample.append(0.519271) #9 + sample.append(0.173148) #10 + sample.append(1.000000) #11 + sample.append(0.000000) #12 + sample.append(0.000000) #13 + sample.append(0.000000) #14 + sample.append(0.450000) #15 + sample.append(0.238889) #16 + sample.append(1.000000) #17 + sample.append(0.422917) #18 + sample.append(0.337037) #19 + sample.append(1.000000) #20 + sample.append(0.000000) #21 + sample.append(0.000000) #22 + sample.append(0.000000) #23 + sample.append(0.509375) #24 + sample.append(0.239815) #25 + sample.append(1.000000) #26 + sample.append(0.500000) #27 + sample.append(0.362963) #28 + sample.append(1.000000) #29 + sample.append(0.000000) #30 + sample.append(0.000000) #31 + sample.append(0.000000) #32 + sample.append(0.652332) #33 + sample.append(0.283472) #34 + sample.append(0.547378) #35 + sample.append(0.213181) #36 + sample.append(0.536087) #37 + sample.append(0.077135) #38 + sample.append(0.214366) #39 + sample.append(0.509478) #40 + sample.append(0.095500) #41 + sample.append(0.029691) #42 + sample.append(0.167750) #43 + sample.append(0.522894) #44 + sample.append(0.141564) #45 + sample.append(0.065052) #46 + sample.append(0.050908) #47 + sample.append(0.124542) #48 + sample.append(0.540789) #49 + sample.append(0.190115) #50 + sample.append(0.112984) #51 + sample.append(0.101816) #52 + sample.append(0.050908) #53 + sample.append(0.383942) #54 + sample.append(0.270394) #55 + sample.append(0.307847) #56 + sample.append(0.277424) #57 + sample.append(0.248705) #58 + sample.append(0.254739) #59 + sample.append(0.270394) #60 + sample.append(0.652332) #61 + sample.append(0.000000) #62 + sample.append(0.547378) #63 + sample.append(0.536087) #64 + sample.append(0.509478) #65 + sample.append(0.522894) #66 + sample.append(0.540789) #67 + sample.append(0.270394) #68 + sample.append(0.212132) #69 + sample.append(0.440391) #70 + sample.append(0.248585) #71 + sample.append(0.182055) #72 + sample.append(0.155387) #73 + sample.append(0.126341) #74 + sample.append(0.113851) #75 + sample.append(0.174184) #76 + sample.append(0.440391) #77 + sample.append(0.212132) #78 + sample.append(0.678899) #79 + sample.append(0.152507) #80 + sample.append(0.143071) #81 + sample.append(0.171002) #82 + sample.append(0.175392) #83 + sample.append(0.193561) #84 + sample.append(0.419609) #85 + sample.append(0.678899) #86 + sample.append(0.300000) #87 + sample.append(0.216108) #88 + sample.append(0.563004) #89 + sample.append(0.067397) #90 + sample.append(0.029691) #91 + sample.append(0.059382) #92 + sample.append(0.087379) #93 + sample.append(0.130105) #94 + sample.append(0.306329) #95 + sample.append(0.563004) #96 + sample.append(0.209537) #97 + sample.append(0.116016) #98 + sample.append(0.154849) #99 + sample.append(0.587831) #100 + sample.append(0.129067) #101 + sample.append(0.066885) #102 + sample.append(0.083048) #103 + sample.append(0.069538) #104 + sample.append(0.089203) #105 + sample.append(0.321928) #106 + sample.append(0.587831) #107 + sample.append(0.192741) #108 + sample.append(0.107269) #109 + sample.append(0.061752) #110 + sample.append(0.094237) #111 + sample.append(0.617853) #112 + sample.append(0.190791) #113 + sample.append(0.125269) #114 + sample.append(0.133770) #115 + sample.append(0.098298) #116 + sample.append(0.081326) #117 + sample.append(0.347944) #118 + sample.append(0.617853) #119 + sample.append(0.194984) #120 + sample.append(0.131074) #121 + sample.append(0.123504) #122 + sample.append(0.061752) #123 + sample.append(0.348961) #124 + sample.append(0.308926) #125 + sample.append(0.269400) #126 + sample.append(0.236866) #127 + sample.append(0.208076) #128 + sample.append(0.214721) #129 + sample.append(0.232589) #130 + sample.append(0.040663) #131 + sample.append(0.308926) #132 + sample.append(0.143338) #133 + sample.append(0.378955) #134 + sample.append(0.265854) #135 + sample.append(0.281502) #136 + sample.append(0.308926) #137 + sample.append(0.652332) #138 + sample.append(0.000000) #139 + sample.append(0.547378) #140 + sample.append(0.536087) #141 + sample.append(0.509478) #142 + sample.append(0.522894) #143 + sample.append(0.540789) #144 + sample.append(0.270394) #145 + sample.append(0.000000) #146 + sample.append(0.440391) #147 + sample.append(0.678899) #148 + sample.append(0.563004) #149 + sample.append(0.587831) #150 + sample.append(0.617853) #151 + sample.append(0.308926) #152 + sample.append(0.106590) #153 + sample.append(0.587454) #154 + sample.append(0.178302) #155 + sample.append(0.106590) #156 + sample.append(0.109798) #157 + sample.append(0.069458) #158 + sample.append(0.052693) #159 + sample.append(0.317531) #160 + sample.append(0.587454) #161 + sample.append(0.166465) #162 + sample.append(0.146195) #163 + sample.append(0.111492) #164 + sample.append(0.053520) #165 + sample.append(0.030413) #166 + sample.append(0.278528) #167 + sample.append(0.587454) #168 + sample.append(3.096379) #169 + sample.append(0.651328) #170 + sample.append(5.955375) #171 + sample.append(6.084674) #172 + sample.append(-0.059619) #173 + sample.append(0.048326) #174 + sample.append(0.234845) #175 + sample.append(0.585668) #176 + sample.append(0.651328) #177 + sample.append(0.686142) #178 + sample.append(5.398531) #179 + sample.append(5.946909) #180 + sample.append(5.904308) #181 + sample.append(5.806487) #182 + sample.append(0.529019) #183 + sample.append(0.651328) #184 + sample.append(6.084674) #185 + sample.append(3.792921) #186 + sample.append(3.694669) #187 + sample.append(4.237240) #188 + sample.append(4.096264) #189 + sample.append(4.071058) #190 + sample.append(3.975877) #191 + sample.append(3.886222) #192 + sample.append(3.886222) #193 + sample.append(0.000000) #194 + sample.append(3.776154) #195 + sample.append(4.110508) #196 + sample.append(4.119073) #197 + sample.append(4.020748) #198 + sample.append(3.931176) #199 + sample.append(3.931177) #200 + sample.append(0.000000) #201 + sample.append(3.929643) #202 + sample.append(2.813782) #203 + sample.append(1.095648) #204 + sample.append(3.325809) #205 + sample.append(2.449419) #206 + sample.append(2.176756) #207 + sample.append(2.363436) #208 + sample.append(2.456802) #209 + sample.append(1.402459) #210 + sample.append(1.095648) #211 + sample.append(2.003560) #212 + sample.append(3.639651) #213 + sample.append(2.840933) #214 + sample.append(2.875062) #215 + sample.append(2.887117) #216 + sample.append(1.448436) #217 + sample.append(1.095648) #218 + sample.append(2.736625) #219 + sample.append(2.943081) #220 + sample.append(0.954672) #221 + sample.append(5.591012) #222 + sample.append(3.255518) #223 + sample.append(1.401905) #224 + sample.append(2.261431) #225 + sample.append(2.461843) #226 + sample.append(1.159315) #227 + sample.append(0.954672) #228 + sample.append(1.819818) #229 + sample.append(4.163903) #230 + sample.append(4.543498) #231 + sample.append(3.371371) #232 + sample.append(3.151165) #233 + sample.append(1.170683) #234 + sample.append(0.954672) #235 + sample.append(2.943081) #236 + sample.append(3.081973) #237 + sample.append(0.929466) #238 + sample.append(5.318349) #239 + sample.append(4.543498) #240 + sample.append(3.256703) #241 + sample.append(2.719052) #242 + sample.append(2.719052) #243 + sample.append(1.130633) #244 + sample.append(0.929466) #245 + sample.append(1.897446) #246 + sample.append(4.228285) #247 + sample.append(4.543498) #248 + sample.append(3.707124) #249 + sample.append(3.371372) #250 + sample.append(1.137976) #251 + sample.append(0.929466) #252 + sample.append(3.216783) #253 + sample.append(3.189919) #254 + sample.append(0.834284) #255 + sample.append(5.505028) #256 + sample.append(5.403023) #257 + sample.append(5.860644) #258 + sample.append(3.256703) #259 + sample.append(2.719052) #260 + sample.append(0.929466) #261 + sample.append(0.834284) #262 + sample.append(1.597937) #263 + sample.append(4.522202) #264 + sample.append(5.142685) #265 + sample.append(4.364796) #266 + sample.append(3.691174) #267 + sample.append(0.898620) #268 + sample.append(0.834284) #269 + sample.append(3.574276) #270 + sample.append(3.376438) #271 + sample.append(0.744629) #272 + sample.append(5.598395) #273 + sample.append(5.603435) #274 + sample.append(5.860644) #275 + sample.append(5.860644) #276 + sample.append(3.166879) #277 + sample.append(0.744629) #278 + sample.append(0.744629) #279 + sample.append(1.183479) #280 + sample.append(4.781023) #281 + sample.append(5.403024) #282 + sample.append(4.970201) #283 + sample.append(4.234641) #284 + sample.append(0.684905) #285 + sample.append(0.744629) #286 + sample.append(4.391370) #287 + sample.append(3.727261) #288 + sample.append(0.744629) #289 + sample.append(4.544052) #290 + sample.append(4.300908) #291 + sample.append(4.272226) #292 + sample.append(4.071058) #293 + sample.append(3.886222) #294 + sample.append(3.166879) #295 + sample.append(0.744629) #296 + sample.append(3.604797) #297 + sample.append(4.254323) #298 + sample.append(4.324193) #299 + sample.append(4.133639) #300 + sample.append(3.966107) #301 + sample.append(4.234641) #302 + sample.append(0.744629) #303 + sample.append(3.966615) #304 + sample.append(3.792921) #305 + sample.append(0.000000) #306 + sample.append(4.237240) #307 + sample.append(4.096264) #308 + sample.append(4.071058) #309 + sample.append(3.975877) #310 + sample.append(3.886222) #311 + sample.append(3.886222) #312 + sample.append(3.694669) #313 + sample.append(3.776154) #314 + sample.append(4.110508) #315 + sample.append(4.119073) #316 + sample.append(4.020748) #317 + sample.append(3.931176) #318 + sample.append(3.931177) #319 + sample.append(0.000000) #320 + sample.append(3.929643) #321 + sample.append(3.827735) #322 + sample.append(0.634561) #323 + sample.append(5.145152) #324 + sample.append(4.961411) #325 + sample.append(5.039039) #326 + sample.append(4.739530) #327 + sample.append(4.325072) #328 + sample.append(0.463204) #329 + sample.append(0.634561) #330 + sample.append(0.000000) #331 + sample.append(4.613133) #332 + sample.append(4.903871) #333 + sample.append(4.607087) #334 + sample.append(4.287396) #335 + sample.append(0.295320) #336 + sample.append(0.634561) #337 + sample.append(4.346044) #338 + sample.append(2.256939) #339 + sample.append(0.968915) #340 + sample.append(0.498058) #341 + sample.append(1.022310) #342 + sample.append(1.086692) #343 + sample.append(1.380610) #344 + sample.append(1.639430) #345 + sample.append(1.112731) #346 + sample.append(0.968915) #347 + sample.append(1.471541) #348 + sample.append(0.000000) #349 + sample.append(0.927337) #350 + sample.append(1.482404) #351 + sample.append(1.967686) #352 + sample.append(1.114843) #353 + sample.append(0.968915) #354 + sample.append(1.776775) #355 + sample.append(2.805316) #356 + sample.append(0.977481) #357 + sample.append(5.982525) #358 + sample.append(1.401905) #359 + sample.append(1.401905) #360 + sample.append(2.001093) #361 + sample.append(2.261431) #362 + sample.append(1.182600) #363 + sample.append(0.977481) #364 + sample.append(1.762278) #365 + sample.append(4.068930) #366 + sample.append(3.258445) #367 + sample.append(2.912313) #368 + sample.append(2.912314) #369 + sample.append(1.196280) #370 + sample.append(0.977481) #371 + sample.append(2.673644) #372 + sample.append(2.762715) #373 + sample.append(0.879156) #374 + sample.append(6.016655) #375 + sample.append(0.229778) #376 + sample.append(0.565532) #377 + sample.append(1.223204) #378 + sample.append(1.828609) #379 + sample.append(0.992046) #380 + sample.append(0.879156) #381 + sample.append(1.465495) #382 + sample.append(4.623997) #383 + sample.append(6.053906) #384 + sample.append(3.258445) #385 + sample.append(2.912314) #386 + sample.append(0.977481) #387 + sample.append(0.879156) #388 + sample.append(2.397369) #389 + sample.append(2.664894) #390 + sample.append(0.789584) #391 + sample.append(6.028709) #392 + sample.append(0.009572) #393 + sample.append(0.229779) #394 + sample.append(0.549582) #395 + sample.append(1.093048) #396 + sample.append(0.824514) #397 + sample.append(0.789584) #398 + sample.append(1.145804) #399 + sample.append(5.109279) #400 + sample.append(6.053906) #401 + sample.append(6.053907) #402 + sample.append(3.136574) #403 + sample.append(0.789584) #404 + sample.append(0.789584) #405 + sample.append(0.819205) #406 + sample.append(3.670611) #407 + sample.append(0.789584) #408 + sample.append(4.590029) #409 + sample.append(4.312276) #410 + sample.append(4.279569) #411 + sample.append(4.040213) #412 + sample.append(3.826498) #413 + sample.append(1.093048) #414 + sample.append(0.789584) #415 + sample.append(3.436912) #416 + sample.append(4.256435) #417 + sample.append(4.337873) #418 + sample.append(4.119073) #419 + sample.append(3.931176) #420 + sample.append(3.136574) #421 + sample.append(0.789584) #422 + sample.append(3.927943) #423 + sample.append(3.792921) #424 + sample.append(0.000000) #425 + sample.append(4.237240) #426 + sample.append(4.096264) #427 + sample.append(4.071058) #428 + sample.append(3.975877) #429 + sample.append(3.886222) #430 + sample.append(3.886222) #431 + sample.append(0.000000) #432 + sample.append(3.776154) #433 + sample.append(4.110508) #434 + sample.append(4.119073) #435 + sample.append(4.020748) #436 + sample.append(3.931176) #437 + sample.append(3.931177) #438 + sample.append(3.694669) #439 + sample.append(3.929643) #440 + sample.append(2.943082) #441 + sample.append(0.788050) #442 + sample.append(5.878218) #443 + sample.append(6.084674) #444 + sample.append(0.075190) #445 + sample.append(0.432683) #446 + sample.append(1.249777) #447 + sample.append(0.825022) #448 + sample.append(0.788050) #449 + sample.append(1.204451) #450 + sample.append(4.918368) #451 + sample.append(5.815236) #452 + sample.append(5.538962) #453 + sample.append(3.960798) #454 + sample.append(0.786350) #455 + sample.append(0.788050) #456 + sample.append(3.255518) #457 + + + + out = decomp.transform(np.asarray(sample),selectedPCADimensions=210) + for i in range(0,len(out)): + print(i," = ",out[i]) + + diff --git a/src/python/mnet4/dataset/headerWithHeadAndOneMotion.bvh b/src/python/mnet4/dataset/headerWithHeadAndOneMotion.bvh new file mode 100644 index 0000000..5009ccf --- /dev/null +++ b/src/python/mnet4/dataset/headerWithHeadAndOneMotion.bvh @@ -0,0 +1,1022 @@ +HIERARCHY +ROOT hip +{ + OFFSET 0 0 0 + CHANNELS 6 Xposition Yposition Zposition Zrotation Yrotation Xrotation + JOINT abdomen + { + OFFSET 0 20.6881 -0.73152 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT chest + { + OFFSET 0 11.7043 -0.48768 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT neck + { + OFFSET 0 22.1894 -2.19456 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT neck1 + { + OFFSET 0.000000 5.364170 1.574630 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT head + { + OFFSET 0.000000 5.364141 1.574630 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT __jaw + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT jaw + { + OFFSET 0.000000 -13.499860 2.500710 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT special04 + { + OFFSET -0.000000 -6.835370 4.375500 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oris02 + { + OFFSET 0.000000 1.711150 2.820850 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oris01 + { + OFFSET -0.000000 0.972390 0.845650 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.000000 1.162291 0.607091 + } + } + } + JOINT oris06.l + { + OFFSET 0.000000 1.711150 2.820850 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oris07.l + { + OFFSET 1.168850 0.445180 0.506110 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.450611 1.195178 0.204519 + } + } + } + JOINT oris06.r + { + OFFSET 0.000000 1.711150 2.820850 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oris07.r + { + OFFSET -1.168850 0.445180 0.506110 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.450611 1.195173 0.204519 + } + } + } + } + JOINT tongue00 + { + OFFSET -0.000000 -6.835370 4.375500 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT tongue01 + { + OFFSET 0.000000 3.973650 -3.762340 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT tongue02 + { + OFFSET 0.000000 0.429760 2.924710 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT tongue03 + { + OFFSET 0.000000 0.018530 2.059010 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT __tongue04 + { + OFFSET 0.000000 -0.440240 0.838860 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT tongue04 + { + OFFSET 0.000000 0.000000 0.000000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.000000 -0.440230 0.838860 + } + } + } + JOINT tongue07.l + { + OFFSET 0.000000 -0.440240 0.838860 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 1.160923 -0.331531 0.018227 + } + } + JOINT tongue07.r + { + OFFSET 0.000000 -0.440240 0.838860 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -1.160922 -0.331531 0.018227 + } + } + } + JOINT tongue06.l + { + OFFSET 0.000000 0.018530 2.059010 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 1.644752 -0.526075 -0.203281 + } + } + JOINT tongue06.r + { + OFFSET 0.000000 0.018530 2.059010 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -1.644752 -0.526075 -0.203282 + } + } + } + JOINT tongue05.l + { + OFFSET 0.000000 0.429760 2.924710 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 1.971028 -0.388618 0.239206 + } + } + JOINT tongue05.r + { + OFFSET 0.000000 0.429760 2.924710 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -1.971028 -0.388618 0.239205 + } + } + } + } + } + } + JOINT __levator02.l + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator02.l + { + OFFSET 0.313580 -11.321120 11.599360 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator03.l + { + OFFSET 1.681690 -1.563730 -1.357570 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator04.l + { + OFFSET 0.504730 -1.676760 -0.058160 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator05.l + { + OFFSET 0.145440 -1.643170 -0.225470 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.310116 -0.760198 -0.121474 + } + } + } + } + } + } + JOINT __levator02.r + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator02.r + { + OFFSET -0.313580 -11.321120 11.599360 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator03.r + { + OFFSET -1.681690 -1.563740 -1.357570 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator04.r + { + OFFSET -0.504730 -1.676750 -0.058160 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator05.r + { + OFFSET -0.145440 -1.643170 -0.225470 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.310116 -0.760193 -0.121474 + } + } + } + } + } + } + JOINT __special01 + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT special01 + { + OFFSET -0.000000 -14.026930 -5.716970 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oris04.l + { + OFFSET -0.000000 -0.492640 17.312620 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oris03.l + { + OFFSET 1.215520 -0.627430 -0.393050 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.288776 -0.560899 -0.149645 + } + } + } + JOINT oris04.r + { + OFFSET -0.000000 -0.492640 17.312620 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oris03.r + { + OFFSET -1.215520 -0.627440 -0.393050 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.288776 -0.560894 -0.149645 + } + } + } + JOINT oris06 + { + OFFSET -0.000000 -0.492640 17.312620 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oris05 + { + OFFSET -0.000000 -0.486000 0.000950 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.000000 -0.630493 0.197635 + } + } + } + } + } + JOINT __special03 + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT special03 + { + OFFSET 0.000000 -13.499860 2.500710 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT __levator06.l + { + OFFSET -0.000000 1.035800 10.090229 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator06.l + { + OFFSET 0.522240 -0.615720 0.045900 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 1.107505 -0.131898 -2.142227 + } + } + } + JOINT __levator06.r + { + OFFSET -0.000000 1.035800 10.090229 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator06.r + { + OFFSET -0.522240 -0.615730 0.045900 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -1.107506 -0.131893 -2.142227 + } + } + } + } + } + JOINT special06.l + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT special05.l + { + OFFSET 2.108890 0.153870 5.595070 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT eye.l + { + OFFSET 0.857170 -10.254801 2.414670 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.111609 0.006490 4.051424 + } + } + JOINT orbicularis03.l + { + OFFSET 0.857170 -10.254801 2.414670 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.097789 1.146431 3.788029 + } + } + JOINT orbicularis04.l + { + OFFSET 0.857170 -10.254801 2.414670 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.113609 -1.130505 3.863064 + } + } + } + } + JOINT special06.r + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT special05.r + { + OFFSET -2.108890 0.153870 5.595070 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT eye.r + { + OFFSET -0.857170 -10.254801 2.414670 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.111609 0.006490 4.051424 + } + } + JOINT orbicularis03.r + { + OFFSET -0.857170 -10.254801 2.414670 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.097789 1.146431 3.788029 + } + } + JOINT orbicularis04.r + { + OFFSET -0.857170 -10.254801 2.414670 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.113609 -1.130505 3.863064 + } + } + } + } + JOINT __temporalis01.l + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT temporalis01.l + { + OFFSET 6.332510 -9.444281 6.595120 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oculi02.l + { + OFFSET -0.804920 0.053010 1.621140 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oculi01.l + { + OFFSET -2.161570 1.690970 2.142660 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -1.740622 0.281157 0.647323 + } + } + } + } + } + JOINT __temporalis01.r + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT temporalis01.r + { + OFFSET -6.332510 -9.444281 6.595120 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oculi02.r + { + OFFSET 0.804920 0.053010 1.621140 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oculi01.r + { + OFFSET 2.161570 1.690970 2.142660 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 1.740622 0.281157 0.647323 + } + } + } + } + } + JOINT __temporalis02.l + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT temporalis02.l + { + OFFSET 6.377600 -11.680510 6.235180 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT risorius02.l + { + OFFSET -0.814250 0.451130 1.721730 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT risorius03.l + { + OFFSET -0.649710 -2.514660 0.612550 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.479556 -1.760402 -1.642659 + } + } + } + } + } + JOINT __temporalis02.r + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT temporalis02.r + { + OFFSET -6.377600 -11.680510 6.235180 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT risorius02.r + { + OFFSET 0.814250 0.451130 1.721730 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT risorius03.r + { + OFFSET 0.649710 -2.514660 0.612550 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.479556 -1.760402 -1.642659 + } + } + } + } + } + } + } + } + JOINT rCollar + { + OFFSET -2.68224 19.2634 -4.8768 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT rShldr + { + OFFSET -8.77824 -1.95073 1.46304 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT rForeArm + { + OFFSET -28.1742 -1.7115 0.48768 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT rHand + { + + OFFSET -21.049400 0.002190 -0.634230 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT metacarpal1.r + { + OFFSET -2.815680 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger2-1.r + { + OFFSET -6.292930 0.272380 2.520090 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger2-2.r + { + OFFSET -2.310530 -0.320530 -0.060510 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger2-3.r + { + OFFSET -2.051030 -0.295400 -0.164890 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -2.376838 -0.681367 -0.183877 + } + } + } + } + } + JOINT metacarpal2.r + { + OFFSET -2.815680 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger3-1.r + { + OFFSET -6.313640 0.626130 0.318530 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger3-2.r + { + OFFSET -3.015730 -0.589480 -0.088540 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger3-3.r + { + OFFSET -2.482120 -0.426280 0.076670 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -2.344174 -0.731969 0.003260 + } + } + } + } + } + JOINT __metacarpal3.r + { + OFFSET -2.815680 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT metacarpal3.r + { + OFFSET -0.606080 -0.162120 -1.874870 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger4-1.r + { + OFFSET -5.355730 0.702040 0.402510 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger4-2.r + { + OFFSET -2.643900 -0.485530 -0.117520 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger4-3.r + { + OFFSET -2.215850 -0.353160 0.066220 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -2.350273 -0.621223 -0.046375 + } + } + } + } + } + } + JOINT __metacarpal4.r + { + OFFSET -2.815680 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT metacarpal4.r + { + OFFSET -0.606080 -0.162120 -1.874870 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger5-1.r + { + OFFSET -4.761700 0.175470 -1.109590 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger5-2.r + { + OFFSET -1.916360 -0.173360 -0.146170 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger5-3.r + { + OFFSET -1.411290 -0.108670 -0.020110 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -1.799226 -0.102363 -0.078601 + } + } + } + } + } + } + JOINT __rthumb + { + OFFSET -2.815680 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT rthumb + { + OFFSET -0.283040 -0.142720 1.950690 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger1-2.r + { + OFFSET -0.915590 -2.152150 1.546760 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger1-3.r + { + OFFSET -3.213140 -0.470060 0.247480 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -2.521224 -0.161543 -0.511272 + } + } + } + } + } + } + } + } + } + JOINT lCollar + { + OFFSET 2.68224 19.2634 -4.8768 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT lShldr + { + OFFSET 8.77824 -1.95073 1.46304 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT lForeArm + { + OFFSET 28.1742 -1.7115 0.48768 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT lHand + { + + OFFSET 21.049408 0.002200 -0.634230 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT metacarpal1.l + { + OFFSET 2.815670 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger2-1.l + { + OFFSET 6.292930 0.272390 2.520090 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger2-2.l + { + OFFSET 2.310530 -0.320520 -0.060510 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger2-3.l + { + OFFSET 2.051030 -0.295400 -0.164880 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 2.376823 -0.681367 -0.183876 + } + } + } + } + } + JOINT metacarpal2.l + { + OFFSET 2.815670 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger3-1.l + { + OFFSET 6.313640 0.626120 0.318530 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger3-2.l + { + OFFSET 3.015730 -0.589470 -0.088540 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger3-3.l + { + OFFSET 2.482120 -0.426270 0.076670 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 2.344170 -0.731978 0.003260 + } + } + } + } + } + JOINT __metacarpal3.l + { + OFFSET 2.815670 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT metacarpal3.l + { + OFFSET 0.606080 -0.162120 -1.874870 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger4-1.l + { + OFFSET 5.355730 0.702050 0.402510 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger4-2.l + { + OFFSET 2.643900 -0.485530 -0.117510 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger4-3.l + { + OFFSET 2.215840 -0.353150 0.066210 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 2.350273 -0.621228 -0.046377 + } + } + } + } + } + } + JOINT __metacarpal4.l + { + OFFSET 2.815670 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT metacarpal4.l + { + OFFSET 0.606080 -0.162120 -1.874870 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger5-1.l + { + OFFSET 4.761700 0.175480 -1.109600 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger5-2.l + { + OFFSET 1.916350 -0.173360 -0.146170 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger5-3.l + { + OFFSET 1.411290 -0.108670 -0.020110 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 1.799216 -0.102372 -0.078600 + } + } + } + } + } + } + JOINT __lthumb + { + OFFSET 2.815670 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT lthumb + { + OFFSET 0.283040 -0.142710 1.950690 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger1-2.l + { + OFFSET 0.915930 -2.151960 1.546820 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger1-3.l + { + OFFSET 3.213210 -0.469680 0.247300 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 2.521210 -0.161290 -0.511422 + } + } + } + } + } + } + } + } + } + } + } + JOINT rButtock + { + OFFSET -8.77824 4.35084 1.2192 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT rThigh + { + OFFSET 0 -1.70687 -2.19456 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT rShin + { + OFFSET 0 -36.8199 0.73152 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT rFoot + { + + OFFSET 0.73152 -45.1104 -5.12064 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe1-1.R + { + OFFSET 2.454000 -4.050002 13.194999 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe1-2.R + { + OFFSET -0.214000 -0.646000 2.427000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.401900 -0.827789 2.725930 + } + } + } + JOINT toe2-1.R + { + OFFSET 0.177000 -4.299998 13.329000 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe2-2.R + { + OFFSET -0.177000 -0.323000 2.039000 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe2-3.R + { + OFFSET -0.067000 -0.440998 1.248000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.042990 -0.647306 1.660872 + } + } + } + } + JOINT toe3-1.R + { + OFFSET -1.396000 -4.461999 13.078999 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe3-2.R + { + OFFSET -0.161000 -0.247002 1.809000 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe3-3.R + { + OFFSET -0.033000 -0.441999 1.202000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.032040 -0.433550 1.271800 + } + } + } + } + JOINT toe4-1.R + { + OFFSET -2.888001 -4.480000 12.376999 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe4-2.R + { + OFFSET -0.160000 -0.331998 1.491001 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe4-3.R + { + OFFSET 0.035999 -0.251002 1.138999 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.088911 -0.568814 0.969530 + } + } + } + } + JOINT toe5-1.R + { + OFFSET -4.257999 -4.467001 11.711999 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe5-2.R + { + OFFSET -0.046000 -0.265999 0.982000 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe5-3.R + { + OFFSET 0.086999 -0.372000 0.791000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.044329 -0.555482 1.085780 + } + } + } + } + } + } + } + } + JOINT lButtock + { + OFFSET 8.77824 4.35084 1.2192 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT lThigh + { + OFFSET 0 -1.70687 -2.19456 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT lShin + { + OFFSET 0 -36.8199 0.73152 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT lFoot + { + + OFFSET -0.73152 -45.1104 -5.12064 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe1-1.L + { + OFFSET -2.454000 -4.050002 13.194999 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe1-2.L + { + OFFSET 0.214000 -0.646000 2.427000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.401900 -0.827789 2.725930 + } + } + } + JOINT toe2-1.L + { + OFFSET -0.177000 -4.299998 13.329000 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe2-2.L + { + OFFSET 0.177000 -0.323000 2.039000 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe2-3.L + { + OFFSET 0.067000 -0.440998 1.248000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.042990 -0.647306 1.660872 + } + } + } + } + JOINT toe3-1.L + { + OFFSET 1.396000 -4.461999 13.078999 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe3-2.L + { + OFFSET 0.161000 -0.247002 1.809000 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe3-3.L + { + OFFSET 0.033000 -0.441999 1.202000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.032040 -0.433550 1.271800 + } + } + } + } + JOINT toe4-1.L + { + OFFSET 2.888001 -4.480000 12.376999 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe4-2.L + { + OFFSET 0.160000 -0.331998 1.491001 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe4-3.L + { + OFFSET -0.035999 -0.251002 1.138999 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.088911 -0.568814 0.969530 + } + } + } + } + JOINT toe5-1.L + { + OFFSET 4.257999 -4.467001 11.711999 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe5-2.L + { + OFFSET 0.046000 -0.265999 0.982000 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe5-3.L + { + OFFSET -0.086999 -0.372000 0.791000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.044329 -0.555482 1.085780 + } + } + } + } + } + } + } + } +} +MOTION +Frames: 1 +Frame Time: 0.04 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 diff --git a/src/python/mnet4/espStream.py b/src/python/mnet4/espStream.py new file mode 100644 index 0000000..8ee20f0 --- /dev/null +++ b/src/python/mnet4/espStream.py @@ -0,0 +1,75 @@ +# HTTP Multipart MJPEG downloader .. +# Edited version to work with HTTP Multipart MJPEG +# Based on https://github.com/sglvladi/TensorFlowObjectDetectionTutorial + +#pip install numpy matplotlib pil opencv-python --user + +import numpy as np +import os +import six.moves.urllib as urllib +import sys +import tarfile +import zipfile +import cv2 + +from collections import defaultdict +from io import StringIO +from matplotlib import pyplot as plt +from PIL import Image + +# Helper code +def load_image_into_numpy_array(image): + (im_width, im_height) = image.size + return np.array(image.getdata()).reshape( + (im_height, im_width, 3)).astype(np.uint8) + + +class ESP32CamStreamer(): + def __init__(self, + url = "http://192.168.1.33:80", + timeout = 35, + readBuffer = 2048 + ): + self.stream = urllib.request.urlopen(url,timeout=timeout) + self.should_stop = False + self.bytebuffer = bytes() + self.readBuffer = readBuffer + + def isOpened(self): + return True + + def release(self): + del self.stream + del self.should_stop + del self.bytebuffer + + def read(self): + # Read frame from camera + success = True + while True: + self.bytebuffer += self.stream.read(self.readBuffer) + a = self.bytebuffer.find(b'\xff\xd8') + b = self.bytebuffer.find(b'\xff\xd9') + if a != -1 and b != -1: + jpg = self.bytebuffer[a:b+2] + self.bytebuffer = self.bytebuffer[b+2:] + try: + self.image_np = cv2.imdecode(np.frombuffer(jpg, dtype=np.uint8), cv2.IMREAD_COLOR) + except: + success=False + break + return success,self.image_np + def visualize( + self, + windowname='Object Detection', + width=800, + height=600 + ): + # Display output + cv2.imshow(windowname, cv2.resize(self.image_np,(width,height))) + + if cv2.waitKey(10) & 0xff == ord('q'): + cv2.destroyAllWindows() + self.should_stop = True + #break + diff --git a/src/python/mnet4/evaluateMocapNET.py b/src/python/mnet4/evaluateMocapNET.py new file mode 100755 index 0000000..afc4d03 --- /dev/null +++ b/src/python/mnet4/evaluateMocapNET.py @@ -0,0 +1,674 @@ +#!/usr/bin/python3 + +""" +Author : "Ammar Qammaz" +Copyright : "2022 Foundation of Research and Technology, Computer Science Department Greece, See license.txt" +License : "FORTH" +""" +#TODO : FIX 3D MODE! +import os +import sys +import gc +import time + +#-------------------------------------------------- +import numpy as np +#-------------------------------------------------- +from readCSV import parseConfiguration,checkIfConfigurationHierarchyIsTheSameAsLabelList,readCSVLabels,readCSVFile,checkIfTestAndTrainListsAreOk,readGroundTruthFile,splitNumpyArray +from DNNModel import setupDNNModelsUsingJSONConfiguration +from DNNTraining import printTrainingVersion,regularTraining,onlineHardExampleMiningTraining +from tools import bcolors,dumpListToFile,getRAMInformation,createDirectory,checkIfFileExists,getConfigurationJointIsDeclaredInHierarchy,getConfigurationJointPriority,getParentNetwork,notification +from getModelFromDatabase import retrieveAndSetupModel,retrieveAndSetupBasedOnSerial + +print("evaluateMocapNET starting up..\n") +printTrainingVersion() +print(""" +███████╗██╗ ██╗ █████╗ ██╗ ██╗ ██╗ █████╗ ████████╗███████╗ +██╔════╝██║ ██║██╔══██╗██║ ██║ ██║██╔══██╗╚══██╔══╝██╔════╝ +█████╗ ██║ ██║███████║██║ ██║ ██║███████║ ██║ █████╗ +██╔══╝ ╚██╗ ██╔╝██╔══██║██║ ██║ ██║██╔══██║ ██║ ██╔══╝ +███████╗ ╚████╔╝ ██║ ██║███████╗╚██████╔╝██║ ██║ ██║ ███████╗ +╚══════╝ ╚═══╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ + +""") + +print("Ensuring the same seed for reproductible results always..\n") +from numpy.random import seed +seed(1) +np.set_printoptions(precision=2) +np.set_printoptions(formatter={"float_kind": lambda x: "%g" % x}) + + +def setBackendToHalfFloats(): + dtype='float16' + print("\nUsing Half-Floats for training "); + + +#----------------------------------------------------------------------------------------------------------------------- +def identifyProblematicOutput(mapPath): + print("READING evaluateMocapNET.lastresults.csv") + #----------------------------------------------------------------- + losses = readCSVFile("evaluateMocapNET.lastresults.csv" , 1.0 , 0) + #----------------------------------------------------------------- + f = open("evaluateMocapNET.identified.csv", "w") + f.write("SampleID,MAE,BVH,fID,cumulative\n") + # Using readlines() + file1 = open(mapPath, 'r') + Lines = file1.readlines() + #----------------------------------------------------------------- + minimum=np.min(losses['body']) + maximum=np.max(losses['body']) + median=np.median(losses['body']) + mean=np.mean(losses['body']) + std=np.std(losses['body']) + var=np.var(losses['body']) + print(" Min/Max=%0.2f/%0.2f,Median=%0.2f,Mean=%0.2f,Std=%0.2f,Var=%0.2f" % (minimum,maximum,median,mean,std,var)) + sampleID = 0 + hits = 0 + previousBVHFile="dummy initial value" + consecutiveHits=0 + errorThreshold=250.0 + for item in losses['body']: + if (item>errorThreshold): + bvhFile = Lines[sampleID+1].split(",")[0] + fID = int(Lines[sampleID+1].split(",")[1]) + #---------------------------------------------- + if (previousBVHFile!=bvhFile): + consecutiveHits=0 + previousBVHFile=bvhFile + else: + consecutiveHits=consecutiveHits+1 + #---------------------------------------------- + print(sampleID," error=",item[0]," -> ",bvhFile," fID ",fID) + f.write("%u,%f,%s,%u,%u\n"%(sampleID,item[0],bvhFile,fID,consecutiveHits)) + hits=hits+1 + sampleID=sampleID+1 + #--------------------------------------------------------------- + print("Found ",hits," occurances of error > ",errorThreshold) + file1.close() + +#----------------------------------------------------------------------------------------------------------------------- +def appendRAWResultsForGNUplot(outputFile,globalJointDistances): + fileHandler = open(outputFile, "w") + fileHandler.write(outputFile) + fileHandler.write("\n") + for measurement in globalJointDistances: + fileHandler.write(str(measurement)) + fileHandler.write("\n") + fileHandler.close() +#----------------------------------------------------------------------------------------------------------------------- +def ground3DJointsFromBVH(bvh,rawBVHPrediction :dict): + #Extract a BVH dict of BVH motion fields + #Modify our BVH armature with the new BVH values + if (bvh.modify(rawBVHPrediction)): + + #Render to 2D/3D + bvh.processFrame(0) #only have 1 frame ID + + #Retreive 2D/3D Values + output2D = dict() + output3D = dict() + for jointID in range(0,bvh.numberOfJoints): + #------------------------------------------- + jointName = bvh.getJointName(jointID).lower() + #------------------------------------------- + x3D,y3D,z3D = bvh.getJoint3D(jointID) + output3D["3DX_"+jointName]=float(x3D) + output3D["3DY_"+jointName]=float(y3D) + output3D["3DZ_"+jointName]=float(z3D) + #------------------------------------------- + x2D,y2D = bvh.getJoint2D(jointID) + output2D["2DX_"+jointName]=float(x2D) + output2D["2DY_"+jointName]=float(y2D) + #------------------------------------------- + else: + print(bcolors.FAIL,"We where unable to process the BVH output",bcolors.ENDC) + + return output3D + + +def evaluateMocapNET(useCSVBackend,groundTruthTrain,groundTruthTest): + #---------------------------------------------------------------------------------------------------------- + #---------------------------------------------------------------------------------------------------------- + # Command-Line configuration and parameters and stuff + #---------------------------------------------------------------------------------------------------------- + #---------------------------------------------------------------------------------------------------------- + #Note starting time + startEverythingAt = time.time() + + #1/0 = safe settings + saveResultsToDisk=1 + if (not saveResultsToDisk): + input("\n\n\n\n\nResults will not be saved..! All of the computing time will go in vain..\nAre you sure you want to do this ?\n\n\n\n") + + #Options in case of cluster operation + startPosition = 0 + endPosition = 0 + jobsPerCluster = 0 + jobsPerClusterStart = 0 + jobsPerClusterEnd = 0 + + #Cheapout on the test by default + testMemPercentage=1.0 + + #Use the whole training Dataset by default + memPercentage=1.0 + + #networkCompression=1.0 + useHalfFloats = 0 + useRadians = 0 + + #Resume training options + step=1 + compareAllJoints=0 + + #AutoStart Tensorboard, disabled by default! + autostartTensorboard=0 + + doProfiling=False + doOnlyIdentification = False + + #I/O settings + outputDirectoryForTrainedModels = "step0FrontBody" + dataFile = "body" + hierarchyPartName = "body" + outputMode = "bvh" + + useTestData = True + doHCDPostProcessing = 0 + + #This needs to be populated using the --config argument otherwise session will fail.. + configuration = [] + configurationPath="" + + engine = "onnx" + label = "evaluation" + doProcrustes = 1 + + # Sample call with some example parameters : ( needs --all body to have all joints ) if you see MocapNET always + # gets initiallized with upper/lower networks, this is just needed to correctly parse the ground truth test files + # --getPCA 15 105 + # python3 -m evaluateMocapNET --getPCA 15 32 --config dataset/body_configuration.json --skip 5 --all body + # python3 -m evaluateMocapNET --config dataset/body_configuration.json --all body --skip 5 --mem 100 + # python3 -m evaluateMocapNET --getPCA 210 64 --mem 10 --config dataset/body_configuration.json --all body + # python3 -m evaluateMocapNET --get 128 --config dataset/body_configuration.json --all body + # python3 -m evaluateMocapNET --config dataset/body_configuration.json --all body --engine onnx --3d #<- Evaluate 3D data! + # + # To generate evaluateMocapNET.lastresults.csv and then evaluateMocapNET.identified.csv do : + # python3 -m evaluateMocapNET --config dataset/body_configuration.json --all body --mem 1.0 --engine onnx --traindataset + # python3 -m evaluateMocapNET --config dataset/body_configuration.json --all body --mem 10 --engine onnx --traindataset --identify + #----------------------------------------------------------------------------------------------------------------------------------------- + if (len(sys.argv)>1): + print('Argument List:', str(sys.argv)) + for i in range(0, len(sys.argv)): + if (sys.argv[i]=="--hcd"): + doHCDPostProcessing=1 + if (sys.argv[i]=="--noprocrustes"): + doProcrustes=0 + if (sys.argv[i]=="--3d"): + print(bcolors.WARNING,"Evaluate in 3D mode\n",bcolors.ENDC) + #if needed by the model this will get auto activated later.. + outputMode = "3d" + if (sys.argv[i]=="--identify"): + doOnlyIdentification=True + if (sys.argv[i]=="--engine"): + engine=sys.argv[i+1] + print("Selecting engine : ",engine) + if (sys.argv[i]=="--profile"): + doProfiling=True + if (sys.argv[i]=="--get"): + serial =int(sys.argv[i+1]) + retrieveAndSetupBasedOnSerial(serial,download=1) + if (sys.argv[i]=="--getPCA"): + PCADimensions =int(sys.argv[i+1]) + parameterCount=int(sys.argv[i+2]) + label = "evaluation_pca%u_param%u" % (PCADimensions,parameterCount) + print("Get ",label) + retrieveAndSetupModel(PCADimensions,parameterCount,download=1) + if (sys.argv[i]=="--config"): + configurationPath=sys.argv[i+1] + configuration = parseConfiguration(configurationPath) + setupDNNModelsUsingJSONConfiguration(configuration) + if (sys.argv[i]=="--mem"): + print("\nMemory usage ",sys.argv[i+1]); + memPercentage=float(sys.argv[i+1]) + if (sys.argv[i]=="--skip"): + print("\nSkipping frames ",sys.argv[i+1]); + step=int(sys.argv[i+1]) + if (sys.argv[i]=="--full"): + print("\nComparing All Joints : ",sys.argv[i+1]); + compareAllJoints=int(sys.argv[i+1]) + #----------------------------------------------- + # New 4way split + #----------------------------------------------- + if (sys.argv[i]=="--traindataset"): + print("\n Using training data instead of test data"); + useTestData=False + if (sys.argv[i]=="--dataset"): + print("\nOverriding dataset ",dataFile," and using ",sys.argv[i+1]); + dataFile=sys.argv[i+1] + if (sys.argv[i]=="--all"): + #Don't mix this --all with the step2_OrientatonClassifier.py --all + hierarchyPartName=sys.argv[i+1] + dataFile="%s_all" % hierarchyPartName + outputDirectoryForTrainedModels="step0_%s" % dataFile + if (sys.argv[i]=="--back"): + hierarchyPartName=sys.argv[i+1] + dataFile="%s_back" % hierarchyPartName + outputDirectoryForTrainedModels="step0_%s" % dataFile + if (sys.argv[i]=="--front"): + hierarchyPartName=sys.argv[i+1] + dataFile="%s_front" % hierarchyPartName + outputDirectoryForTrainedModels="step0_%s" % dataFile + if (sys.argv[i]=="--left"): + hierarchyPartName=sys.argv[i+1] + dataFile="%s_left" % hierarchyPartName + outputDirectoryForTrainedModels="step0_%s" % dataFile + if (sys.argv[i]=="--right"): + hierarchyPartName=sys.argv[i+1] + dataFile="%s_right" % hierarchyPartName + outputDirectoryForTrainedModels="step0_%s" % dataFile + #----------------------------------------------- + + if (len(configuration)==0): + print(bcolors.FAIL,"Evaluation cannot run without a configuration!",bcolors.ENDC) + sys.exit(0) + + + #Resolve PCA name.. :( + x = configuration['doPCA'].split("$") + if (len(x)>0): + #If there is a $ character this is our place holder to autocomplete our dataFile + print("PCA filename is resolved from ",configuration['doPCA']) + print(" to ") + configuration['doPCA']=x[0]+dataFile+".pca" + print(configuration['doPCA']) + + + + print("Creating directory for training models "+outputDirectoryForTrainedModels) + createDirectory(outputDirectoryForTrainedModels) + #---------------------------------------------------------------------------------------------------------- + #---------------------------------------------------------------------------------------------------------- + + + #The default compatibility setting is the BMVC2019 2channel NSDM, however nowadays we use NSRM + numberOfChannelsPerNSDMElement=2 + if (configuration['NSDMAlsoUseAlignmentAngles']==1): + numberOfChannelsPerNSDMElement=1 + print("Number of Channels Per NSDM element ",numberOfChannelsPerNSDMElement) + configuration['numberOfChannelsPerNSDMElement']=numberOfChannelsPerNSDMElement + + + #Printout the current configuration with regard to what network to inherit from + if (configuration['rememberWeights']): + print("Reloading previous training results") + else: + print("Not reloading") + + + #---------------------------------------------------------------------------------------------------------- + #---------------------------------------------------------------------------------------------------------- + # Read our training and test datasets + #---------------------------------------------------------------------------------------------------------- + #---------------------------------------------------------------------------------------------------------- + print("\n-------------------------------------------------"); + startAt = time.time() + + #Special case if the user has a set memory requirement we try to mimic it for test as well.. + if (testMemPercentage==1.0): + if (memPercentage==0): testMemPercentage=memPercentage + if (memPercentage>1): testMemPercentage=memPercentage + + #Hardcoded path for dataset if using CSV file + #datasetDirectory="/home/ammar/Documents/Programming/DNNTracker/DNNTracker/dataset" + #datasetDirectory="../../DNNTracker/dataset" + datasetDirectory="dataset/generated/" + + #Auto activate 3D outputMode if needed + if ('outputMode' in configuration) and (configuration['outputMode'] == "3d"): + outputMode="3d" + + #Eigenposes should be loaded before Train/Test files since they are used in the input transform step + #--------------------------------------------------------------------------------------------------- + if ("eigenPoses" in configuration): + #We have eigenPoses Declared! + if (configuration['eigenPoses']==1): + #We have eigenPoses Active! + configuration['eigenPoseData'] = readGroundTruthFile( + configuration, + "Eigenposes", + "%s/2d_%s_eigenposes.csv" % (datasetDirectory,dataFile), + "%s/%s_%s_eigenposes.csv" % (datasetDirectory,outputMode,dataFile), # comparisonMode / configuration['outputMode'] is either bvh or 3d + 1.0, + numberOfChannelsPerNSDMElement, + useRadians, + useHalfFloats + ) + + + datasetUsedLabel="Train" + filenamePostfix="" + if (useTestData): + datasetUsedLabel="Test" + filenamePostfix="_test" + + #Easier and faster way to get output list.. + if (useCSVBackend): + inputLabelsWithoutNSDM = readCSVLabels("%s/2d_%s%s.csv" % (datasetDirectory,dataFile,filenamePostfix)) + outputLabels = readCSVLabels("%s/%s_%s%s.csv" % (datasetDirectory,outputMode,dataFile,filenamePostfix)) #outputMode is either bvh or 3d + mapPath = "%s/map_%s%s.csv" % (datasetDirectory,dataFile,filenamePostfix) + else: + inputLabelsWithoutNSDM = groundTruthTrain["labelInNoNSDM"] + outputLabels = groundTruthTrain["labelOut"] + #No Map Path will cause an error in --identify + + + if (not checkIfConfigurationHierarchyIsTheSameAsLabelList(configuration,inputLabelsWithoutNSDM,0)): + print(bcolors.WARNING +"step0_DNNTrainer.py : Inconsistent Configuration association compared to the inputLabelList" + bcolors.ENDC ) + sys.exit(1) + + + print("Input Labels (no NSDM) : "); + print(inputLabelsWithoutNSDM) + + print("\nOutput Labels : "); + print(outputLabels) + + numberOfOutputColumns=len(outputLabels) + #Initially we would have to do all outputs ourself + endPosition=numberOfOutputColumns + + #But if a cluster has been set up we should only do *some* of the training + if (jobsPerCluster): + startPosition = jobsPerClusterStart + endPosition = jobsPerClusterEnd + + + print("\nWill run from ",startPosition," to ",endPosition," / ",numberOfOutputColumns); + + RAMBefore = getRAMInformation() + print("Initial | free RAM ",RAMBefore['free']," KB"); + + #If we want to use the CSV loading back end here + #otherwise we assume groundTruthTrain and groundTruthTest have been already provided + if (useCSVBackend): + #--------------------------------------------------------------- + #Simplify.. + data2D = readCSVFile("%s/2d_%s%s.csv" % (datasetDirectory,dataFile,filenamePostfix),testMemPercentage,useHalfFloats) + dataBVH = readCSVFile("%s/%s_%s%s.csv" % (datasetDirectory,outputMode,dataFile,filenamePostfix),testMemPercentage,useHalfFloats) + groundTruthTest = dict() + groundTruthTest['labelIn'] = data2D['label'] + groundTruthTest['in'] = data2D['body'] + groundTruthTest['labelOut'] = dataBVH['label'] + groundTruthTest['out'] = dataBVH['body'] + checkIfTestAndTrainListsAreOk(groundTruthTest,groundTruthTrain) + #--------------------------------------------------------------- + + RAMAfter = getRAMInformation() + print("After reading the dataset | free RAM ",RAMAfter['free']," KB"); + #---------------------------------------------------------------------------------------------------------- + endAt = time.time() + print("Memory Occupied ",(RAMAfter['used']-RAMBefore['used'])/1024," MB") + print("Time required to load from disk was ",(endAt-startAt)/60," mins") + # + print("\n-------------------------------------------------"); + #---------------------------------------------------------------------------------------------------------- + #---------------------------------------------------------------------------------------------------------- + + #We have lodaded everything as normal but we only want to do identification in this run! + if (doOnlyIdentification): + identifyProblematicOutput(mapPath) + sys.exit(0) + + #At this point we are ready.. + #We should free whatever memory we have allocated because it is training time..! + #------------------------ + gc.collect() + #------------------------ + + #Select a MocapNET class from tensorflow/tensorrt/onnx/tf-lite engines + from MocapNET import easyMocapNETConstructor + mnet = easyMocapNETConstructor(engine,doProfiling=doProfiling,doHCDPostProcessing=doHCDPostProcessing) #<- do not use post processing to actually evaluate MocapNET NN + mnet.test() + + from align3DPoints import compareGroundTruthToPrediction + + # Use body to grab all joints + #python3 evaluateMocapNET.py --mem 50 --config dataset/body_configuration.json --all upperbody + #----------------------------------------------- + totalSamples = 0 + totalError = 0.0 + averageErrorDistances = list() + allErrorDistanceSamples = list() + errorSamplesPerJoint = dict() + #----------------------------------------------- + numberOfSamples = len(groundTruthTest['in']) + + #Declare joints we are interested in.. + #----------------------------------------- + JOINTS_TO_COMPARE=list() + if (compareAllJoints==0): + JOINTS_TO_COMPARE.append('neck') #0 + JOINTS_TO_COMPARE.append('rshoulder') #1 + JOINTS_TO_COMPARE.append('relbow') #2 + JOINTS_TO_COMPARE.append('rhand') #3 + JOINTS_TO_COMPARE.append('lshoulder') #4 + JOINTS_TO_COMPARE.append('lelbow') #5 + JOINTS_TO_COMPARE.append('lhand') #6 + JOINTS_TO_COMPARE.append('hip') #7 + JOINTS_TO_COMPARE.append('rhip') #8 + JOINTS_TO_COMPARE.append('rknee') #9 + JOINTS_TO_COMPARE.append('rfoot') #10 + JOINTS_TO_COMPARE.append('lhip') #11 + JOINTS_TO_COMPARE.append('lknee') #12 + JOINTS_TO_COMPARE.append('lfoot') #13 + else: + print(bcolors.FAIL,"TODO: IMPLEMENT COMPARE ALL JOINTS",bcolors.ENDC) + sys.exit(1) + + #Allocate errors per joint + #----------------------------------------- + if (len(JOINTS_TO_COMPARE)==0): + numberOfJoints = len(configuration["hierarchy"]) + for jID in range (0,numberOfJoints): + jointName = configuration["hierarchy"][jID]["joint"].lower() + errorSamplesPerJoint[jointName]=list() + JOINTS_TO_COMPARE.append(jointName) + else: + numberOfJoints = len(JOINTS_TO_COMPARE) + for jID in range (0,numberOfJoints): + jointName = JOINTS_TO_COMPARE[jID].lower() + errorSamplesPerJoint[jointName]=list() + #----------------------------------------- + print("Doing evaluation based on errors of the following ",numberOfJoints," joints : ",JOINTS_TO_COMPARE) + + + f = open("evaluateMocapNET.lastresults.csv", "w") + #Append CSV Log + f.write("mae_from_%u_to_%u_step_%u\n" % (0,numberOfSamples,step)) + + framerates = list() + beginTime = time.time() + evaluationMode = "?" + for thisSample in range(0,numberOfSamples,step) : + thisInputRaw = groundTruthTest['in'][thisSample] + correctOutputRaw = groundTruthTest['out'][thisSample] + + regressedResult = dict() + correctResult = dict() + + + #DATA Looks like this : { 'labelInNoNSDM':names2D, 'labelIn':inputLabels, 'in':npInput, 'labelOut':outputLabels, 'out':npOutput }; + thisInput = dict() + for i in range(0,len(groundTruthTest['labelIn'])): + thisLabelIn = groundTruthTest['labelIn'][i].lower() + if ( ( thisLabelIn.find("edm-")== -1 ) and ( thisLabelIn.find("nsrm-")== -1 ) ): + thisInput[thisLabelIn] = float(groundTruthTest['in'][thisSample][i]) + + #If we are dealing with just BVH output use training BVH output to extract 3D data + #------------------------------------------------------------------------------------------------------ + if outputMode=="bvh":# (not 'outputMode' in configuration) or (configuration['outputMode'] == "bvh"): + evaluationMode="bvh" + correctOutput = dict() + for i in range(0,len(groundTruthTest['labelOut'])): + correctOutput[groundTruthTest['labelOut'][i]] = float(groundTruthTest['out'][thisSample][i]) + correctResult = ground3DJointsFromBVH(mnet.bvh,correctOutput) + #------------------------------------------------------------------------------------------------------ + #If we are dealing with 3D output immediately use training 3D output to compare 3D data + elif outputMode=="3d":# ('outputMode' in configuration) and (configuration['outputMode'] == "3d"): + evaluationMode="3d" + correctResult = dict() + for i in range(0,len(groundTruthTest['labelOut'])): + correctResult[groundTruthTest['labelOut'][i]] = float(groundTruthTest['out'][thisSample][i]) + #------------------------------------------------------------------------------------------------------ + startTime = time.time() + #================================================ + #================================================ + regressedResult = mnet.predict3DJoints(thisInput) + #================================================ + #================================================ + endTime = time.time() + processing_time = endTime - startTime + framerates.append(float(processing_time)) + if (processing_time==0): + processing_time=1 + #------------------------------------------------------------------------------------------------------ + + + #print("\n\n\n\nthisInput ",thisInput) + #print("\n\n\n\ncorrectOutput ",correctOutput) + #print("\n\n\n\regressedResult ",regressedResult) + #print("\n\n\n\n") + stats = compareGroundTruthToPrediction( + configuration, + correctResult, #<- We want to compare this + regressedResult, #<- to This + doProcrustes=doProcrustes, + allowProcrustesToChangeScale=1, + jointsToCompare=JOINTS_TO_COMPARE + ) + + #Append CSV log + f.write("%f\n" % (float(stats["meanAverageError"]))) + + #print("\nstats ",stats) + print("\nstats={",end="") + for k in stats.keys(): + print("'%s':%0.2f, "%(k,stats[k]),end="") + print("}\n") + + + progressMessage = "Progress : %0.2f %% %u / %u %0.2f fps" % ((100*thisSample)/numberOfSamples,thisSample,numberOfSamples,1/processing_time) + print(progressMessage," Evaluation Mode :",bcolors.OKGREEN,evaluationMode,bcolors.ENDC,"\n\n\n\n") + + #numberOfJoints = len(configuration["hierarchy"]) + for jID in range (0,numberOfJoints): + jointName = JOINTS_TO_COMPARE[jID] + if jointName in stats: + allErrorDistanceSamples.append(float(stats[jointName])) + errorSamplesPerJoint[jointName].append(float(stats[jointName])) + totalSamples = totalSamples + 1 + totalError = totalError + float(stats[jointName]) + else: + print("Could not find ",jointName) + + averageErrorDistances.append(float(stats["meanAverageError"])) + + endTime = time.time() + print("Finished in ",(endTime-beginTime)/60," minutes") + + f.close() + + + import numpy as np + appendRAWResultsForGNUplot("alldistance.raw",averageErrorDistances) + + for jID in range (0,numberOfJoints): + jointName = JOINTS_TO_COMPARE[jID] + appendRAWResultsForGNUplot("joint_%s.raw"%jointName,errorSamplesPerJoint[jointName]) + + meanProcessingTime = np.mean(framerates) + minimum=np.min(averageErrorDistances) + maximum=np.max(averageErrorDistances) + median=np.median(averageErrorDistances) + mean=np.mean(averageErrorDistances) + std=np.std(averageErrorDistances) + var=np.var(averageErrorDistances) + title_string=" Min/Max=%0.1f/%0.1f,Mean=%0.1f,Median=%0.1f,Std=%0.1f,Var=%0.1f" % (minimum,maximum,mean,median,std,var) + + #Show a summary + #------------------------------------------ + print("Flops : ",mnet.getModelFlops()," Model Params : ",mnet.getModelParameters()) + print("Upperbody Decomposition : ",mnet.ensemble["upperbody"].configuration["decompositionType"]," | Input Size : ",mnet.ensemble["upperbody"].configuration["PCADimensionsKept"]) + print("EDM upperbody : ",mnet.ensemble["upperbody"].configuration["EDM"]) + print("eNSRM upperbody : ",mnet.ensemble["upperbody"].configuration["eNSRM"]) + print("Lowerbody Decomposition : ",mnet.ensemble["lowerbody"].configuration["decompositionType"]," | Input Size : ",mnet.ensemble["lowerbody"].configuration["PCADimensionsKept"]) + print("EDM lowerbody : ",mnet.ensemble["lowerbody"].configuration["EDM"]) + print("eNSRM lowerbody : ",mnet.ensemble["lowerbody"].configuration["eNSRM"]) + print("Sample Step : %u / Total Samples %u "%(step,numberOfSamples)) + print("Procrustes : %u"%doProcrustes) + print("Mean Framerate : %0.2f fps"%(1/meanProcessingTime)) + print("doHCDPostProcessing = ",doHCDPostProcessing) + print("Result : %s "%title_string) + notification("MocapNET Network Evaluation","Done evaluating -> %s " % (title_string)) + + f = open(label+"_results.csv", "w") + f.write("Params,PCA,FPS,Min,Max,Median,Mean,Std,Var\n") + f.write("%0.2f"%(mnet.getModelParameters()/1000.0)) + f.write(",") + f.write(str(mnet.ensemble["upperbody"].configuration["PCADimensionsKept"])) + f.write(",") + f.write("%0.2f"%(1/meanProcessingTime)) + f.write(",") + f.write(str(minimum)) + f.write(",") + f.write(str(maximum)) + f.write(",") + f.write(str(median)) + f.write(",") + f.write(str(mean)) + f.write(",") + f.write(str(std)) + f.write(",") + f.write(str(var)) + f.write("\n") + f.close() + + #Plotting ! + #------------------------------------------ + import matplotlib.pyplot as plt + + fig, ax = plt.subplots(figsize=(8, 4)) + #------------------------------------------ + x = averageErrorDistances + mu = 200 + sigma = 25 + n_bins = 100 + # plot the cumulative histogram + n, bins, patches = ax.hist(x, n_bins, density=True, histtype='step',cumulative=True, label='Accuracy') + #------------------------------------------ + # Add a line showing the expected distribution. + y = ((1 / (np.sqrt(2 * np.pi) * sigma)) * + np.exp(-0.5 * (1 / sigma * (bins - mu))**2)) + y = y.cumsum() + y /= y[-1] + #------------------------------------------ + #ax.plot(bins, y, 'k--', linewidth=1.5, label='Theoretical') + # tidy up the figure + ax.grid(True) + ax.legend(loc='right') + ax.set_title('Cumulative step histograms') + ax.set_xlabel('3D Error (mm)') + ax.set_ylabel('Likelihood of occurrence') + #------------------------------------------ + #plt.show() + print("Done") + + +if __name__ == '__main__': + #When working standalone assume simple csv backend + emptyData = dict() + emptyTestData = { 'labelInNoNSDM': [], 'labelIn': [], 'in': [], 'labelOut':[], 'out': [] } + useCSVBackend=1 + evaluateMocapNET(useCSVBackend,emptyData,emptyTestData) diff --git a/src/python/mnet4/folderStream.py b/src/python/mnet4/folderStream.py new file mode 100644 index 0000000..cf5f871 --- /dev/null +++ b/src/python/mnet4/folderStream.py @@ -0,0 +1,116 @@ +#pip install numpy opencv-python --user + +import numpy as np +import os +import sys +import cv2 + + +""" +Check if a file exists +""" +def checkIfFileExists(filename): + return os.path.isfile(filename) + +def resize_with_padding(image, width, height, DO_PADDING=True, TINY_FLOAT=1e-5): + """ + Resizes an image to the specified size, + adding padding to preserve the aspect ratio. + """ + shape_out=(height,width) + if image.ndim == 3 and len(shape_out) == 2: + shape_out = [*shape_out, 3] + hw_out, hw_image = [np.array(x[:2]) for x in (shape_out, image.shape)] + resize_ratio = np.min(hw_out / hw_image) + hw_wk = (hw_image * resize_ratio + TINY_FLOAT).astype(int) + + # Resize the image + resized_image = cv2.resize( + image, tuple(hw_wk[::-1]), interpolation=cv2.INTER_NEAREST + ) + if not DO_PADDING or np.all(hw_out == hw_wk): + return resized_image + + # Create a black image with the target size + padded_image = np.zeros(shape_out, dtype=np.uint8) + + # Calculate the number of rows/columns to add as padding + dh, dw = (hw_out - hw_wk) // 2 + # Add the resized image to the padded image, with padding on the left and right sides + padded_image[dh : hw_wk[0] + dh, dw : hw_wk[1] + dw] = resized_image + + return padded_image + +class FolderStreamer(): + def __init__(self, + path = "./", + label = "colorFrame_0_", + width = 0, + height = 0 + ): + self.path = path + self.label = label + self.frameNumber = 0 + self.width = width + self.height = height + self.should_stop = False + + def isOpened(self): + return not self.should_stop + + def release(self): + print("Release Called ") + self.should_stop = True + + def read(self): + # Read frame from camera + #---------------------------------------------------------------------- + filenameJPG = "%s/%s%05u.jpg" % (self.path,self.label,self.frameNumber) + filenamePNG = "%s/%s%05u.png" % (self.path,self.label,self.frameNumber) + #---------------------------------------------------------------------- + if (checkIfFileExists(filenameJPG)): + self.img = cv2.imread(filenameJPG) + elif (checkIfFileExists(filenamePNG)): + self.img = cv2.imread(filenamePNG) + else: + print("Could not find ",filenameJPG," or ",filenamePNG) + self.img = None + #---------------------------------------------------------------------- + + if not self.img is None: + if (self.width != 0) and (self.height != 0): + #-------------------------- + width = self.img.shape[1] + height = self.img.shape[0] + print("Received Image size was ",width,"x",height, end = "") + #-------------------------- + self.img = resize_with_padding(self.img, self.width, self.height) + #-------------------------- + width = self.img.shape[1] + height = self.img.shape[0] + print(" Rescaled Image size is ",width,"x",height) + #-------------------------- + + success = True + + self.frameNumber+=1 + else: + success = False + self.should_stop = True + + return success,self.img + + def visualize( + self, + windowname='Object Detection', + width=800, + height=600 + ): + # Display output + cv2.imshow(windowname, cv2.resize(self.img,(width,height))) + + if cv2.waitKey(10) & 0xff == ord('q'): + cv2.destroyAllWindows() + self.should_stop = True + #break + diff --git a/src/python/mnet4/getModelFromDatabase.py b/src/python/mnet4/getModelFromDatabase.py new file mode 100755 index 0000000..02f1807 --- /dev/null +++ b/src/python/mnet4/getModelFromDatabase.py @@ -0,0 +1,483 @@ +#!/usr/bin/python3 + +""" +Author : "Ammar Qammaz" +Copyright : "2022 Foundation of Research and Technology, Computer Science Department Greece, See license.txt" +License : "FORTH" +""" + +import os +import sys +import gc +import time +import numpy as np +#-------------------------------------------------- +from tools import bcolors,notification,checkIfFileExists,getRAMInformation + +def downloadAndParseDatabase(): + file = "modelDatabase.json" + if (checkIfFileExists(file)): + print("Erasing previous database snapshot") + os.system('rm "%s"' % (file)) + os.system('wget "http://ammar.gr/mocapnet/mnet4/database.php" && mv database.php "%s"' % file) + import json + db = [] + with open(file) as f: + s = f.read() + s = s.replace('\t','') + s = s.replace('\n','') + s = s.replace(',}','}') + s = s.replace(',]',']') + db = json.loads(s) + return db + + +def quickCopy(step0,step1): + onnxModel = "%s/model.onnx" % step0 + tfliteModel = "%s/model.tflite" % step0 + if ( (not checkIfFileExists(onnxModel)) or (not checkIfFileExists(tfliteModel)) ): + print("No quick copy capability (old MocapNET package)") + return False + + os.system('rm -rf %s/' % step1) + os.system('mkdir %s/' % step1) + os.system('cp %s/* %s/' % (step0,step1) ) + return True + +def downloadAndCompileSingle(file,part,step0,step1,json,pca,allowQuickCopy=True,download=1): + #--------------------------------------------------------------------------------------- + if (download): + if (not checkIfFileExists(file)): + os.system('wget "http://ammar.gr/mocapnet/mnet4/%s"' % (file)) + else: + print("Ensemble %s already exists locally" % (file)) + #--------------------------------------------------------------------------------------- + print(bcolors.OKBLUE,"Clean up ",step0," \n",bcolors.ENDC) + os.system('rm -rf %s/'% (step0)) + print(bcolors.OKBLUE,"Extracting models from ",file," \n",bcolors.ENDC) + #print(bcolors.OKBLUE,'tar -xf %s' % (file),bcolors.ENDC) + os.system('tar -xf %s' % (file)) + + if (allowQuickCopy) and (quickCopy(step0,step1)): + print(bcolors.OKBLUE,"Quick Copied models \n",bcolors.ENDC) + else: + print(bcolors.OKBLUE,"Combine models \n",bcolors.ENDC) + #os.system('cp %s/%s dataset/'%(step0,pca)) #We dont need to do this it will be done automatically from step1_DNNCombine.py + #os.system('mkdir %s/'%step1) #We dont need to do this it will be done automatically from step1_DNNCombine.py + #os.system('cp %s/%s %s/'%(step0,json,step1))#We dont need to do this it will be done automatically from step1_DNNCombine.py + #os.system('cp %s/%s %s/'%(step0,pca,step1))#We dont need to do this it will be done automatically from step1_DNNCombine.py + print(bcolors.OKBLUE,"Combining %s \n"%part,bcolors.ENDC) + print(bcolors.OKBLUE,'python3 step1_DNNCombine.py --config %s/%s --all %s'%(step0,json,part),bcolors.ENDC) + return_value = os.system('python3 step1_DNNCombine.py --config %s/%s --all %s'%(step0,json,part)) + if (return_value!=0): + print(bcolors.FAIL,"Failed preparing ",part," stopping model database retrieval",bcolors.ENDC) + raise IOError + #--------------------------------------------------------------------------------------- + RAM = getRAMInformation() + if (RAM["total"]<9000000): + os.system('python3 -m tf2onnx.convert --saved-model %s --opset 14 --tag serve --output %s/model.onnx' % (step1,step1)) + os.system('tflite_convert --saved_model_dir=%s --output_file=%s/model.tflite' % (step1,step1)) + notification("MocapNET Database","Finished Compiling MocapNET %s ensemble" % part) + +def downloadAndCompileModel(fileUpper,fileLower,fileHand="",fileFace="",fileReye="",fileMouth="",allowQuickCopy=True,download=1): + #--------------------------------------------------------------------------------------- + if (fileUpper!=""): + downloadAndCompileSingle( + fileUpper, + "upperbody", + "step0_upperbody_all", + "step1_upperbody_all", + "upperbody_configuration.json", + "upperbody_all.pca", + allowQuickCopy=allowQuickCopy + ) + #--------------------------------------------------------------------------------------- + if (fileLower!=""): + downloadAndCompileSingle( + fileLower, + "lowerbody", + "step0_lowerbody_all", + "step1_lowerbody_all", + "lowerbody_configuration.json", + "lowerbody_all.pca", + allowQuickCopy=allowQuickCopy + ) + #--------------------------------------------------------------------------------------- + if (fileHand!=""): + downloadAndCompileSingle( + fileHand, + "lhand", + "step0_lhand_all", + "step1_lhand_all", + "lhand_configuration.json", + "lhand_all.pca", + allowQuickCopy=allowQuickCopy + ) + #--------------------------------------------------------------------------------------- + if (fileFace!=""): + downloadAndCompileSingle( + fileFace, + "face", + "step0_face_all", + "step1_face_all", + "face_configuration.json", + "face_all.pca", + allowQuickCopy=allowQuickCopy + ) + #--------------------------------------------------------------------------------------- + if (fileReye!=""): + downloadAndCompileSingle( + fileReye, + "reye", + "step0_reye_all", + "step1_reye_all", + "reye_configuration.json", + "reye_all.pca", + allowQuickCopy=allowQuickCopy + ) + #--------------------------------------------------------------------------------------- + if (fileMouth!=""): + downloadAndCompileSingle( + fileMouth, + "mouth", + "step0_mouth_all", + "step1_mouth_all", + "mouth_configuration.json", + "mouth_all.pca", + allowQuickCopy=allowQuickCopy + ) + #--------------------------------------------------------------------------------------- + print(bcolors.OKGREEN,"Models ready for use \n",bcolors.ENDC) + print(bcolors.OKGREEN,"try : python3 -m PoseNET --from shuffle.webm\n",bcolors.ENDC) + print(bcolors.OKGREEN,"or : python3 -m evaluateMocapNET --config dataset/body_configuration.json --all body --skip 5\n",bcolors.ENDC) + #--------------------------------------------------------------------------------------- + notification("MocapNET Database","Finished Compiling MocapNET ensemble") + + + +def retrieveAndSetupModel(PCADimensions:int,parameterCount:int,download:int=0): + #Baseline + fileUpper = "87A_Training-22-05-06_13-22-16-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "87B_Training-22-05-06_22-29-54-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + + #PCA 15 - 105K Parameters + if (PCADimensions==15) and (parameterCount==105): + fileUpper = "81B_Training-22-05-03_05-10-48-upperbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + fileLower = "82B_Training-22-05-03_15-03-10-lowerbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + elif (PCADimensions==15) and (parameterCount==32): + fileUpper = "84B_Training-22-05-05_17-12-02-upperbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + fileLower = "85B_Training-22-05-06_03-08-00-lowerbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + elif (PCADimensions==30) and (parameterCount==105): + fileUpper = "79B_Training-22-05-01_06-59-02-upperbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + fileLower = "80B_Training-22-05-01_17-01-13-lowerbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + elif (PCADimensions==30) and (parameterCount==32): + fileUpper = "90A_Training-22-05-08_17-56-16-upperbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + fileLower = "90B_Training-22-05-09_00-15-55-lowerbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + elif (PCADimensions==60) and (parameterCount==105): + fileUpper = "94A_Training-22-05-10_13-08-52-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "94B_Training-22-05-10_19-18-19-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (PCADimensions==60) and (parameterCount==32): + fileUpper = "93A_Training-22-05-09_17-37-46-upperbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + fileLower = "93B_Training-22-05-10_01-27-31-lowerbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + elif (PCADimensions==90) and (parameterCount==105): + fileUpper = "77B_Training-22-04-29_18-02-28-upperbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + fileLower = "78B_Training-22-04-30_03-20-33-lowerbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + elif (PCADimensions==90) and (parameterCount==32): + fileUpper = "88A_Training-22-05-07_09-20-26-upperbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + fileLower = "88B_Training-22-05-07_17-03-03-lowerbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + elif (PCADimensions==120) and (parameterCount==105): + fileUpper = "96A_Training-22-05-11_09-31-02-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "96B_Training-22-05-11_15-52-05-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (PCADimensions==120) and (parameterCount==32): + fileUpper = "77A_Training-22-04-28_22-55-49-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "78A_Training-22-04-29_05-11-09-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (PCADimensions==150) and (parameterCount==105): + fileUpper = "79A_Training-22-04-30_12-55-12-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "80A_Training-22-04-30_19-08-26-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (PCADimensions==150) and (parameterCount==32): + fileUpper = "92A_Training-22-05-09_11-28-35-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "92B_Training-22-05-09_18-00-37-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (PCADimensions==180) and (parameterCount==105): + fileUpper = "99A_Training-22-05-13_01-55-31-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "99B_Training-22-05-13_08-45-27-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (PCADimensions==180) and (parameterCount==32): + fileUpper = "91A_Training-22-05-08_11-35-19-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "91B_Training-22-05-08_18-15-12-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (PCADimensions==210) and (parameterCount==105): + fileUpper = "83A_Training-22-05-03_03-35-52-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "83B_Training-22-05-03_10-10-19-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (PCADimensions==210) and (parameterCount==30): + fileUpper = "103A_Training-22-05-20_07-41-34-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "103B_Training-22-05-21_01-16-48-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (PCADimensions==210) and (parameterCount==32): + fileUpper = "84A_Training-22-05-04_11-39-49-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "85A_Training-22-05-04_19-08-05-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (PCADimensions==210) and (parameterCount==417): + #12 layers deep! + fileUpper = "106A_Training-22-07-11_09-35-20-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "106B_Training-22-07-11_15-57-01-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (PCADimensions==210) and (parameterCount==326): + #12 layers deep! + fileUpper = "107A_Training-22-07-13_09-30-24-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "107B_Training-22-07-13_15-40-47-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (PCADimensions==210) and (parameterCount==83): + #6 layers deep! + fileUpper = "100A_Training-22-05-14_21-23-11-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "100B_Training-22-05-15_02-28-09-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (PCADimensions==210) and (parameterCount==64): + #4 layers deep! + fileUpper = "101A_Training-22-05-16_04-13-58-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "101B_Training-22-05-16_08-08-49-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (PCADimensions==323) and (parameterCount==105): + fileUpper = "95A_Training-22-05-11_04-10-01-upperbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + fileLower = "95B_Training-22-05-11_11-26-16-lowerbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + elif (PCADimensions==323) and (parameterCount==32): + print("TODO") + elif (PCADimensions==323) and (parameterCount==153): + fileUpper = "87A_Training-22-05-06_13-22-16-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "87B_Training-22-05-06_22-29-54-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + else: + print(bcolors.WARNING,"Unknown combination PCA dims ",PCADimensions," and ",parameterCount,"K parameters \n",bcolors.ENDC) + print(bcolors.WARNING,"Completely halting execution to avoid a wrong run!\n",bcolors.ENDC) + sys.exit(1) + return; + + print("Selecting PCA dims ",PCADimensions," and ",parameterCount," K parameters \n") + downloadAndCompileModel(fileUpper,fileLower,download=download) + + + +def retrieveAndSetupBasedOnSerial(serial:int,allowQuickCopy:bool=True,download:int=0): + #Baseline + fileUpper = "" #"116A_Training-22-09-01_00-27-23-upperbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + fileLower = "" #"116B_Training-22-09-01_06-19-41-lowerbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + fileHand = "" + fileFace = "" + fileReye = "" + fileMouth = "" + + if (serial==116): + fileUpper = "116A_Training-22-09-01_00-27-23-upperbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + fileLower = "116B_Training-22-09-01_06-19-41-lowerbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + elif (serial==117): + fileUpper = "117A_Training-22-09-02_15-14-17-upperbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + fileLower = "117B_Training-22-09-03_03-56-55-lowerbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + elif (serial==118): + fileUpper = "118A_Training-22-09-05_02-34-35-upperbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + fileLower = "118B_Training-22-09-05_10-27-46-lowerbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + elif (serial==119): + fileUpper = "119A_Training-22-09-05_23-19-05-upperbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + fileLower = "119B_Training-22-09-06_05-52-27-lowerbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + elif (serial==120): + fileUpper = "120A_Training-22-09-06_19-16-27-upperbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + fileLower = "120B_Training-22-09-07_00-21-03-lowerbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + elif (serial==125): + fileUpper = "125-A-Training-22-10-12_06-28-17-upperbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + fileLower = "125-B-Training-22-10-12_12-06-36-lowerbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + elif (serial==128): + fileUpper = "128-a-remanual.tar.bz2" + fileLower = "128-b-remanual.tar.bz2" + elif (serial==129): + fileUpper = "129-A-Training-22-10-18_01-17-44-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "129-B-Training-22-10-18_07-11-41-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (serial==130): + fileUpper = "130-A-Training-22-10-19_23-50-56-upperbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + fileLower = "130-B-Training-22-10-20_07-05-38-lowerbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + elif (serial==131): + fileUpper = "131-A-manual.tar.bz2" + fileLower = "131-B-manual.tar.bz2" + elif (serial==132): + fileUpper = "132-A-Training-22-10-23_21-16-03-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "132-B-Training-22-10-24_07-28-31-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (serial==133): + fileUpper = "133-A-Training-22-10-25_08-24-58-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "133-B-Training-22-10-25_16-11-05-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (serial==134): + fileUpper = "134-A-Training-22-10-27_06-52-13-upperbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + fileLower = "134-B-Training-22-10-28_20-21-16-lowerbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + elif (serial==137): + fileUpper = "137-A-Training-22-10-29_05-15-56-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "137-B-Training-22-10-29_10-55-49-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (serial==138): + fileUpper = "138-A-Training-22-10-30_03-45-34-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "138-B-Training-22-10-30_12-51-38-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (serial==139): + fileUpper = "139-A-Training-22-10-31_04-38-46-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "139-B-Training-22-10-31_13-29-44-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (serial==140): + fileUpper = "140-A-Training-22-11-02_09-41-59-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "140-B-Training-22-11-02_17-37-13-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (serial==150): + fileUpper = "150-A-Training-22-12-01_05-34-13-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "150-B-Training-22-12-02_06-58-52-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (serial==168): + fileUpper = "168-A-Training-23-01-28_22-23-09-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "168-B-Training-23-01-31_02-23-50-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileHand = "169A-Training-23-02-02_19-05-54-lhand-ammar-forth-Ubuntu-20.04.tar.bz2" + fileFace = "167A-Training-23-01-27_10-23-03-face-elina-kriti-Ubuntu-22.04.tar.bz2" + elif (serial==170): + fileUpper = "170-A-Training-23-02-05_18-10-16-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "170-B-Training-23-02-06_03-49-34-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileHand = "169B-Training-23-02-05_13-51-44-lhand-cvrl-demo-Ubuntu-18.04.tar.bz2" + elif (serial==174): + fileUpper = "174-A-Training-23-02-16_12-34-37-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "174-B-Training-23-02-17_07-21-02-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileHand = "169B-Training-23-02-05_13-51-44-lhand-cvrl-demo-Ubuntu-18.04.tar.bz2" + fileFace = "176A-Training-23-02-17_15-49-14-face-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (serial==177): + fileUpper = "174-A-Training-23-02-16_12-34-37-upperbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileLower = "174-B-Training-23-02-17_07-21-02-lowerbody-ammar-forth-Ubuntu-20.04.tar.bz2" + fileHand = "175B-Training-23-02-21_05-10-46-lhand-cvrl-demo-Ubuntu-18.04.tar.bz2" + fileFace = "176A-Training-23-02-17_15-49-14-face-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (serial==193): + fileUpper = "193-A-Training-23-03-18_22-07-35-upperbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + fileLower = "193-B-Training-23-03-21_01-31-46-lowerbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + #fileHand = "175B-Training-23-02-21_05-10-46-lhand-cvrl-demo-Ubuntu-18.04.tar.bz2" + #fileFace = "176A-Training-23-02-17_15-49-14-face-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (serial==209): + fileUpper = "209-A-Training-23-03-23_16-11-00-upperbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + fileLower = "209-B-Training-23-03-24_13-16-47-lowerbody-cvrl-demo-Ubuntu-18.04.tar.bz2" + fileHand = "217A-Training-23-04-11_03-40-56-lhand-cvrl-demo-Ubuntu-18.04.tar.bz2" + #fileFace = "176A-Training-23-02-17_15-49-14-face-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (serial==217): + fileHand = "217A-Training-23-04-11_03-40-56-lhand-cvrl-demo-Ubuntu-18.04.tar.bz2" + fileUpper = "" + fileLower = "" + elif (serial==232): + fileUpper = "232-A-Training-23-04-21_17-14-06-upperbody-ffe5156750f3-Ubuntu-20.04.tar.bz2" #ffe5156750f3 is supermicro docker container + fileLower = "232-B-Training-23-04-21_21-37-52-lowerbody-ffe5156750f3-Ubuntu-20.04.tar.bz2" + fileHand = "230A-Training-23-04-24_11-14-08-lhand-cvrl-demo-Ubuntu-18.04.tar.bz2" + fileFace = "233A-Training-23-04-23_06-39-35-face-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (serial==234): + fileUpper = "234-A-Training-23-04-25_06-27-18-upperbody-ffe5156750f3-Ubuntu-20.04.tar.bz2" + fileLower = "234-B-Training-23-04-26_00-29-55-lowerbody-ffe5156750f3-Ubuntu-20.04.tar.bz2" + elif (serial==241): + fileUpper = "241-A-Training-23-05-05_20-51-52-upperbody-ffe5156750f3-Ubuntu-20.04.tar.bz2" + fileLower = "241-B-Training-23-05-06_01-32-59-lowerbody-ffe5156750f3-Ubuntu-20.04.tar.bz2" + elif (serial==242): + fileUpper = "242-A-Training-23-05-07_11-16-13-upperbody-ffe5156750f3-Ubuntu-20.04.tar.bz2" + fileLower = "242-B-Training-23-05-07_16-18-47-lowerbody-ffe5156750f3-Ubuntu-20.04.tar.bz2" + elif (serial==262): + fileUpper = "262-A-Training-23-05-20_10-41-38-upperbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + fileLower = "262-B-Training-23-05-21_12-00-38-lowerbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + #fileHand = "251A-Training-23-05-19_17-32-01-lhand-ffe5156750f3-Ubuntu-20.04.tar.bz2" + #fileReye = "261B-Training-23-05-19_03-25-32-reye-ammar-forth-Ubuntu-20.04.tar.bz2" + #fileMouth = "253A-Training-23-05-13_09-53-34-mouth-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (serial==269): + fileUpper = "268-A-Training-23-05-27_20-01-59-upperbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + fileLower = "268-B-Training-23-05-28_16-05-19-lowerbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + fileHand = "269A-Training-23-05-30_22-06-06-lhand-ffe5156750f3-Ubuntu-20.04.tar.bz2" + fileReye = "267B-Training-23-05-26_06-50-57-reye-ammar-forth-Ubuntu-20.04.tar.bz2" + fileMouth = "269B-Training-23-05-29_09-46-36-mouth-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (serial==266): + fileUpper = "266-A-Training-23-05-25_12-31-28-upperbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + fileLower = "266-B-Training-23-05-25_21-46-50-lowerbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + elif (serial==268): + fileUpper = "268-A-Training-23-05-27_20-01-59-upperbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + fileLower = "268-B-Training-23-05-28_16-05-19-lowerbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + elif (serial==270): + fileUpper = "270-A-Training-23-05-30_16-32-29-upperbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + fileLower = "270-B-Training-23-05-31_12-36-21-lowerbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + elif (serial==272): + fileUpper = "272-A-Training-23-06-03_06-50-09-upperbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + fileLower = "272-B-Training-23-06-04_22-01-12-lowerbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + elif (serial==274): + fileUpper = "274-A-Training-23-06-07_23-45-42-upperbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + fileLower = "274-B-Training-23-06-09_19-32-34-lowerbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + elif (serial==277): + fileUpper = "277-A-Training-23-06-11_21-50-57-upperbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + fileLower = "277-B-Training-23-06-13_08-41-09-lowerbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + elif (serial==279): + fileUpper = "279-A-Training-23-06-15_13-36-59-upperbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + fileLower = "279-B-Training-23-06-16_23-44-25-lowerbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + elif (serial==282): + fileUpper = "282-A-Training-23-06-18_06-16-23-upperbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + fileLower = "282-B-Training-23-06-19_03-35-29-lowerbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + elif (serial==284): + #fileUpper = "282-A-Training-23-06-18_06-16-23-upperbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + #fileLower = "282-B-Training-23-06-19_03-35-29-lowerbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + #fileHand = "280B-Training-23-06-17_14-26-51-lhand-ffe5156750f3-Ubuntu-20.04.tar.bz2" + fileReye = "283A-Training-23-06-19_10-01-31-reye-ammar-forth-Ubuntu-20.04.tar.bz2" + fileMouth = "284A-Training-23-06-20_12-01-36-mouth-ammar-forth-Ubuntu-20.04.tar.bz2" #<- Paper FACE + elif (serial==285): + fileUpper = "285-A-Training-23-06-20_18-26-12-upperbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + fileLower = "285-B-Training-23-06-21_10-17-45-lowerbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + elif (serial==287): + fileUpper = "287-A-Training-23-06-22_13-25-01-upperbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + fileLower = "287-B-Training-23-06-23_07-02-15-lowerbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + elif (serial==289): + fileUpper = "289-A-Training-23-06-24_21-37-02-upperbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + fileLower = "289-B-Training-23-06-25_03-03-02-lowerbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + elif (serial==290): + fileUpper = "289-A-Training-23-06-24_21-37-02-upperbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + fileLower = "289-B-Training-23-06-25_03-03-02-lowerbody-cvrl-demo-Ubuntu-20.04.tar.bz2" + fileHand = "290A-Training-23-06-28_10-31-52-lhand-ffe5156750f3-Ubuntu-20.04.tar.bz2" + fileReye = "290B-Training-23-06-27_14-48-40-reye-ammar-forth-Ubuntu-20.04.tar.bz2" + fileMouth = "284B-Training-23-06-21_03-14-26-mouth-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (serial==292): + fileUpper = "292-A-Training-23-07-09_18-18-30-upperbody-ffe5156750f3-Ubuntu-20.04.tar.bz2" + fileLower = "292-B-Training-23-07-10_13-51-52-lowerbody-ffe5156750f3-Ubuntu-20.04.tar.bz2" + elif (serial==293): + fileMouth = "293B-Training-23-07-13_05-05-04-mouth-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (serial==294): + fileUpper = "294-A-Training-23-07-11_19-04-21-upperbody-ffe5156750f3-Ubuntu-20.04.tar.bz2" + fileLower = "294-B-Training-23-07-12_01-32-27-lowerbody-ffe5156750f3-Ubuntu-20.04.tar.bz2" + elif (serial==295): + fileUpper = "295-A-Training-23-07-12_16-47-41-upperbody-ffe5156750f3-Ubuntu-20.04.tar.bz2" + fileLower = "295-B-Training-23-07-12_22-49-19-lowerbody-ffe5156750f3-Ubuntu-20.04.tar.bz2" + elif (serial==297): + fileUpper = "297-A-Training-23-07-14_16-50-22-upperbody-ffe5156750f3-Ubuntu-20.04.tar.bz2" + fileLower = "297-B-Training-23-07-14_21-34-29-lowerbody-ffe5156750f3-Ubuntu-20.04.tar.bz2" + fileReye = "297A-Training-23-07-14_07-26-12-reye-ammar-forth-Ubuntu-20.04.tar.bz2" + fileMouth = "293B-Training-23-07-13_05-05-04-mouth-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (serial==300): + fileUpper = "300-A-Training-23-07-16_02-44-28-upperbody-ffe5156750f3-Ubuntu-20.04.tar.bz2" + fileLower = "300-B-Training-23-07-16_07-40-40-lowerbody-ffe5156750f3-Ubuntu-20.04.tar.bz2" + elif (serial==301): + fileUpper = "301-A-Training-23-07-16_17-58-25-upperbody-ffe5156750f3-Ubuntu-20.04.tar.bz2" + fileLower = "301-B-Training-23-07-16_22-45-49-lowerbody-ffe5156750f3-Ubuntu-20.04.tar.bz2" + else: + print(bcolors.WARNING,"Unknown serial ",serial,bcolors.ENDC) + print(bcolors.WARNING,"Completely halting execution to avoid a wrong run!\n",bcolors.ENDC) + sys.exit(1) + return; + + print("Selecting Serial ",serial," => ",fileUpper," ",fileLower," \n") + print("FileHand ",fileHand," fileFace ",fileFace," \n") + downloadAndCompileModel(fileUpper,fileLower,fileHand=fileHand,fileFace=fileFace,fileReye=fileReye,fileMouth=fileMouth,allowQuickCopy=allowQuickCopy,download=download) + + +if __name__ == '__main__': + + startEverythingAt = time.time() + + db = downloadAndParseDatabase() + print(db) + #sys.exit(0) + + allowQuickCopy = False + #When working standalone assume simple csv backend + if (len(sys.argv)>1): + print('Argument List:', str(sys.argv)) + for i in range(0, len(sys.argv)): + if (sys.argv[i]=="--noquickcopy"): + allowQuickCopy = False + if (sys.argv[i]=="--quickcopy"): + allowQuickCopy = True + #-------------------------------------- + for i in range(0, len(sys.argv)): + if (sys.argv[i]=="--getPCA"): + PCADimensions =int(sys.argv[i+1]) + parameterCount=int(sys.argv[i+2]) + retrieveAndSetupModel(PCADimensions,parameterCount) + if (sys.argv[i]=="--get"): + serial =int(sys.argv[i+1]) + retrieveAndSetupBasedOnSerial(serial,allowQuickCopy=allowQuickCopy,download=1) + + + endAt = time.time() + finalMessage="getModelFromDatabase run took %0.2f mins"%((endAt-startEverythingAt)/60) + print(finalMessage) + + + diff --git a/src/python/mnet4/holisticPartNames.py b/src/python/mnet4/holisticPartNames.py new file mode 100755 index 0000000..d739833 --- /dev/null +++ b/src/python/mnet4/holisticPartNames.py @@ -0,0 +1,990 @@ +#!/usr/bin/python3 +""" +Author : "Ammar Qammaz" +Copyright : "2022 Foundation of Research and Technology, Computer Science Department Greece, See license.txt" +License : "FORTH" +""" + + +def normalize2DPointWhileAlsoMatchingTrainingAspectRatio(x, y, currentAspectRatio, trainingAspectRatio=float(1920/1080)): + if x == 0 and y == 0: + # Only Fix Aspect Ratio on visible points to preserve 0,0,0 that are + # important to the neural network. + return x, y + + aspectRatioDiff = currentAspectRatio - trainingAspectRatio + if (aspectRatioDiff<0): + aspectRatioDiff = -1 * aspectRatioDiff + + #print("ASPECT RATIO DIFFERENCE => ",aspectRatioDiff) + if aspectRatioDiff<0.001: + # Only Fix Aspect Ratio when it needs to be fixed.. + return x, y + + + if currentAspectRatio > trainingAspectRatio: + targetHeight = 1.0 + targetWidth = currentAspectRatio + else: + targetWidth = 1.0 + targetHeight = 1.0 / currentAspectRatio + + addX = (targetWidth - trainingAspectRatio) / 2 + addY = (targetHeight - 1.0) / 2 + + targetAspectRatio = targetWidth / targetHeight + if int(targetAspectRatio * 100) != int(currentAspectRatio * 100): + print("Failed to perfectly match training aspect ratio ({:.5f}), managed to reach ({:.5f})".format(currentAspectRatio, targetAspectRatio)) + + x = (x + addX) / targetWidth + y = (y + addY) / targetHeight + + return x, y + + +def normalize2DPointWhileAlsoMatchingTrainingAspectRatioSimple(x, y, currentAspectRatio, trainingAspectRatio=float(1920/1080)): + aspectRatioFix = float(trainedAspectRatio/currentAspectRatio) + return aspectRatioFix * x, y + + +#------------------------------------------------------------------------------------------------------------------- +def processPoseLandmarks(mnetPose2D,correctLabels,holisticPose,currentAspectRatio,trainedAspectRatio,flipX=False,useVisibility=True,visibilityThreshold=0.3): + itemNumber = 0 + aspectRatioFix = float(trainedAspectRatio/currentAspectRatio) + #------------------------------------------------------------ + if holisticPose is not None: + for item in holisticPose.landmark: + thisLandmarkName = correctLabels[itemNumber].lower() + if (thisLandmarkName!=''): + #-------------------------------- + #Do Visibility logic -> transform + #-------------------------------- + x = item.x + y = item.y + z = item.z + vis = item.visibility + if (useVisibility): + if (vis0.0): + mnetPose2D["2dx_endsite_eye.r"] = rEyeX + mnetPose2D["2dy_endsite_eye.r"] = rEyeY + mnetPose2D["visible_endsite_eye.r"] = rEyeV + #--------------------------------------------------- + #---------------------------------------------------------------------------------------------------------------- + #--------------------------------------------------- + if ("2dx_head_leye" in mnetPose2D) and ("2dy_head_leye" in mnetPose2D) and ("visible_head_leye" in mnetPose2D): + #--------------------------------------------- + lEyeX = float(mnetPose2D["2dx_head_leye"]) + lEyeY = float(mnetPose2D["2dy_head_leye"]) + lEyeV = float(mnetPose2D["visible_head_leye"]) + #--------------------------------------------- + if (lEyeV>0.0): + mnetPose2D["2dx_endsite_eye.l"] = lEyeX + mnetPose2D["2dy_endsite_eye.l"] = lEyeY + mnetPose2D["visible_endsite_eye.l"] = lEyeV + #--------------------------------------------------- + #---------------------------------------------------------------------------------------------------------------- + #--------------------------------------------------- + if ("2dx_head_nosebone_3" in mnetPose2D) and ("2dy_head_nosebone_3" in mnetPose2D) and ("visible_head_nosebone_3" in mnetPose2D): + #--------------------------------------------- + headX = float(mnetPose2D["2dx_head_nosebone_3"]) + headY = float(mnetPose2D["2dy_head_nosebone_3"]) + headV = float(mnetPose2D["visible_head_nosebone_3"]) + #--------------------------------------------- + if (headV>0.0): + mnetPose2D["2dx_head"] = headX + mnetPose2D["2dy_head"] = headY + mnetPose2D["visible_head"] = headV + #--------------------------------------------------- + #---------------------------------------------------------------------------------------------------------------- + #--------------------------------------------------- + + + + + #--------------------------------------------------- + if ("2dx_rshoulder" in mnetPose2D) and ("2dy_rshoulder" in mnetPose2D) and ("visible_rshoulder" in mnetPose2D) and ("2dx_lshoulder" in mnetPose2D) and ("2dy_lshoulder" in mnetPose2D) and ("visible_lshoulder" in mnetPose2D): + #--------------------------------------------- + rX = float(mnetPose2D["2dx_rshoulder"]) + rY = float(mnetPose2D["2dy_rshoulder"]) + rV = float(mnetPose2D["visible_rshoulder"]) + #--------------------------------------------- + lX = float(mnetPose2D["2dx_lshoulder"]) + lY = float(mnetPose2D["2dy_lshoulder"]) + lV = float(mnetPose2D["visible_lshoulder"]) + #--------------------------------------------- + if (rV>0.0) and (lV>0.0): + mnetPose2D["2dx_neck"] = float((rX+lX)/2) + mnetPose2D["2dy_neck"] = float((rY+lY)/2) + mnetPose2D["visible_neck"] = float((rV+lV)/2) + #-------------------------------------- + mnetPose2D["2dx_neck1"] = float((rX+lX)/2) + mnetPose2D["2dy_neck1"] = float((rY+lY)/2) + mnetPose2D["visible_neck1"] = float((rV+lV)/2) + #--------------------------------------------------- + #--------------------------------------------------- + #--------------------------------------------------- + if ("2dx_rhip" in mnetPose2D) and ("2dx_rhip" in mnetPose2D) and ("visible_rhip" in mnetPose2D) and ("2dx_lhip" in mnetPose2D) and ("2dy_lhip" in mnetPose2D) and ("visible_lhip" in mnetPose2D) : + #--------------------------------------------- + rX = float(mnetPose2D["2dx_rhip"]) + rY = float(mnetPose2D["2dy_rhip"]) + rV = float(mnetPose2D["visible_rhip"]) + #--------------------------------------------- + lX = float(mnetPose2D["2dx_lhip"]) + lY = float(mnetPose2D["2dy_lhip"]) + lV = float(mnetPose2D["visible_lhip"]) + #--------------------------------------------- + if (rV>0.0) and (lV>0.0): + mnetPose2D["2dx_hip"] = float((rX+lX)/2) + mnetPose2D["2dy_hip"] = float((rY+lY)/2) + mnetPose2D["visible_hip"] = float((rV+lV)/2) + #--------------------------------------------------- + #--------------------------------------------------- + #--------------------------------------------------- + if ("2dx_endsite_toe1-2.l" in mnetPose2D) and ("2dy_endsite_toe1-2.l" in mnetPose2D) and ("visible_endsite_toe1-2.l" in mnetPose2D) : + #--------------------------------------------- + mnetPose2D["2dx_endsite_toe5-3.l"] = mnetPose2D["2dx_endsite_toe1-2.l"] + mnetPose2D["2dy_endsite_toe5-3.l"] = mnetPose2D["2dy_endsite_toe1-2.l"] + mnetPose2D["visible_endsite_toe5-3.l"] = mnetPose2D["visible_endsite_toe1-2.l"] + #--------------------------------------------------- + #--------------------------------------------------- + #--------------------------------------------------- + if ("2dx_endsite_toe1-2.r" in mnetPose2D) and ("2dy_endsite_toe1-2.r" in mnetPose2D) and ("visible_endsite_toe1-2.r" in mnetPose2D) : + #--------------------------------------------- + mnetPose2D["2dx_endsite_toe5-3.r"] = mnetPose2D["2dx_endsite_toe1-2.r"] + mnetPose2D["2dy_endsite_toe5-3.r"] = mnetPose2D["2dy_endsite_toe1-2.r"] + mnetPose2D["visible_endsite_toe5-3.r"] = mnetPose2D["visible_endsite_toe1-2.r"] + #--------------------------------------------------- + #--------------------------------------------------- + #--------------------------------------------------- + if ("2dx_rear" in mnetPose2D) and ("2dy_rear" in mnetPose2D) and ("visible_rear" in mnetPose2D) : + #--------------------------------------------- + mnetPose2D["2dx_ear.r"] = mnetPose2D["2dx_rear"] + mnetPose2D["2dy_ear.r"] = mnetPose2D["2dy_rear"] + mnetPose2D["visible_ear.r"] = mnetPose2D["visible_rear"] + #--------------------------------------------------- + #--------------------------------------------------- + #--------------------------------------------------- + if ("2dx_rear" in mnetPose2D) and ("2dy_rear" in mnetPose2D) and ("visible_rear" in mnetPose2D) : + #--------------------------------------------- + mnetPose2D["2dx___temporalis02.r"] = mnetPose2D["2dx_rear"] + mnetPose2D["2dy___temporalis02.r"] = mnetPose2D["2dy_rear"] + mnetPose2D["visible___temporalis02.r"] = mnetPose2D["visible_rear"] + mnetPose2D["2dx_ear.r"] = mnetPose2D["2dx_rear"] + mnetPose2D["2dy_ear.r"] = mnetPose2D["2dy_rear"] + mnetPose2D["visible_ear.r"] = mnetPose2D["visible_rear"] + #--------------------------------------------------- + #--------------------------------------------------- + #--------------------------------------------------- + if ("2dx_lear" in mnetPose2D) and ("2dy_lear" in mnetPose2D) and ("visible_lear" in mnetPose2D) : + #--------------------------------------------- + mnetPose2D["2dx___temporalis02.l"] = mnetPose2D["2dx_lear"] + mnetPose2D["2dy___temporalis02.l"] = mnetPose2D["2dy_lear"] + mnetPose2D["visible___temporalis02.l"] = mnetPose2D["visible_lear"] + mnetPose2D["2dx_ear.l"] = mnetPose2D["2dx_lear"] + mnetPose2D["2dy_ear.l"] = mnetPose2D["2dy_lear"] + mnetPose2D["visible_ear.l"] = mnetPose2D["visible_lear"] + #--------------------------------------------------- + #--------------------------------------------------- + #--------------------------------------------------- + if ("2dx_neck" in mnetPose2D) and ("2dy_neck" in mnetPose2D) and ("visible_neck" in mnetPose2D): + #--------------------------------------------- + neckX = float(mnetPose2D["2dx_neck"]) + neckY = float(mnetPose2D["2dy_neck"]) + neckV = float(mnetPose2D["visible_neck"]) + #--------------------------------------------- + if (neckV>0.0): + mnetPose2D["2dx_neck1"] = neckX + mnetPose2D["2dy_neck1"] = neckY + mnetPose2D["visible_neck1"] = neckV + #--------------------------------------------------- + #--------------------------------------------------- + #--------------------------------------------------- + + return mnetPose2D + + + + + + +#MocapNET list of expected inputs +#frameNumber,skeletonID,totalSkeletons,2DX_head,2DY_head,visible_head,2DX_neck,2DY_neck,visible_neck,2DX_rshoulder,2DY_rshoulder,visible_rshoulder,2DX_relbow,2DY_relbow,visible_relbow,2DX_rhand,2DY_rhand,visible_rhand,2DX_lshoulder,2DY_lshoulder,visible_lshoulder,2DX_lelbow,2DY_lelbow,visible_lelbow,2DX_lhand,2DY_lhand,visible_lhand,2DX_hip,2DY_hip,visible_hip,2DX_rhip,2DY_rhip,visible_rhip,2DX_rknee,2DY_rknee,visible_rknee,2DX_rfoot,2DY_rfoot,visible_rfoot,2DX_lhip,2DY_lhip,visible_lhip,2DX_lknee,2DY_lknee,visible_lknee,2DX_lfoot,2DY_lfoot,visible_lfoot,2DX_endsite_eye.r,2DY_endsite_eye.r,visible_endsite_eye.r,2DX_endsite_eye.l,2DY_endsite_eye.l,visible_endsite_eye.l,2DX_rear,2DY_rear,visible_rear,2DX_lear,2DY_lear,visible_lear,2DX_endsite_toe1-2.l,2DY_endsite_toe1-2.l,visible_endsite_toe1-2.l,2DX_endsite_toe5-3.l,2DY_endsite_toe5-3.l,visible_endsite_toe5-3.l,2DX_lheel,2DY_lheel,visible_lheel,2DX_endsite_toe1-2.r,2DY_endsite_toe1-2.r,visible_endsite_toe1-2.r,2DX_endsite_toe5-3.r,2DY_endsite_toe5-3.r,visible_endsite_toe5-3.r,2DX_rheel,2DY_rheel,visible_rheel,2DX_bkg,2DY_bkg,visible_bkg, + + +def getHolisticBodyNameList(): + bn=list() + #--------------------------------------------------- + bn.append("head") #0 - nose + bn.append("head_leye_0") #1 - left_eye_inner + bn.append("endsite_eye.l") #2 - left_eye eye.l + bn.append("head_leye_3") #3 - left_eye_outer + bn.append("head_reye_3") #4 - right_eye_inner + bn.append("endsite_eye.r") #5 - right_eye eye.r + bn.append("head_reye_0") #6 - right_eye_outer + bn.append("lear") #7 - left_ear | ear.l | __temporalis02.l + bn.append("rear") #8 - right_ear | ear.r | __temporalis02.r + bn.append("head_outmouth_0") #9 - mouth_left + bn.append("head_outmouth_6") #10 - mouth_right + bn.append("lshoulder") #11 - left_shoulder + bn.append("rshoulder") #12 - right_shoulder + bn.append("lelbow") #13 - left_elbow + bn.append("relbow") #14 - right_elbow + bn.append("lhand") #15 - left_wrist + bn.append("rhand") #16 - right_wrist + bn.append("finger5-3.l") #17 - left_pinky + bn.append("finger5-3.r") #18 - right_pinky + bn.append("finger2-3.l") #19 - left_index + bn.append("finger2-3.r") #20 - right_index + bn.append("finger1-3.l") #21 - left_thumb + bn.append("finger1-3.r") #22 - right_thumb + bn.append("lhip") #23 - left_hip + bn.append("rhip") #24 - right_hip + bn.append("lknee") #25 - left_knee + bn.append("rknee") #26 - right_knee + bn.append("lfoot") #27 - left_ankle + bn.append("rfoot") #28 - right_ankle + bn.append("lheel") #29 - left_heel + bn.append("rheel") #30 - right_heel + bn.append("endsite_toe1-2.l") #31 - left_foot_index + bn.append("endsite_toe1-2.r") #32 - right_foot_index + return bn +#--------------------------------------------------- + + + +def getBody25BodyNameList(): + bn=list() + #--------------------------------------------------- + bn.append("head") #0 + bn.append("neck") #1 + bn.append("rshoulder") #2 + bn.append("relbow") #3 + bn.append("rhand") #4 + bn.append("lshoulder") #5 + bn.append("lelbow") #6 + bn.append("lhand") #7 + bn.append("hip") #8 + bn.append("rhip") #9 + bn.append("rknee") #10 + bn.append("rfoot") #11 + bn.append("lhip") #12 + bn.append("lknee") #13 + bn.append("lfoot") #14 + bn.append("endsite_eye.r") #15 + bn.append("endsite_eye.l") #16 + bn.append("rear") #17 + bn.append("lear") #18 + bn.append("endsite_toe1-2.l") #19 + bn.append("endsite_toe5-3.l") #20 + bn.append("lheel") #21 + bn.append("endsite_toe1-2.r") #22 + bn.append("endsite_toe5-3.r") #23 + bn.append("rheel") #24 + #bn.append("bkg") #25 + return bn +#--------------------------------------------------- + + + + +#2DX_lhand,2DY_lhand,visible_lhand,2DX_lthumb,2DY_lthumb,visible_lthumb,2DX_finger1-2.l,2DY_finger1-2.l,visible_finger1-2.l,2DX_finger1-3.l,2DY_finger1-3.l,visible_finger1-3.l,2DX_endsite_finger1-3.l,2DY_endsite_finger1-3.l,visible_endsite_finger1-3.l,2DX_finger2-1.l,2DY_finger2-1.l,visible_finger2-1.l,2DX_finger2-2.l,2DY_finger2-2.l,visible_finger2-2.l,2DX_finger2-3.l,2DY_finger2-3.l,visible_finger2-3.l,2DX_endsite_finger2-3.l,2DY_endsite_finger2-3.l,visible_endsite_finger2-3.l,2DX_finger3-1.l,2DY_finger3-1.l,visible_finger3-1.l,2DX_finger3-2.l,2DY_finger3-2.l,visible_finger3-2.l,2DX_finger3-3.l,2DY_finger3-3.l,visible_finger3-3.l,2DX_endsite_finger3-3.l,2DY_endsite_finger3-3.l,visible_endsite_finger3-3.l,2DX_finger4-1.l,2DY_finger4-1.l,visible_finger4-1.l,2DX_finger4-2.l,2DY_finger4-2.l,visible_finger4-2.l,2DX_finger4-3.l,2DY_finger4-3.l,visible_finger4-3.l,2DX_endsite_finger4-3.l,2DY_endsite_finger4-3.l,visible_endsite_finger4-3.l,2DX_finger5-1.l,2DY_finger5-1.l,visible_finger5-1.l,2DX_finger5-2.l,2DY_finger5-2.l,visible_finger5-2.l,2DX_finger5-3.l,2DY_finger5-3.l,visible_finger5-3.l,2DX_endsite_finger5-3.l,2DY_endsite_finger5-3.l,visible_endsite_finger5-3.l +def getHolisticLHandNameList(): + bn=list() + #--------------------------------------------------- + bn.append("lhand") #0 - wrist + bn.append("lthumb") #1 - thumb_cmc + bn.append("finger1-2.l") #2 - thumb_mcp + bn.append("finger1-3.l") #3 - thumb_ip + bn.append("endsite_finger1-3.l") #4 - thumb_tip + bn.append("finger2-1.l") #5 - index_finger_mcp + bn.append("finger2-2.l") #6 - index_finger_pip + bn.append("finger2-3.l") #7 - index_finger_dip + bn.append("endsite_finger2-3.l") #8 - index_finger_tip + bn.append("finger3-1.l") #9 - middle_finger_mcp + bn.append("finger3-2.l") #10 - middle_finger_pip + bn.append("finger3-3.l") #11 - middle_finger_dip + bn.append("endsite_finger3-3.l") #12 - middle_finger_tip + bn.append("finger4-1.l") #13 - ring_finger_mcp + bn.append("finger4-2.l") #14 - ring_finger_pip + bn.append("finger4-3.l") #15 - ring_finger_dip + bn.append("endsite_finger4-3.l") #16 - ring_tip + bn.append("finger5-1.l") #17 - pinky_mcp + bn.append("finger5-2.l") #18 - pinky_pip + bn.append("finger5-3.l") #19 - pinky_dip + bn.append("endsite_finger5-3.l") #20 - pinky_tip + return bn +#--------------------------------------------------- + + + + +#2DX_rhand,2DY_rhand,visible_rhand,2DX_rthumb,2DY_rthumb,visible_rthumb,2DX_finger1-2.r,2DY_finger1-2.r,visible_finger1-2.r,2DX_finger1-3.r,2DY_finger1-3.r,visible_finger1-3.r,2DX_endsite_finger1-3.r,2DY_endsite_finger1-3.r,visible_endsite_finger1-3.r,2DX_finger2-1.r,2DY_finger2-1.r,visible_finger2-1.r,2DX_finger2-2.r,2DY_finger2-2.r,visible_finger2-2.r,2DX_finger2-3.r,2DY_finger2-3.r,visible_finger2-3.r,2DX_endsite_finger2-3.r,2DY_endsite_finger2-3.r,visible_endsite_finger2-3.r,2DX_finger3-1.r,2DY_finger3-1.r,visible_finger3-1.r,2DX_finger3-2.r,2DY_finger3-2.r,visible_finger3-2.r,2DX_finger3-3.r,2DY_finger3-3.r,visible_finger3-3.r,2DX_endsite_finger3-3.r,2DY_endsite_finger3-3.r,visible_endsite_finger3-3.r,2DX_finger4-1.r,2DY_finger4-1.r,visible_finger4-1.r,2DX_finger4-2.r,2DY_finger4-2.r,visible_finger4-2.r,2DX_finger4-3.r,2DY_finger4-3.r,visible_finger4-3.r,2DX_endsite_finger4-3.r,2DY_endsite_finger4-3.r,visible_endsite_finger4-3.r,2DX_finger5-1.r,2DY_finger5-1.r,visible_finger5-1.r,2DX_finger5-2.r,2DY_finger5-2.r,visible_finger5-2.r,2DX_finger5-3.r,2DY_finger5-3.r,visible_finger5-3.r,2DX_endsite_finger5-3.r,2DY_endsite_finger5-3.r,visible_endsite_finger5-3.r +def getHolisticRHandNameList(): + bn=list() + #--------------------------------------------------- + bn.append("rhand") #0 - wrist + bn.append("rthumb") #1 - thumb_cmc + bn.append("finger1-2.r") #2 - thumb_mcp + bn.append("finger1-3.r") #3 - thumb_ip + bn.append("endsite_finger1-3.r") #4 - thumb_tip + bn.append("finger2-1.r") #5 - index_finger_mcp + bn.append("finger2-2.r") #6 - index_finger_pip + bn.append("finger2-3.r") #7 - index_finger_dip + bn.append("endsite_finger2-3.r") #8 - index_finger_tip + bn.append("finger3-1.r") #9 - middle_finger_mcp + bn.append("finger3-2.r") #10 - middle_finger_pip + bn.append("finger3-3.r") #11 - middle_finger_dip + bn.append("endsite_finger3-3.r") #12 - middle_finger_tip + bn.append("finger4-1.r") #13 - ring_finger_mcp + bn.append("finger4-2.r") #14 - ring_finger_pip + bn.append("finger4-3.r") #15 - ring_finger_dip + bn.append("endsite_finger4-3.r") #16 - ring_tip + bn.append("finger5-1.r") #17 - pinky_mcp + bn.append("finger5-2.r") #18 - pinky_pip + bn.append("finger5-3.r") #19 - pinky_dip + bn.append("endsite_finger5-3.r") #20 - pinky_tip + return bn +#--------------------------------------------------- + +#THIS IS HERE FOR COMPATIBILITY +def getOpenPoseFaceNameList(): + bn=list() + #--------------------------------------------------- + bn.append("head_rchin_0") # 0 + bn.append("head_rchin_1") # 1 + bn.append("head_rchin_2") # 2 + bn.append("head_rchin_3") # 3 + bn.append("head_rchin_4") # 4 + bn.append("head_rchin_5") # 5 + bn.append("head_rchin_6") # 6 + bn.append("head_rchin_7") # 7 + bn.append("head_chin") # 8 + bn.append("head_lchin_7") # 9 + bn.append("head_lchin_6") # 10 + bn.append("head_lchin_5") # 11 + bn.append("head_lchin_4") # 12 + bn.append("head_lchin_3") # 13 + bn.append("head_lchin_2") # 14 + bn.append("head_lchin_1") # 15 + bn.append("head_lchin_0") # 16 + bn.append("head_reyebrow_0") # 17 + bn.append("head_reyebrow_1") # 18 + bn.append("head_reyebrow_2") # 19 + bn.append("head_reyebrow_3") # 20 + bn.append("head_reyebrow_4") # 21 + bn.append("head_leyebrow_4") # 22 + bn.append("head_leyebrow_3") # 23 + bn.append("head_leyebrow_2") # 24 + bn.append("head_leyebrow_1") # 25 + bn.append("head_leyebrow_0") # 26 + bn.append("head_nosebone_0") # 27 + bn.append("head_nosebone_1") # 28 + bn.append("head_nosebone_2") # 29 + bn.append("head_nosebone_3") # 30 + bn.append("head_nostrills_0") # 31 + bn.append("head_nostrills_1") # 32 + bn.append("head_nostrills_2") # 33 + bn.append("head_nostrills_3") # 34 + bn.append("head_nostrills_4") # 35 + bn.append("head_reye_0") # 36 + bn.append("head_reye_1") # 37 + bn.append("head_reye_2") # 38 + bn.append("head_reye_3") # 39 + bn.append("head_reye_4") # 40 + bn.append("head_reye_5") # 41 + bn.append("head_leye_0") # 42 + bn.append("head_leye_1") # 43 + bn.append("head_leye_2") # 44 + bn.append("head_leye_3") # 45 + bn.append("head_leye_4") # 46 + bn.append("head_leye_5") # 47 + bn.append("head_outmouth_0") # 48 + bn.append("head_outmouth_1") # 49 + bn.append("head_outmouth_2") # 50 + bn.append("head_outmouth_3") # 51 + bn.append("head_outmouth_4") # 52 + bn.append("head_outmouth_5") # 53 + bn.append("head_outmouth_6") # 54 + bn.append("head_outmouth_7") # 55 + bn.append("head_outmouth_8") # 56 + bn.append("head_outmouth_9") # 57 + bn.append("head_outmouth_10") # 58 + bn.append("head_outmouth_11") # 59 + bn.append("head_inmouth_0") # 60 + bn.append("head_inmouth_1") # 61 + bn.append("head_inmouth_2") # 62 + bn.append("head_inmouth_3") # 63 + bn.append("head_inmouth_4") # 64 + bn.append("head_inmouth_5") # 65 + bn.append("head_inmouth_6") # 66 + bn.append("head_inmouth_7") # 67 + bn.append("head_reye") # 68 + bn.append("head_leye") # 69 + return bn +#--------------------------------------------------- + +#2DX_head_rchin_0,2DY_head_rchin_0,visible_head_rchin_0,2DX_head_rchin_1,2DY_head_rchin_1,visible_head_rchin_1,2DX_head_rchin_2,2DY_head_rchin_2,visible_head_rchin_2,2DX_head_rchin_3,2DY_head_rchin_3,visible_head_rchin_3,2DX_head_rchin_4,2DY_head_rchin_4,visible_head_rchin_4,2DX_head_rchin_5,2DY_head_rchin_5,visible_head_rchin_5,2DX_head_rchin_6,2DY_head_rchin_6,visible_head_rchin_6,2DX_head_rchin_7,2DY_head_rchin_7,visible_head_rchin_7,2DX_head_chin,2DY_head_chin,visible_head_chin,2DX_head_lchin_7,2DY_head_lchin_7,visible_head_lchin_7,2DX_head_lchin_6,2DY_head_lchin_6,visible_head_lchin_6,2DX_head_lchin_5,2DY_head_lchin_5,visible_head_lchin_5,2DX_head_lchin_4,2DY_head_lchin_4,visible_head_lchin_4,2DX_head_lchin_3,2DY_head_lchin_3,visible_head_lchin_3,2DX_head_lchin_2,2DY_head_lchin_2,visible_head_lchin_2,2DX_head_lchin_1,2DY_head_lchin_1,visible_head_lchin_1,2DX_head_lchin_0,2DY_head_lchin_0,visible_head_lchin_0,2DX_head_reyebrow_0,2DY_head_reyebrow_0,visible_head_reyebrow_0,2DX_head_reyebrow_1,2DY_head_reyebrow_1,visible_head_reyebrow_1,2DX_head_reyebrow_2,2DY_head_reyebrow_2,visible_head_reyebrow_2,2DX_head_reyebrow_3,2DY_head_reyebrow_3,visible_head_reyebrow_3,2DX_head_reyebrow_4,2DY_head_reyebrow_4,visible_head_reyebrow_4,2DX_head_leyebrow_4,2DY_head_leyebrow_4,visible_head_leyebrow_4,2DX_head_leyebrow_3,2DY_head_leyebrow_3,visible_head_leyebrow_3,2DX_head_leyebrow_2,2DY_head_leyebrow_2,visible_head_leyebrow_2,2DX_head_leyebrow_1,2DY_head_leyebrow_1,visible_head_leyebrow_1,2DX_head_leyebrow_0,2DY_head_leyebrow_0,visible_head_leyebrow_0,2DX_head_nosebone_0,2DY_head_nosebone_0,visible_head_nosebone_0,2DX_head_nosebone_1,2DY_head_nosebone_1,visible_head_nosebone_1,2DX_head_nosebone_2,2DY_head_nosebone_2,visible_head_nosebone_2,2DX_head_nosebone_3,2DY_head_nosebone_3,visible_head_nosebone_3,2DX_head_nostrills_0,2DY_head_nostrills_0,visible_head_nostrills_0,2DX_head_nostrills_1,2DY_head_nostrills_1,visible_head_nostrills_1,2DX_head_nostrills_2,2DY_head_nostrills_2,visible_head_nostrills_2,2DX_head_nostrills_3,2DY_head_nostrills_3,visible_head_nostrills_3,2DX_head_nostrills_4,2DY_head_nostrills_4,visible_head_nostrills_4,2DX_head_reye_0,2DY_head_reye_0,visible_head_reye_0,2DX_head_reye_1,2DY_head_reye_1,visible_head_reye_1,2DX_head_reye_2,2DY_head_reye_2,visible_head_reye_2,2DX_head_reye_3,2DY_head_reye_3,visible_head_reye_3,2DX_head_reye_4,2DY_head_reye_4,visible_head_reye_4,2DX_head_reye_5,2DY_head_reye_5,visible_head_reye_5,2DX_head_leye_0,2DY_head_leye_0,visible_head_leye_0,2DX_head_leye_1,2DY_head_leye_1,visible_head_leye_1,2DX_head_leye_2,2DY_head_leye_2,visible_head_leye_2,2DX_head_leye_3,2DY_head_leye_3,visible_head_leye_3,2DX_head_leye_4,2DY_head_leye_4,visible_head_leye_4,2DX_head_leye_5,2DY_head_leye_5,visible_head_leye_5,2DX_head_outmouth_0,2DY_head_outmouth_0,visible_head_outmouth_0,2DX_head_outmouth_1,2DY_head_outmouth_1,visible_head_outmouth_1,2DX_head_outmouth_2,2DY_head_outmouth_2,visible_head_outmouth_2,2DX_head_outmouth_3,2DY_head_outmouth_3,visible_head_outmouth_3,2DX_head_outmouth_4,2DY_head_outmouth_4,visible_head_outmouth_4,2DX_head_outmouth_5,2DY_head_outmouth_5,visible_head_outmouth_5,2DX_head_outmouth_6,2DY_head_outmouth_6,visible_head_outmouth_6,2DX_head_outmouth_7,2DY_head_outmouth_7,visible_head_outmouth_7,2DX_head_outmouth_8,2DY_head_outmouth_8,visible_head_outmouth_8,2DX_head_outmouth_9,2DY_head_outmouth_9,visible_head_outmouth_9,2DX_head_outmouth_10,2DY_head_outmouth_10,visible_head_outmouth_10,2DX_head_outmouth_11,2DY_head_outmouth_11,visible_head_outmouth_11,2DX_head_inmouth_0,2DY_head_inmouth_0,visible_head_inmouth_0,2DX_head_inmouth_1,2DY_head_inmouth_1,visible_head_inmouth_1,2DX_head_inmouth_2,2DY_head_inmouth_2,visible_head_inmouth_2,2DX_head_inmouth_3,2DY_head_inmouth_3,visible_head_inmouth_3,2DX_head_inmouth_4,2DY_head_inmouth_4,visible_head_inmouth_4,2DX_head_inmouth_5,2DY_head_inmouth_5,visible_head_inmouth_5,2DX_head_inmouth_6,2DY_head_inmouth_6,visible_head_inmouth_6,2DX_head_inmouth_7,2DY_head_inmouth_7,visible_head_inmouth_7,2DX_head_reye,2DY_head_reye,visible_head_reye,2DX_head_leye,2DY_head_leye,visible_head_leye +def getHolisticFaceNameList(useNumbers=False): + #Last Update : 27/03/23 (Automatic via associate2D files) + bn=list() + if (useNumbers): + for i in range(0,486): + bn.append("head_%u"%i) + return bn + #--------------------------------------------------- + #IMPORTANT MAKE SURE TO KEEP THIS IN SYNC WITH holisticDumped.py + #--------------------------------------------------- + bn.append("head_outmouth_3") #0 + bn.append("head") #1 + bn.append("head_nostrills_2") #2 + bn.append("") #3 + bn.append("head_nosebone_3") #4 + bn.append("head_nosebone_2") #5 + bn.append("") #6 + bn.append("") #7 + bn.append("") #8 + bn.append("") #9 + bn.append("") #10 + bn.append("") #11 head_outmouth_3 + bn.append("head_inmouth_2") #12 + bn.append("") #13head_inmouth_2 + bn.append("") #14head_inmouth_6 + bn.append("") #15 + bn.append("head_inmouth_6") #16 + bn.append("head_outmouth_9") #17head_outmouth_9 + bn.append("") #18 + bn.append("head_nostrills_2") #19 + bn.append("") #20 + bn.append("") #21 + bn.append("") #22 + bn.append("") #23 + bn.append("") #24 + bn.append("") #25 + bn.append("") #26 + bn.append("") #27 + bn.append("") #28 + bn.append("") #29 + bn.append("") #30 + bn.append("") #31 + bn.append("") #32 + bn.append("head_reye_0") #33 was head_reye_3 + bn.append("head_rchin_0") #34 was head_rchin_0 + bn.append("") #35 + bn.append("") #36 + bn.append("head_outmouth_2") #37head_outmouth_4 + bn.append("head_inmouth_1") #38 + bn.append("") #39 + bn.append("head_outmouth_1") #40 + bn.append("") #41 + bn.append("") #42 + bn.append("") #43 + bn.append("") #44 + bn.append("") #45 + bn.append("") #46 + bn.append("") #47 + bn.append("") #48 + bn.append("") #49 + bn.append("") #50 + bn.append("") #51 + bn.append("head_reyebrow_2") #52 was head_reyebrow_2 + bn.append("head_reyebrow_1") #53 was head_reyebrow_3 + bn.append("") #54 + bn.append("head_reyebrow_4") #55 was head_reyebrow_0 + bn.append("") #56 + bn.append("") #57 + bn.append("") #58 was head_lchin_3 + bn.append("") #59 + bn.append("") #60 + bn.append("head_outmouth_0") #61 + bn.append("") #62 + bn.append("") #63 + bn.append("") #64 + bn.append("head_reyebrow_3") #65 was head_reyebrow_1 + bn.append("") #66 head_leyebrow_3 + bn.append("") #67 + bn.append("") #68 + bn.append("") #69 + bn.append("head_reyebrow_0") #70 + bn.append("") #71 + bn.append("") #72 + bn.append("") #73head_outmouth_5 + bn.append("") #74 + bn.append("") #75 + bn.append("") #76 + bn.append("") #77 + bn.append("head_inmouth_0") #78 + bn.append("head_nostrills_1") #79 + bn.append("") #80 + bn.append("") #81 + bn.append("") #82head_inmouth_3 + bn.append("") #83 + bn.append("head_outmouth_10") #84head_outmouth_8 + bn.append("") #85 + bn.append("head_inmouth_7") #86 + bn.append("") #87head_inmouth_5 + bn.append("") #88 + bn.append("") #89 + bn.append("") #90 + bn.append("head_outmouth_11") #91 + bn.append("") #92 + bn.append("") #93 + bn.append("") #94 + bn.append("") #95 + bn.append("") #96 + bn.append("") #97 + bn.append("") #98 + bn.append("") #99 + bn.append("") #100 + bn.append("") #101 + bn.append("head_nostrills_0") #102 + bn.append("") #103 + bn.append("") #104 + bn.append("") #105 head_leyebrow_2 + bn.append("") #106 + bn.append("") #107 + bn.append("") #108 + bn.append("") #109 + bn.append("") #110 + bn.append("") #111 + bn.append("") #112 + bn.append("") #113 + bn.append("") #114 + bn.append("") #115 + bn.append("") #116 + bn.append("") #117 + bn.append("") #118 + bn.append("") #119 + bn.append("") #120 + bn.append("") #121 + bn.append("") #122 + bn.append("") #123 + bn.append("") #124 + bn.append("") #125 + bn.append("") #126 + bn.append("") #127 + bn.append("") #128 + bn.append("") #129 + bn.append("") #130 + bn.append("") #131 + bn.append("") #132 + bn.append("head_reye_3") #133 + bn.append("") #134 + bn.append("") #135 + bn.append("") #136 + bn.append("head_rchin_2") #137 was head_rchin_1 + bn.append("head_rchin_5") #138 + bn.append("") #139 + bn.append("") #140 + bn.append("") #141 + bn.append("") #142 + bn.append("") #143 + bn.append("head_reye_5") #144 head_reye_4 + bn.append("") #145 + bn.append("") #146 + bn.append("") #147 + bn.append("") #148 was head_rchin_7 + bn.append("") #149 was head_rchin_6 + bn.append("") #150 was head_rchin_5 + bn.append("") #151 + bn.append("head_chin") #152 + bn.append("head_reye_4") #153 head_reye_5 + bn.append("") #154 + bn.append("") #155 head_reye_0 + bn.append("") #156 was head_reyebrow_4 + bn.append("") #157 + bn.append("head_reye_2") #158 head_reye_1 + bn.append("") #159 + bn.append("head_reye_1") #160 head_reye_2 + bn.append("") #161 + bn.append("") #162 + bn.append("") #163 + bn.append("") #164 + bn.append("") #165 + bn.append("") #166 + bn.append("") #167 + bn.append("head_nosebone_0") #168 + bn.append("") #169 + bn.append("head_rchin_6") #170 + bn.append("head_rchin_7") #171 + bn.append("") #172 was head_rchin_4 + bn.append("") #173 + bn.append("") #174 + bn.append("") #175 + bn.append("") #176 + bn.append("head_rchin_3") #177 + bn.append("") #178 + bn.append("") #179 + bn.append("") #180head_outmouth_7 + bn.append("") #181 + bn.append("") #182 + bn.append("") #183head_inmouth_4 + bn.append("") #184 + bn.append("") #185head_outmouth_6 + bn.append("") #186 + bn.append("") #187 + bn.append("") #188 + bn.append("") #189 + bn.append("") #190 + bn.append("") #191 + bn.append("") #192 + bn.append("") #193 + bn.append("") #194 + bn.append("") #195 + bn.append("") #196 + bn.append("head_nosebone_1") #197 + bn.append("") #198 + bn.append("") #199 + bn.append("") #200 + bn.append("") #201 + bn.append("") #202 + bn.append("") #203 + bn.append("") #204 + bn.append("") #205 + bn.append("") #206 + bn.append("") #207 + bn.append("") #208 + bn.append("") #209 + bn.append("") #210 + bn.append("") #211 + bn.append("") #212 + bn.append("") #213 was head_rchin_2 + bn.append("") #214 + bn.append("head_rchin_4") #215 + bn.append("") #216 + bn.append("") #217 + bn.append("") #218 + bn.append("") #219 + bn.append("") #220 + bn.append("") #221 + bn.append("") #222 + bn.append("") #223 + bn.append("") #224 + bn.append("") #225 + bn.append("") #226 + bn.append("head_rchin_1") #227 was head_rchin_0 + bn.append("") #228 + bn.append("") #229 + bn.append("") #230 + bn.append("") #231 + bn.append("") #232 + bn.append("") #233 + bn.append("") #234 + bn.append("") #235 + bn.append("") #236 + bn.append("") #237 + bn.append("") #238 + bn.append("") #239 + bn.append("") #240 + bn.append("") #241 + bn.append("") #242 + bn.append("") #243 + bn.append("") #244 + bn.append("") #245 + bn.append("") #246 + bn.append("") #247 + bn.append("") #248 + bn.append("") #249 + bn.append("") #250 + bn.append("") #251 + bn.append("") #252 + bn.append("") #253 + bn.append("") #254 + bn.append("") #255 + bn.append("") #256 + bn.append("") #257 + bn.append("") #258 + bn.append("") #259 + bn.append("") #260 + bn.append("") #261 + bn.append("") #262 + bn.append("head_leye_3") #263 + bn.append("head_lchin_0") #264 was head_lchin_0 + bn.append("") #265 + bn.append("") #266 + bn.append("head_outmouth_4") #267head_outmouth_2 + bn.append("head_inmouth_3") #268 + bn.append("") #269head_outmouth_1 + bn.append("head_outmouth_5") #270 + bn.append("") #271 + bn.append("") #272 + bn.append("") #273 + bn.append("") #274 + bn.append("") #275 + bn.append("") #276 + bn.append("") #277 + bn.append("") #278 + bn.append("") #279 + bn.append("") #280 + bn.append("") #281 + bn.append("head_leyebrow_2") #282 was head_leyebrow_2 + bn.append("head_leyebrow_1") #283 was head_leyebrow_3 + bn.append("") #284 + bn.append("head_leyebrow_4") #285 was head_leyebrow_0 + bn.append("") #286 + bn.append("") #287 + bn.append("") #288 was head_lchin_3 + bn.append("") #289 + bn.append("") #290 + bn.append("head_outmouth_6") #291head_outmouth_0 + bn.append("") #292 + bn.append("") #293 + bn.append("") #294 + bn.append("head_leyebrow_3") #295 was head_leyebrow_1 + bn.append("") #296 + bn.append("") #297 + bn.append("") #298 + bn.append("") #299 + bn.append("head_leyebrow_0") #300 + bn.append("") #301 + bn.append("") #302 + bn.append("") #303 + bn.append("") #304 + bn.append("") #305 + bn.append("") #306 + bn.append("") #307 + bn.append("head_inmouth_4") #308 + bn.append("head_nostrills_3") #309 + bn.append("") #310 + bn.append("") #311 + bn.append("") #312head_inmouth_1 + bn.append("") #313 + bn.append("head_outmouth_8") #314head_outmouth_10 + bn.append("") #315 + bn.append("head_inmouth_5") #316 + bn.append("") #317head_inmouth_7 + bn.append("") #318 + bn.append("") #319 + bn.append("") #320 + bn.append("head_outmouth_7") #321 + bn.append("") #322 + bn.append("") #323 was head_lchin_1 + bn.append("") #324head_inmouth_0 + bn.append("") #325 + bn.append("") #326 + bn.append("") #327 + bn.append("") #328 + bn.append("") #329 + bn.append("") #330 + bn.append("head_nostrills_4") #331 + bn.append("") #332 + bn.append("") #333 + bn.append("") #334 + bn.append("") #335 + bn.append("") #336 + bn.append("") #337 + bn.append("") #338 + bn.append("") #339 + bn.append("") #340 + bn.append("") #341 + bn.append("") #342 + bn.append("") #343 + bn.append("") #344 + bn.append("") #345 + bn.append("") #346 + bn.append("") #347 + bn.append("") #348 + bn.append("") #349 + bn.append("") #350 + bn.append("") #351 + bn.append("") #352 + bn.append("") #353 + bn.append("") #354 + bn.append("") #355 + bn.append("") #356 + bn.append("") #357 + bn.append("") #358 + bn.append("") #359 + bn.append("") #360 + bn.append("") #361 was head_lchin_2 + bn.append("head_leye_0") #362 + bn.append("") #363 + bn.append("") #364 + bn.append("") #365 + bn.append("head_lchin_2") #366 + bn.append("head_lchin_5") #367 + bn.append("") #368 + bn.append("") #369 + bn.append("") #370 + bn.append("") #371 + bn.append("") #372 + bn.append("head_leye_4") #373 + bn.append("") #374 + bn.append("") #375 + bn.append("") #376 + bn.append("") #377 was head_lchin_7 + bn.append("") #378 was head_lchin_6 + bn.append("") #379 was head_lchin_5 + bn.append("head_leye_5") #380 head_reye_4 + bn.append("") #381 + bn.append("") #382 + bn.append("") #383 was head_leyebrow_4 + bn.append("") #384 + bn.append("head_leye_1") #385 head_reye_2 + bn.append("") #386 + bn.append("head_leye_2") #387 head_reye_1 + bn.append("") #388 + bn.append("") #389 + bn.append("") #390 head_reye_5 + bn.append("") #391 + bn.append("") #392 + bn.append("") #393 + bn.append("") #394 + bn.append("head_lchin_6") #395 + bn.append("head_lchin_7") #396 + bn.append("") #397 was head_lchin_4 + bn.append("") #398 + bn.append("") #399 + bn.append("") #400 + bn.append("head_lchin_3") #401 + bn.append("") #402 + bn.append("") #403 + bn.append("") #404 + bn.append("") #405head_outmouth_11 + bn.append("") #406 + bn.append("") #407 + bn.append("") #408 + bn.append("") #409 + bn.append("") #410 + bn.append("") #411 + bn.append("") #412 + bn.append("") #413 + bn.append("") #414 + bn.append("") #415 + bn.append("") #416 + bn.append("") #417 + bn.append("") #418 + bn.append("") #419 + bn.append("") #420 + bn.append("") #421 + bn.append("") #422 + bn.append("") #423 + bn.append("") #424 + bn.append("") #425 + bn.append("") #426 + bn.append("") #427 + bn.append("") #428 + bn.append("") #429 + bn.append("") #430 + bn.append("") #431 + bn.append("") #432 + bn.append("") #433 + bn.append("") #434 + bn.append("head_lchin_4") #435 + bn.append("") #436 + bn.append("") #437 + bn.append("") #438 + bn.append("") #439 + bn.append("") #440 + bn.append("") #441 + bn.append("") #442 + bn.append("") #443 + bn.append("") #444 + bn.append("") #445 + bn.append("") #446 + bn.append("head_lchin_1") #447 was head_lchin_0 + bn.append("") #448 + bn.append("") #449 + bn.append("") #450 + bn.append("") #451 + bn.append("") #452 + bn.append("") #453 + bn.append("") #454 + bn.append("") #455 + bn.append("") #456 + bn.append("") #457 + bn.append("") #458 + bn.append("") #459 + bn.append("") #460 + bn.append("") #461 + bn.append("") #462 + bn.append("") #463 + bn.append("") #464 + bn.append("") #465 head_reye_3 + bn.append("") #466 head_reye_0 + bn.append("") #467 + bn.append("") #468 + bn.append("") #469 + bn.append("") #470 + bn.append("") #471 + bn.append("") #472 + bn.append("") #473 + bn.append("") #474 + bn.append("") #475 + bn.append("") #476 + bn.append("") #477 + bn.append("") #478 + bn.append("") #479 + bn.append("") #480 + bn.append("") #481 + bn.append("") #482 + bn.append("") #483 + bn.append("") #484 + bn.append("") #485 + bn.append("") #486 - + return bn +#--------------------------------------------------- + + +if __name__ == '__main__': + holistic = getHolisticFaceNameList() + openpose = getOpenPoseFaceNameList() + + print("The correspondence between OpenPose/IBUG/Multi-Pie facial landmarks (1..68) is ") + for i in range(0,len(openpose)): + for z in range(0,len(holistic)): + if (openpose[i]==holistic[z]): + print("%u, "%z,end="") + + + + diff --git a/src/python/mnet4/jointMap.py b/src/python/mnet4/jointMap.py new file mode 100755 index 0000000..a7f8870 --- /dev/null +++ b/src/python/mnet4/jointMap.py @@ -0,0 +1,81 @@ +#!/usr/bin/python3 + +""" +Author : "Ammar Qammaz" +Copyright : "2022 Foundation of Research and Technology, Computer Science Department Greece, See license.txt" +License : "FORTH" +""" + +class JointDataMapper: + #----------------------------------------------------------------- + def __init__(self, csvFilename, configuration , jointLabels): + self.filename = csvFilename + self.jointLabelToJointIDMap = dict() + self.numberOfExpectedLabels = len(jointLabels) + jID = 0 + for jointDescription in jointLabels: + self.jointLabelToJointIDMap[jointDescription.lower()]=jID + jID = jID+1 + #----------------------------------------------------------------- + def checkJointListDimensions(self,jointLabels): + return self.numberOfExpectedLabels == len(jointLabels) + #----------------------------------------------------------------- + def getJointID_2DX(self,jointName): + nname = "2dx_"+jointName.lower() + if (not nname in self.jointLabelToJointIDMap): + print("getJointID_2DX: Error resolving ",nname) + return -1 + return self.jointLabelToJointIDMap[nname] + #----------------------------------------------------------------- + def getJointID_2DY(self,jointName): + nname = "2dy_"+jointName.lower() + if (not nname in self.jointLabelToJointIDMap): + print("getJointID_2DY: Error resolving ",nname) + return -1 + return self.jointLabelToJointIDMap[nname] + #----------------------------------------------------------------- + def getJointID_2DZ(self,jointName): + nname = "2dz_"+jointName.lower() + if (not nname in self.jointLabelToJointIDMap): + print("getJointID_2DZ: Error resolving ",nname) + return -1 + return self.jointLabelToJointIDMap[nname] + #----------------------------------------------------------------- + def getJointID_3DX(self,jointName): + nname = "3dx_"+jointName.lower() + if (not nname in self.jointLabelToJointIDMap): + print("getJointID_3DX: Error resolving ",nname) + return -1 + return self.jointLabelToJointIDMap[nname] + #----------------------------------------------------------------- + def getJointID_3DY(self,jointName): + nname = "3dy_"+jointName.lower() + if (not nname in self.jointLabelToJointIDMap): + print("getJointID_3DY: Error resolving ",nname) + return -1 + return self.jointLabelToJointIDMap[nname] + #----------------------------------------------------------------- + def getJointID_3DZ(self,jointName): + nname = "3dz_"+jointName.lower() + if (not nname in self.jointLabelToJointIDMap): + print("getJointID_3DZ: Error resolving ",nname) + return -1 + return self.jointLabelToJointIDMap[nname] + #----------------------------------------------------------------- + def getJointID_Visibility(self,jointName): + nname = "visible_"+jointName.lower() + if (not nname in self.jointLabelToJointIDMap): + print("getJointID_Visibility: Error resolving ",nname) + return -1 + return self.jointLabelToJointIDMap[nname] + #----------------------------------------------------------------- + def getJointID_Exists(self,jointName): + nnameX = "2dx_"+jointName.lower() + nnameY = "2dy_"+jointName.lower() + if ((nnameX in self.jointLabelToJointIDMap) and (nnameY in self.jointLabelToJointIDMap)): + return True + return False + #----------------------------------------------------------------- + +if __name__ == '__main__': + print("jointMap.py is a library it cannot be run standalone") diff --git a/src/python/mnet4/mediapipeHolisticWebcamMocapNET.py b/src/python/mnet4/mediapipeHolisticWebcamMocapNET.py new file mode 100755 index 0000000..e8f00d8 --- /dev/null +++ b/src/python/mnet4/mediapipeHolisticWebcamMocapNET.py @@ -0,0 +1,547 @@ +#!/usr/bin/python3 + +""" +Author : "Ammar Qammaz" +Copyright : "2022 Foundation of Research and Technology, Computer Science Department Greece, See license.txt" +License : "FORTH" +""" + +#pip install mediapipe pandas pillow matplotlib opencv-python + +import cv2 +import mediapipe as mp +import numpy as np +import time +import sys +import os + +from readCSV import parseConfiguration,zeroOutXYJointsThatAreInvisible,performNSRMAlignment +from NSDM import NSDMLabels,createNSDMUsingRules + +from MocapNET import MocapNET + +mp_drawing = mp.solutions.drawing_utils +mp_holistic = mp.solutions.holistic +mp_face_mesh = mp.solutions.face_mesh +#------------------------------------------------------------------------------------------------ +LEFT_EYE = [362, 382, 381, 380, 374, 373, 390, 249, 263, 466, 388, 387, 386, 385,384, 398 ] +LEFT_IRIS = [474,475, 476, 477] +RIGHT_EYE = [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161 , 246 ] +RIGHT_IRIS = [469, 470, 471, 472] +#------------------------------------------------------------------------------------------------ + +#I have added a seperate list with the joints +from holisticPartNames import getHolisticBodyNameList, getHolisticFaceNameList, getHolisticLHandNameList, getHolisticRHandNameList, processPoseLandmarks, guessLandmarks +#------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +MEDIAPIPE_BODY_LANDMARK_NAMES = getHolisticBodyNameList() +MEDIAPIPE_FACE_LANDMARK_NAMES = getHolisticFaceNameList() +MEDIAPIPE_LHAND_LANDMARK_NAMES = getHolisticLHandNameList() +MEDIAPIPE_RHAND_LANDMARK_NAMES = getHolisticRHandNameList() +#------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + +class MediaPipePose(): + def __init__(self): + #Tensorflow attempt to be reasonable + #------------------------------------------ + self.mp = mp_holistic.Holistic(static_image_mode=True) + #------------------------------------------ + self.output = dict() + #------------------------------------------ + def get2DOutput(self): + return self.output + + def processIncomingBodyOnlyLandmarks(self,body,currentAspectRatio,trainedAspectRatio): + #This function runs ~10000Hz + #----------------------------------------------- + mnetPose2D = dict() + #------------------------------------------------------------------------------------ + processPoseLandmarks(mnetPose2D,MEDIAPIPE_BODY_LANDMARK_NAMES ,body,currentAspectRatio,trainedAspectRatio) + #------------------------------------------------------------------------------------ + guessLandmarks(mnetPose2D) #Some landmarks ( neck, hip need to be guessed by others ) + #----------------------------------------------- + return mnetPose2D + + def processIncomingAllLandmarks(self,landmarks,currentAspectRatio,trainedAspectRatio): + #This function runs ~10000Hz + #----------------------------------------------- + mnetPose2D = dict() + #------------------------------------------------------------------------------------ + processPoseLandmarks(mnetPose2D,MEDIAPIPE_BODY_LANDMARK_NAMES ,landmarks.pose_landmarks,currentAspectRatio,trainedAspectRatio) + processPoseLandmarks(mnetPose2D,MEDIAPIPE_LHAND_LANDMARK_NAMES,landmarks.left_hand_landmarks,currentAspectRatio,trainedAspectRatio) + processPoseLandmarks(mnetPose2D,MEDIAPIPE_RHAND_LANDMARK_NAMES,landmarks.right_hand_landmarks,currentAspectRatio,trainedAspectRatio) + processPoseLandmarks(mnetPose2D,MEDIAPIPE_FACE_LANDMARK_NAMES ,landmarks.face_landmarks,currentAspectRatio,trainedAspectRatio) + #------------------------------------------------------------------------------------ + guessLandmarks(mnetPose2D) #Some landmarks ( neck, hip need to be guessed by others ) + #----------------------------------------------- + return mnetPose2D + + def convertImageToMocapNETInput(self,image): + #holistic = self.mp + if (type(image)==type(None)): + print("Invalid Image given, can't do anything with it") + return dict() , image + + width = image.shape[1] + height = image.shape[0] + if ((width==0) or (height==0)): + print("Cannot work with empty image") + return dict() , image + + #Try to speed up by scaling down image + if ( (width>1024) or (height>720) ): + width = int(image.shape[1]/2) + height = int(image.shape[0]/2) + image = cv2.resize(image, (width,height)) + + #The aspect ratios involved + currentAspectRatio = width/height + trainedAspectRatio = 1920/1080 + #----------------------------------------------- + mp_drawing = mp.solutions.drawing_utils + mp_drawing_styles = mp.solutions.drawing_styles + mp_pose = mp.solutions.pose + + # To improve performance, optionally mark the image as not writeable to + # pass by reference. + image.flags.writeable = False + image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + + mocapNETInput = dict() + with mp_pose.Pose(static_image_mode=True,smooth_landmarks=True,model_complexity=0,enable_segmentation=True,min_detection_confidence=0.5) as pose: + # with mp_holistic.Holistic(static_image_mode=True) as holistic: + start = time.time() + #----------------------------------------------- + results = pose.process(image) + #results = holistic.process(image) + #Draw the pose annotation on the image. + image.flags.writeable = True + image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) + + try: + mp_drawing.draw_landmarks(annotated_image, results.face_landmarks , mp_holistic.FACEMESH_TESSELATION) #This used to be called FACE_CONNECTIONS + except: + mp_drawing.draw_landmarks(annotated_image, results.face_landmarks , mp_holistic.FACE_CONNECTIONS) #This used to be called FACE_CONNECTIONS + + mp_drawing.draw_landmarks(image, results.left_hand_landmarks, mp_holistic.HAND_CONNECTIONS) + mp_drawing.draw_landmarks(image, results.right_hand_landmarks, mp_holistic.HAND_CONNECTIONS) + mp_drawing.draw_landmarks( + image, + results.pose_landmarks, + mp_pose.POSE_CONNECTIONS, + landmark_drawing_spec=mp_drawing_styles.get_default_pose_landmarks_style() + ) + + #Body only + #----------------------------------------------- + mocapNETInput = self.processIncomingBodyOnlyLandmarks(results.pose_landmarks,currentAspectRatio,trainedAspectRatio) + #mocapNETInput = self.processIncomingAllLandmarks(results,currentAspectRatio,trainedAspectRatio) + #----------------------------------------------- + + end = time.time() + # Time elapsed + seconds = end - start + if (seconds==0.0): + seconds=1.0 + # Calculate frames per second + fps = 1 / seconds + print("Mediapipe Pose Only 2D Joint Estimation Framerate : ",round(fps,2)," fps \n", end="", flush=True) + + + self.output = mocapNETInput + return mocapNETInput,image + + + +#------------------------------------------------ +#------------------------------------------------ +#------------------------------------------------ +class MediaPipeHolistic(): + def __init__(self,doMediapipeVisualization = False): + #Tensorflow attempt to be reasonable + #------------------------------------------ + self.mp = mp_holistic.Holistic( + static_image_mode=False, + min_detection_confidence=0.3, + min_tracking_confidence=0.3 + ) + #------------------------------------------ + #self.mpHands = mp.solutions.hands.Hands( + # min_detection_confidence=0.5, + # min_tracking_confidence=0.5 + # ) + #------------------------------------------ + self.mpFace = mp_face_mesh.FaceMesh( + max_num_faces=1, + refine_landmarks=True, + min_detection_confidence=0.3, + min_tracking_confidence=0.3 + ) + #------------------------------------------ + self.doMediapipeVisualization = doMediapipeVisualization + self.output = dict() + #------------------------------------------ + def get2DOutput(self): + return self.output + + def processIncomingHolisticLandmarks(self,face,lhand,rhand,body,currentAspectRatio,trainedAspectRatio): + mnetPose2D = dict() + #------------------------------------------------------------------------------------ + processPoseLandmarks(mnetPose2D,MEDIAPIPE_BODY_LANDMARK_NAMES ,body ,currentAspectRatio,trainedAspectRatio,useVisibility=False) + processPoseLandmarks(mnetPose2D,MEDIAPIPE_LHAND_LANDMARK_NAMES,lhand,currentAspectRatio,trainedAspectRatio,useVisibility=False) + processPoseLandmarks(mnetPose2D,MEDIAPIPE_RHAND_LANDMARK_NAMES,rhand,currentAspectRatio,trainedAspectRatio,useVisibility=False) + processPoseLandmarks(mnetPose2D,MEDIAPIPE_FACE_LANDMARK_NAMES ,face ,currentAspectRatio,trainedAspectRatio,useVisibility=False) + #------------------------------------------------------------------------------------ + guessLandmarks(mnetPose2D) #Some landmarks ( neck, hip need to be guessed by others ) + #------------------------------------------------------------------------------------ + return mnetPose2D + + def convertImageToMocapNETInput(self,image): + holisticEstimator = self.mp + faceEstimator = self.mpFace + #handEstimator = self.mpHands + + if (type(image)==type(None)): + print("Invalid Image given, can't do anything with it") + return dict() , image + + #----------------------------------------- + width = image.shape[1] + height = image.shape[0] + if ((width==0) or (height==0)): + print("Cannot work with empty image") + return dict() , image + #----------------------------------------- + currentAspectRatio = width/height + trainedAspectRatio = 1920/1080 + #----------------------------------------- + + start = time.time() + #-------------------------------------------------------------------------------------------------- + # To improve performance, optionally mark the image as not writeable to pass by reference. + image.flags.writeable = False + #============================================== + #================= MEDIAPIPE ================== + #============================================== + results = holisticEstimator.process(image) + resultsF = faceEstimator.process(image) #Extra Face + #resultsH = handEstimator.process(image) + #============================================== + + # Draw the hand annotations on the image. + image.flags.writeable = True + image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) + #-------------------------------------------------------------------------------------------------- + end = time.time() + # Time elapsed + seconds = end - start + if (seconds==0.0): + seconds=1.0 + # Calculate frames per second + fps = 1 / seconds + print("Mediapipe Holistic 2D Joint Estimation Framerate : ",round(fps,2)," fps \n", end="", flush=True) + + #annotated_image = image.copy() + annotated_image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) #Our annotated image must be BGR to show correctly + + #-------------------------------------------------------------------------------------------------- + if (self.doMediapipeVisualization): + #Compensate for name mediapipe change.. + try: + mp_drawing.draw_landmarks(annotated_image, results.face_landmarks , mp_holistic.FACEMESH_TESSELATION) #This used to be called FACE_CONNECTIONS + except: + mp_drawing.draw_landmarks(annotated_image, results.face_landmarks , mp_holistic.FACE_CONNECTIONS) #This used to be called FACE_CONNECTIONS + #-------------------------------------------------------------------------------------------------- + mp_drawing.draw_landmarks(annotated_image, results.left_hand_landmarks , mp_holistic.HAND_CONNECTIONS) + mp_drawing.draw_landmarks(annotated_image, results.right_hand_landmarks, mp_holistic.HAND_CONNECTIONS) + #-------------------------------------------------------------------------------------------------- + #for hand_landmarks in resultsH.multi_hand_landmarks: + # mp_drawing.draw_landmarks(annotated_image, hand_landmarks, mp_hands.HAND_CONNECTIONS) + #-------------------------------------------------------------------------------------------------- + # Use mp_holistic.UPPER_BODY_POSE_CONNECTIONS for drawing below when upper_body_only is set to True. + mp_drawing.draw_landmarks(annotated_image, results.pose_landmarks , mp_holistic.POSE_CONNECTIONS) + #-------------------------------------------------------------------------------------------------- + mocapNETInput = self.processIncomingHolisticLandmarks( + results.face_landmarks, + results.left_hand_landmarks, + results.right_hand_landmarks, + results.pose_landmarks, + currentAspectRatio, + trainedAspectRatio + ) + + #EYES + #-------------------------------------------------------------------------------------------- + img_h, img_w = image.shape[:2] + if resultsF.multi_face_landmarks: + mesh_points=np.array([np.multiply([p.x, p.y], [img_w, img_h]).astype(int) for p in resultsF.multi_face_landmarks[0].landmark]) + (l_cx, l_cy), l_radius = cv2.minEnclosingCircle(mesh_points[LEFT_IRIS]) + (r_cx, r_cy), r_radius = cv2.minEnclosingCircle(mesh_points[RIGHT_IRIS]) + center_left = np.array([l_cx, l_cy], dtype=np.int32) + center_right = np.array([r_cx, r_cy], dtype=np.int32) + #cv2.circle(annotated_image, center_left, int(l_radius), (255,0,0), -1, cv2.LINE_AA) + #cv2.circle(annotated_image, center_right, int(r_radius), (255,0,0), -1, cv2.LINE_AA) + #-------------------------------------------------------- + from holisticPartNames import normalize2DPointWhileAlsoMatchingTrainingAspectRatio + #-------------------------------------------------------- + lEyex2D = float(l_cx/img_w) #Dont Flip X + lEyey2D = float(l_cy/img_h) + lEyex2D,lEyey2D = normalize2DPointWhileAlsoMatchingTrainingAspectRatio(lEyex2D,lEyey2D,currentAspectRatio) + lEyeVis = 0.0 + if ( (lEyex2D!=0.0) or (lEyey2D!=0.0) ): + lEyeVis = 1.0 + mocapNETInput["2dx_head_leye"] = lEyex2D + mocapNETInput["2dy_head_leye"] = lEyey2D + mocapNETInput["visible_head_leye"] = lEyeVis + mocapNETInput["2dx_endsite_eye.l"] = lEyex2D + mocapNETInput["2dy_endsite_eye.l"] = lEyey2D + mocapNETInput["visible_endsite_eye.l"] = lEyeVis + #-------------------------------------------------------- + rEyex2D = float(r_cx/img_w) #Dont Flip X + rEyey2D = float(r_cy/img_h) + rEyex2D,rEyey2D = normalize2DPointWhileAlsoMatchingTrainingAspectRatio(rEyex2D,rEyey2D,currentAspectRatio) + rEyeVis = 0.0 + if ( (rEyex2D!=0.0) or (rEyey2D!=0.0) ): + rEyeVis = 1.0 + mocapNETInput["2dx_head_reye"] = rEyex2D + mocapNETInput["2dy_head_reye"] = rEyey2D + mocapNETInput["visible_head_reye"] = rEyeVis + mocapNETInput["2dx_endsite_eye.r"] = rEyex2D + mocapNETInput["2dy_endsite_eye.r"] = rEyey2D + mocapNETInput["visible_endsite_eye.r"] = rEyeVis + #-------------------------------------------------------- + else: + print("UNABLE TO GET EYES") + #don't populate them at all.. + #mocapNETInput["2dx_head_leye"] = 0.0 + #mocapNETInput["2dy_head_leye"] = 0.0 + #mocapNETInput["visible_head_leye"] = 0.0 + #mocapNETInput["2dx_head_reye"] = 0.0 + #mocapNETInput["2dy_head_reye"] = 0.0 + #mocapNETInput["visible_head_reye"] = 0.0 + #-------------------------------------------------------- + + #-------------------------------------------------------------------------------------------------- + #from MocapNETVisualization import drawMocapNETInput + #drawMocapNETInput(mocapNETInput,annotated_image) + #-------------------------------------------------------------------------------------------------- + #-------------------------------------------------------------------------------------------- + self.output = mocapNETInput + #-------------------------------------------------------------------------------------------- + return mocapNETInput,annotated_image +#------------------------------------------------ +#------------------------------------------------ +#------------------------------------------------ + + + + + + + + + +#------------------------------------------------ +#------------------------------------------------ +#------------------------------------------------ +def streamPosesFromCameraToMocapNET(): + engine = "onnx" + doProfiling = False + multiThreaded = False + videoFilePath = "webcam" + videoWidth = 1280 + videoHeight = 720 + saveVideo = False + doBody = True + doFace = False + doREye = False + doMouth = False + doHands = False + aspectCorrection = 1.0 + scale = 1.0 + addNoise = 0.0 + doHCDPostProcessing = 1 + hcdLearningRate = 0.1 + hcdEpochs = 20 + hcdIterations = 15 + plotBVHChannels = False + calibrationFile = "" + bvhAnglesForPlotting = list() + bvhAllAnglesForPlotting = list() + + #python3 -m mediapipeHolisticWebcamMocapNET --from damien.avi --face --nobody --plot --save + #python3 -m mediapipeHolisticWebcamMocapNET --from damien.avi --nobody --face --plot --aspectCorrection 0.8 --save + + if (len(sys.argv)>1): + #print('Argument List:', str(sys.argv)) + for i in range(0, len(sys.argv)): + if (sys.argv[i]=="--mt"): + multiThreaded = True + if (sys.argv[i]=="--calib"): + calibrationFile = sys.argv[i+1] + if (sys.argv[i]=="--ik"): + hcdLearningRate = float(sys.argv[i+1]) + hcdEpochs = int(sys.argv[i+2]) + hcdIterations = int(sys.argv[i+3]) + if (sys.argv[i]=="--noik"): + doHCDPostProcessing = 0 + if (sys.argv[i]=="--aspectCorrection"): + aspectCorrection=float(sys.argv[i+1]) + if (sys.argv[i]=="--noise"): + addNoise=float(sys.argv[i+1]) + if (sys.argv[i]=="--size"): + videoWidth = int(sys.argv[i+1]) + videoHeight = int(sys.argv[i+2]) + if (sys.argv[i]=="--scale"): + scale=float(sys.argv[i+1]) + if (sys.argv[i]=="--plot"): + plotBVHChannels=True + if (sys.argv[i]=="--nobody"): + doBody=False + if (sys.argv[i]=="--face"): + doFace=True + if (sys.argv[i]=="--eyes") or (sys.argv[i]=="--reye"): + doREye=True + if (sys.argv[i]=="--mouth"): + doMouth=True + if (sys.argv[i]=="--hands"): + doHands=True + if (sys.argv[i]=="--save"): + saveVideo=True + if (sys.argv[i]=="--engine"): + engine=sys.argv[i+1] + print("Selecting engine : ",engine) + if (sys.argv[i]=="--from"): + videoFilePath=sys.argv[i+1] + if (sys.argv[i]=="--profile"): + doProfiling=True + + from MocapNETVisualization import drawMocapNETOutput,drawDescriptor,drawNSRM,drawMAE2DError,drawMocapNETAllPlots + + #Select a MocapNET class from tensorflow/tensorrt/onnx/tf-lite engines + from MocapNET import easyMocapNETConstructor + mnet = easyMocapNETConstructor( + engine, + doProfiling = doProfiling, + multiThreaded = multiThreaded, + doHCDPostProcessing = doHCDPostProcessing, + hcdLearningRate = hcdLearningRate, + hcdEpochs = hcdEpochs, + hcdIterations = hcdIterations, + bvhScale = scale, + doBody = doBody, + doFace = doFace, + doREye = doREye, + doMouth = doMouth, + doHands = doHands, + addNoise = addNoise + ) + + if (calibrationFile!=""): + print("Enforcing Calibration file : ",calibrationFile) + mnet.bvh.configureRendererFromFile(calibrationFile) + + mnet.test() + mnet.recordBVH(True) + #Body only + #mp = MediaPipePose() + mp = MediaPipeHolistic(doMediapipeVisualization = False) + + # For webcam input: + frameNumber = 0 + if (videoFilePath=="esp"): + from espStream import ESP32CamStreamer + cap = ESP32CamStreamer() + elif (videoFilePath=="webcam"): + cap = cv2.VideoCapture(0) + cap.set(cv2.CAP_PROP_FRAME_WIDTH, videoWidth) + cap.set(cv2.CAP_PROP_FRAME_HEIGHT, videoHeight) + elif (videoFilePath=="/dev/video0"): + cap = cv2.VideoCapture(0) + cap.set(cv2.CAP_PROP_FRAME_WIDTH, videoWidth) + cap.set(cv2.CAP_PROP_FRAME_HEIGHT, videoHeight) + elif (videoFilePath=="/dev/video1"): + cap = cv2.VideoCapture(1) + cap.set(cv2.CAP_PROP_FRAME_WIDTH, videoWidth) + cap.set(cv2.CAP_PROP_FRAME_HEIGHT, videoHeight) + elif (videoFilePath=="/dev/video2"): + cap = cv2.VideoCapture(2) + cap.set(cv2.CAP_PROP_FRAME_WIDTH, videoWidth) + cap.set(cv2.CAP_PROP_FRAME_HEIGHT, videoHeight) + else: + from tools import checkIfPathIsDirectory + if (checkIfPathIsDirectory(videoFilePath) and (not "/dev/" in videoFilePath) ): + from folderStream import FolderStreamer + cap = FolderStreamer(path=videoFilePath,width=videoWidth,height=videoHeight) + mnet.bvh.configureRendererFromFile("%s/color.calib"%videoFilePath) + else: + cap = cv2.VideoCapture(videoFilePath) + #----------------------------------------- + + #------------------------------------------------ + #------------------------------------------------ + #------------------------------------------------ + maxBrokenFrames = 100 + brokenFrames = 0 + while cap.isOpened(): + success, image = cap.read() + plotImage = image + if not success: + print("Ignoring empty camera frame : ",brokenFrames,"/",maxBrokenFrames) + brokenFrames = brokenFrames + 1 + if (brokenFrames>maxBrokenFrames): + break + else: + continue + # If loading a video, use 'break' instead of 'continue'. + #continue + + if ( aspectCorrection!=1.0 ): + width = int(image.shape[1]*aspectCorrection) + height = int(image.shape[0]) + image = cv2.resize(image, (width,height)) + + #-------------------------------------------------------------------------------------------------------------- + mocapNETInput,annotated_image = mp.convertImageToMocapNETInput(image) + #-------------------------------------------------------------------------------------------------------------- + mocapNET3DOutput = mnet.predict3DJoints(mocapNETInput) + mocapNETBVHOutput = mnet.outputBVH + bvhAnglesForPlotting.append(mocapNETBVHOutput) + bvhAllAnglesForPlotting.append(mocapNETBVHOutput) + if (len(bvhAnglesForPlotting)>100): + bvhAnglesForPlotting.pop(0) + #-------------------------------------------------------------------------------------------------------------- + from MocapNETVisualization import visualizeMocapNETEnsemble + image,plotImage = visualizeMocapNETEnsemble(mnet,annotated_image,plotBVHChannels=plotBVHChannels,bvhAnglesForPlotting=bvhAnglesForPlotting) + #-------------------------------------------------------------------------------------------------------------- + frameNumber = frameNumber + 1 + + if (saveVideo): + cv2.imwrite('colorFrame_0_%05u.jpg'%(frameNumber), annotated_image) + if (plotBVHChannels): + cv2.imwrite('plotFrame_0_%05u.jpg'%(frameNumber), plotImage) + + cv2.imshow('MocapNET 4 using MediaPipe Holistic 2D Joints', annotated_image) + if cv2.waitKey(1) & 0xFF == 27: + break + + + if (saveVideo): # 1280x720 by default + os.system("ffmpeg -framerate 30 -i colorFrame_0_%%05d.jpg -s %ux%u -y -r 30 -pix_fmt yuv420p -threads 8 livelastRun3DHiRes.mp4 && rm colorFrame_0_*.jpg " % (videoWidth,videoHeight)) # + if (plotBVHChannels): + os.system("ffmpeg -framerate 30 -i plotFrame_0_%05d.jpg -s 1200x720 -y -r 30 -pix_fmt yuv420p -threads 8 livelastPlot3DHiRes.mp4 && rm plotFrame_0_*.jpg") + + + del mnet #So that the out.bvh file gets created.. + os.system("rm 2d_out.csv 3d_out.csv bvh_out.csv map_out.csv") + os.system("./GroundTruthDumper --from out.bvh --setPositionRotation -2.6 0 2000 0 0 0 --csv ./ out.csv 2d+bvh ") # Remove noise offsetPositionRotation + + + cap.release() +#------------------------------------------------ +#------------------------------------------------ +#------------------------------------------------ + + + +if __name__ == '__main__': + streamPosesFromCameraToMocapNET() diff --git a/src/python/mnet4/plotCSV.py b/src/python/mnet4/plotCSV.py new file mode 100755 index 0000000..52d7583 --- /dev/null +++ b/src/python/mnet4/plotCSV.py @@ -0,0 +1,242 @@ +#!/usr/bin/python3 +import sys +import numpy as np +import matplotlib.pyplot as plt +from readCSV import readGroundTruthFile,splitNumpyArray +from readCSV import parseConfiguration + +def splitNumpyArray(model,column): + numberOfSamples=len(model) + npOutput = np.empty([numberOfSamples],dtype=float,order='C') + for num in range(0,numberOfSamples): + npOutput[num]=float(model[num,column]); + return npOutput; + +def draw2D(data,width=1920,height=1080): + #---------------------------------------------------- + from MocapNETVisualization import drawMocapNETInput + #---------------------------------------------------- + import cv2 + import numpy as np + #---------------------------------------------------- + for sampleID in range(0,len(data['body'])): + image = np.zeros([height,width,3],dtype=np.uint8) + joints2D = dict() + for labelID in range(0,len(data['label'])): + label = data['label'][labelID].lower() + value = data['body'][sampleID][labelID] + #---------------------------------------------------- + if "2d_x" in label: + value = value * width + if "2d_y" in label: + value = value * height + #---------------------------------------------------- + joints2D[label] = value + drawMocapNETInput(joints2D,image,flipX=False) + + + font = cv2.FONT_HERSHEY_SIMPLEX + org = (30,60) + fontScale = 1.5 + color = (255,255,255) + thickness = 2 + message = 'Sample Number : %u / %u ' % (sampleID,len(data['body'])) + image = cv2.putText(image, message , org, font, fontScale, color, thickness, cv2.LINE_AA) + + + cv2.imshow('2D',image) + #print("Sample ",sampleID," -> ",joints2D) + if cv2.waitKey(1000) & 0xFF == 27: + sys.exit(0) + #---------------------------------------------------- + +def plotDistribution(column,filename,values): + print("Plotting ",filename) + #print(values.shape) + + plt.clf() + plt.hist(values,bins=100) + median = np.median(values) + mean = np.mean(values) + std = np.std(values) + var = np.var(values) + minimum = np.min(values) + maximum = np.max(values) + + title_string="Histogram of Output distribution for %s \nMedian=%0.2f,Mean=%0.2f,Std=%0.2f,Var=%0.2f,Samples=%u,Min=%0.2f,Max=%0.2f" % (filename,median,mean,std,var,len(values),minimum,maximum) + plt.title(title_string) + + print("Stats,%s,%0.2f,%0.2f,%0.2f,%0.2f,%u,%0.2f,%0.2f" % (filename,median,mean,std,var,len(values),minimum,maximum)) + + plt.savefig("%03u_%s.png" % (column,filename)) + +def calculate_relative_magnitudes(edm, n): + row_magnitudes = [] + column_magnitudes = [] + for i in range(n): + row_magnitude = sum(edm[i*n:(i+1)*n]) + row_magnitudes.append(row_magnitude) + column_magnitude = sum(edm[i::n]) + column_magnitudes.append(column_magnitude) + central_element = edm[n*n // 2] + row_magnitudes_sum = sum(row_magnitudes) - central_element + column_magnitudes_sum = sum(column_magnitudes) - central_element + max_magnitude = max(row_magnitudes_sum, column_magnitudes_sum) + relative_row_magnitudes = [m / max_magnitude for m in row_magnitudes] + relative_column_magnitudes = [m / max_magnitude for m in column_magnitudes] + return relative_row_magnitudes, relative_column_magnitudes + + +def plotCovariance(X): + covariance = np.cov(X.T) + + # Plot the covariance matrix + plt.imshow(covariance, cmap='coolwarm', interpolation='nearest') + plt.colorbar() + plt.title('Covariance Matrix') + plt.savefig("covariance.png") + #plt.show() + + +def plotTestOutputDistribution(csvfiletoplotIn,csvfiletoplotOut,mem): + useRadians=0 + useHalfFloats=0 + + dataFile = "body" + #This will be get populated using the --config argument + configuration = [] + configurationPath="" + + + + if (len(sys.argv)>1): + #print('Argument List:', str(sys.argv)) + for i in range(0, len(sys.argv)): + if (sys.argv[i]=="--config"): + configurationPath=sys.argv[i+1] + configuration = parseConfiguration(configurationPath) + #setupDNNModelsUsingJSONConfiguration(configuration) + if (sys.argv[i]=="--mem"): + print("\nMemory usage ",sys.argv[i+1]); + mem=float(sys.argv[i+1]) + #----------------------------------------------- + # New 4way split + #----------------------------------------------- + if (sys.argv[i]=="--dataset"): + print("\nOverriding dataset ",dataFile," and using ",sys.argv[i+1]); + dataFile=sys.argv[i+1] + if (sys.argv[i]=="--all"): + #Don't mix this --all with the step2_OrientatonClassifier.py --all + hierarchyPartName=sys.argv[i+1] + dataFile="%s_all" % hierarchyPartName + outputDirectoryForTrainedModels="step0_%s" % dataFile + if (sys.argv[i]=="--back"): + hierarchyPartName=sys.argv[i+1] + dataFile="%s_back" % hierarchyPartName + outputDirectoryForTrainedModels="step0_%s" % dataFile + if (sys.argv[i]=="--front"): + hierarchyPartName=sys.argv[i+1] + dataFile="%s_front" % hierarchyPartName + outputDirectoryForTrainedModels="step0_%s" % dataFile + if (sys.argv[i]=="--left"): + hierarchyPartName=sys.argv[i+1] + dataFile="%s_left" % hierarchyPartName + outputDirectoryForTrainedModels="step0_%s" % dataFile + if (sys.argv[i]=="--right"): + hierarchyPartName=sys.argv[i+1] + dataFile="%s_right" % hierarchyPartName + outputDirectoryForTrainedModels="step0_%s" % dataFile + #----------------------------------------------- + + + #Resolve PCA name.. :( + x = configuration['doPCA'].split("$",1) + if (len(x)>0): + #If there is a $ character this is our place holder to autocomplete our dataFile + print("PCA filename is resolved from ",configuration['doPCA']) + print(" to ") + configuration['doPCA']=x[0]+dataFile+".pca" + print(configuration['doPCA']) + + + numberOfChannelsPerNSDMElement=2 + if (configuration['NSDMAlsoUseAlignmentAngles']==1): + numberOfChannelsPerNSDMElement=1 + print("Number of Channels Per NSDM element ",numberOfChannelsPerNSDMElement) + + groundTruth = readGroundTruthFile( + configuration, + csvfiletoplotIn, + csvfiletoplotIn, + csvfiletoplotOut, + mem, + numberOfChannelsPerNSDMElement, + useRadians, + useHalfFloats + ) + #---------------------------------------------------------- + data = np.empty(len(groundTruth['out'])) + for z in range(0,len(groundTruth['labelOut'])): + data=splitNumpyArray(groundTruth['out'],z) + if (z<3): + np.multiply(data,10) + plotDistribution(z,groundTruth['labelOut'][z],data) + #---------------------------------------------------------- + data = np.empty(len(groundTruth['in'])) + for z in range(0,len(groundTruth['labelIn'])): + data=splitNumpyArray(groundTruth['in'],z) + plotDistribution(z,groundTruth['labelIn'][z],data) + #---------------------------------------------------------- + + + + +def doRun(): + label = "nolabel" + if (len(sys.argv)==3) : + #========================================== + if (sys.argv[1]=="--2d"): + print(sys.argv[0]," --2d your.csv") + labelSplit = sys.argv[2].split('.',1) + label = labelSplit[0] + from readCSV import readCSVFile + data = readCSVFile(sys.argv[2],1.0,0) + draw2D(data) + sys.exit(0) + #========================================== + elif (sys.argv[1]=="--simple"): + print(sys.argv[0]," --simple your.csv") + labelSplit = sys.argv[2].split('.',1) + label = labelSplit[0] + + from readCSV import readCSVFile + data = readCSVFile(sys.argv[2],1.0,0) + plotCovariance(data['body']) + #USE : python3 plotCSV.py --simple loss.csv | grep Stats > lossStats.csv to create stats csv file! + print("Stats,Output,Median,Mean,Std,Var,Samples,Min,Max") + + for z in range(0,len(data['label'])): + print("Plotting Column ",data['label'][z]) + specificData=splitNumpyArray(data['body'],z) + plotDistribution(z,'%s-%s' % (label,data['label'][z]),specificData) + + sys.exit(0) + #========================================== + + + if (len(sys.argv)<5) : + print("plotCSV.py: Please rerun using a parameter of the CSV file you want to run..") + print("e.g.") + print(sys.argv[0]," --config dataset/lhand_configuration.json dataset/generated/2d_lhand_all.csv dataset/generated/bvh_lhand_all.csv --all lhand") + print(" or ") + print(sys.argv[0]," --config dataset/upperbody_configuration.json dataset/generated/2d_upperbody_all.csv dataset/generated/bvh_upperbody_all.csv --all upperbody ") + print(" or ") + print(sys.argv[0]," --config dataset/face_configuration.json dataset/generated/2d_face_all.csv dataset/generated/bvh_face_all.csv --all face") + print(" or ") + print(sys.argv[0]," --simple your.csv") + sys.exit(0) + plotTestOutputDistribution(sys.argv[3],sys.argv[4],1.0) + + +if __name__== "__main__": + doRun() diff --git a/src/python/mnet4/principleComponentAnalysis.py b/src/python/mnet4/principleComponentAnalysis.py new file mode 100755 index 0000000..bc7b960 --- /dev/null +++ b/src/python/mnet4/principleComponentAnalysis.py @@ -0,0 +1,1068 @@ +#!/usr/bin/python3 +""" +Author : "Ammar Qammaz" +Copyright : "2022 Foundation of Research and Technology, Computer Science Department Greece, See license.txt" +License : "FORTH" +""" + +import sys +import os +import numpy as np + +#python3 principleComponentAnalysisTester.py --config dataset/body_configuration.json --mem 1000 --highlight 4 --mode 3 +#python3 principleComponentAnalysisTool.py --config dataset/body_configuration.json --all body --mem 1000 --show --type sparsepca + + +def visualizeSKLearnResults(data,numberOfDimensions,principalComponents,label): + import matplotlib.pyplot as plt + fig = plt.figure(figsize = (8,8)) + fig.set_size_inches(19.2, 10.8, forward=True) + + ax1 = fig.add_subplot(2, 1, 1,projection='3d') + ax2 = fig.add_subplot(2, 1, 2) + #ax = fig.add_subplot(1,1,1) + + ax1.set_xlabel('Principal Component 1', fontsize = 15) + ax1.set_ylabel('Principal Component 2', fontsize = 15) + ax1.set_zlabel('Principal Component 3', fontsize = 15) + ax1.set_title('%u component PCA %s '%(numberOfDimensions,label), fontsize = 20) + + ax1.scatter(principalComponents[:,0], + principalComponents[:,1], + principalComponents[:,2], + c = principalComponents[:,3] + #,s = principalComponents[:,3] + ) + + + #print("PCA variange ration : ",pca.explained_variance_ratio_) + PC_values = np.arange(numberOfDimensions) + 1 + ax2.plot(PC_values,PC_values, 'o-', linewidth=2, color='blue') + ax2.set_title('Scree Plot %s'%label) + ax2.set_xlabel('Principal Component') + ax2.set_ylabel('Variance Explained') + plt.show() + sys.exit(0) + + +""" +PCA using SK Learn to make sure there are no errors on my numpy implementation +""" +def doPCAUsingSKLearn(data,label): + from sklearn.decomposition import PCA + + numberOfDimensions = 20 + pca = PCA(n_components=numberOfDimensions) + + # Standardizing the features + from sklearn.preprocessing import StandardScaler + data = StandardScaler().fit_transform(data) + + principalComponents = pca.fit_transform(data) + #print(principalComponents) + + visualizeSKLearnResults(data,numberOfDimensions,principalComponents,label) + + + + + +""" +SparsePCA using SK Learn +""" +def doSparsePCAUsingSKLearn(data,label): + from sklearn.decomposition import SparsePCA + + numberOfDimensions = 20 + pca = SparsePCA(n_components=numberOfDimensions, random_state=0) + + # Standardizing the features + from sklearn.preprocessing import StandardScaler + data = StandardScaler().fit_transform(data) + + principalComponents = pca.fit_transform(data) + #print(principalComponents) + + visualizeSKLearnResults(data,numberOfDimensions,principalComponents,label) + + + + + + +""" +IncrementalPCA using SK Learn +""" +def doIncrementalPCAUsingSKLearn(data,label): + from sklearn.decomposition import IncrementalPCA + + numberOfDimensions = 20 + pca = IncrementalPCA(n_components=numberOfDimensions) + + # Standardizing the features + from sklearn.preprocessing import StandardScaler + data = StandardScaler().fit_transform(data) + + principalComponents = pca.fit_transform(data) + #print(principalComponents) + + visualizeSKLearnResults(data,numberOfDimensions,principalComponents,label) + + + +""" +NMF using SK Learn +""" +def doNMFUsingSKLearn(data,label): + from sklearn.decomposition import NMF + + numberOfDimensions = 20 + pca = NMF(n_components=numberOfDimensions) + + # Standardizing the features + from sklearn.preprocessing import StandardScaler + data = StandardScaler().fit_transform(data) + + principalComponents = pca.fit_transform(data) + #print(principalComponents) + + visualizeSKLearnResults(data,numberOfDimensions,principalComponents,label) + + + +""" +IncrementalPCA using SK FastICA +""" +def doFastICAUsingSKLearn(data,label): + from sklearn.decomposition import FastICA + + numberOfDimensions = 20 + pca = FastICA(n_components=numberOfDimensions) + + # Standardizing the features + from sklearn.preprocessing import StandardScaler + data = StandardScaler().fit_transform(data) + + principalComponents = pca.fit_transform(data) + #print(principalComponents) + + visualizeSKLearnResults(data,numberOfDimensions,principalComponents,label) + + +""" +DictionaryLearning using SK +""" +def doDictionaryLearningUsingSKLearn(data,label): + from sklearn.decomposition import DictionaryLearning + + numberOfDimensions = 20 + pca = DictionaryLearning(n_components=numberOfDimensions) + + # Standardizing the features + from sklearn.preprocessing import StandardScaler + data = StandardScaler().fit_transform(data) + + principalComponents = pca.fit_transform(data) + #print(principalComponents) + + visualizeSKLearnResults(data,numberOfDimensions,principalComponents,label) + + +""" +DictionaryLearning using SK +""" +def doFactorAnalysisUsingSKLearn(data,label): + from sklearn.decomposition import FactorAnalysis + + numberOfDimensions = 20 + pca = FactorAnalysis(n_components=numberOfDimensions) + + # Standardizing the features + from sklearn.preprocessing import StandardScaler + data = StandardScaler().fit_transform(data) + + principalComponents = pca.fit_transform(data) + #print(principalComponents) + + visualizeSKLearnResults(data,numberOfDimensions,principalComponents,label) + + + + +""" +LatentDirichletAllocation using SK +""" +def doLatentDirichletAllocationUsingSKLearn(data,label): + from sklearn.decomposition import LatentDirichletAllocation + + numberOfDimensions = 20 + pca = LatentDirichletAllocation(n_components=numberOfDimensions) + + # Standardizing the features + from sklearn.preprocessing import StandardScaler + data = StandardScaler().fit_transform(data) + + principalComponents = pca.fit_transform(data) + #print(principalComponents) + + visualizeSKLearnResults(data,numberOfDimensions,principalComponents,label) + + + +""" +TruncatedSVD using SK +""" +def doTruncatedSVDUsingSKLearn(data,label): + from sklearn.decomposition import TruncatedSVD + + numberOfDimensions = 20 + pca = TruncatedSVD(n_components=numberOfDimensions) + + # Standardizing the features + from sklearn.preprocessing import StandardScaler + data = StandardScaler().fit_transform(data) + + principalComponents = pca.fit_transform(data) + #print(principalComponents) + import pickle + with open("dump.pkl",'wb') as file: + pickle.dump(pca,file) + + visualizeSKLearnResults(data,numberOfDimensions,principalComponents,label) + + +def calculateStandardDeviationInPlace(data): + #Welford's Algorithm + #https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance + n = 0 + mean = 0.0 + M2 = 0.0 + for row in data: + for x in row: + n += 1 + delta = x - mean + mean += delta/n + M2 += delta*(x - mean) + return np.sqrt(M2 / (n-1)) + + +from tools import calculateStandardDeviationInPlaceKnowingMean +""" +#Import optimized version instead of this code.. +def calculateStandardDeviationInPlaceKnowingMean(data,mean): + N = data.shape[0] * data.shape[1] + if (N==0): + return np.float32(float("NaN")) + #---------------------------------- + M2 = 0.0 + for row in data: + for x in row: + delta = x - mean + M2 += delta**2 + return np.sqrt(M2 / N) +""" + +def convertStandardDeviationToVariance(std): + return std ** 2 + +def test_calculate_standard_deviation(X): + success = 0 + for i in range(X): + # Generate random 2D numpy array + arr = np.random.rand(100, 100) + + # Calculate mean of array + mean = np.mean(arr) + + # Calculate standard deviation using custom function + std_custom = calculateStandardDeviationInPlaceKnowingMean(arr, mean) + + # Calculate standard deviation using numpy.std() function + std_np = np.std(arr) + + # Check that the two standard deviations are close + assert np.isclose(std_custom, std_np) + success += np.isclose(std_custom, std_np) + return (success==X) + +""" +Debug function to check the stats per column for our input +""" +def getStatsPerColumn(NSxM): + print("NSxM is ",NSxM.shape[0]," x ",NSxM.shape[1]) + #NSxM.tofile('dump.csv', sep=',') + + for column in range(0,NSxM.shape[1]): + singleNSxMElement = splitNumpyArray(NSxM,column,1,0) + median = np.median(singleNSxMElement) + mean = np.mean(singleNSxMElement) + #std = np.std(singleNSxMElement) + std = calculateStandardDeviationInPlaceKnowingMean(singleNSxMElement,mean) + #var = np.var(singleNSxMElement) + var = convertStandardDeviationToVariance(std) + print("Element ",column,"/",NSxM.shape[1]," => Median is ",median,"Mean is ",mean," Std is ",std," Var is ",var) + + sys.exit(0) + +class PCA(): + def __init__(self, + inputData:np.array=np.array([]), + savedFile:str="", + decompositionType:str="pca" + ): + self.mean = 0.0 + self.std = 1.0 + self.eigenvalues = np.array([]) + self.eigenvectors = np.array([]) + self.proportional = list() + self.cumulative = list() + self.numberOfSamplesFittedOn = 0 + self.expectedInputs = 0 + self.decompositionType = decompositionType + self.trackedFiles = list() #<- all needed files should be tracked here + + if (savedFile!=""): + self.load(savedFile) + elif inputData.size != 0: + self.fit(inputData) + else: + print("No PCA input given..!") + + def ok(self): + return self.numberOfSamplesFittedOn!=0 + + def getNumberOfExpectedSamples(self): + #return len(self.eigenvalues) + return np.size(self.eigenvalues, axis = 0) + + def fit(self,data): + #doPCAUsingSKLearn(data,"Test") + #getStatsPerColumn(data) + #print(data) + if (self.decompositionType!="pca"): + print("Handle things using sk-learn") + if (self.decompositionType=="skpca"): + doPCAUsingSKLearn(data,"sk PCA Test") + if (self.decompositionType=="sparsepca"): + doSparsePCAUsingSKLearn(data,"SparsePCA Test") + if (self.decompositionType=="incrementalpca"): + doIncrementalPCAUsingSKLearn(data,"IncrementalPCA Test") + if (self.decompositionType=="fastica"): + doFastICAUsingSKLearn(data,"FastICA Test") + if (self.decompositionType=="nmf"): + doNMFUsingSKLearn(data,"NMF Test") + if (self.decompositionType=="dictionary"): + doDictionaryLearningUsingSKLearn(data,"DictionaryLearning Test") + if (self.decompositionType=="factoranalysis"): + doFactorAnalysisUsingSKLearn(data,"Factor Analysis Test") + if (self.decompositionType=="dirichlet"): + doLatentDirichletAllocationUsingSKLearn(data,"Factor Analysis Test") + if (self.decompositionType=="svd"): + doTruncatedSVDUsingSKLearn(data,"Factor Analysis Test") + + + + self.numberOfSamplesFittedOn = data.shape[0] + + print("Doing PCA fit on ",self.numberOfSamplesFittedOn," samples") + print(" please wait (this might take a while) .. ") + + #Standardize data + #------------------------------------------------------------------------------------------------- + self.mean = data.mean() + #data = data - self.mean #Apparently in python this allocates a new array.. (!?) + data -= self.mean #Do subtraction in place + # Normalize + #self.std = data.std() #<- this apparently does a x = asanyarray(arr - arrmean) inside that in case of large data doubles memory usage! + self.std = calculateStandardDeviationInPlaceKnowingMean(data,self.mean) # <- in place STD calculation + if (self.std!=0.0): + #data = data / self.std #Apparently in python this allocates a new array.. (!?) + np.divide(data,self.std , out = data) #<- attempt to divide in place ? + #print('Data Mean : ',self.mean,'STD: ',self.std) + #------------------------------------------------------------------------------------------------- + + #Take the matrix, transpose it, and multiply the transposed matrix. This is the covariance matrix. + covarianceMatrix = np.dot(data.T,data) + + #Get an array of computed eigenvalues and a matrix whose columns are the normalized eigenvectors corresponding to the eigenvalues in that order. + #In this step it is important to make sure that the eigenvalues and its eigenvectors are sorted in descending order (from largest to smallest). Sort the eigenvalues and then the eigenvectors, accordingly. + self.eigenvalues, self.eigenvectors = np.linalg.eig(covarianceMatrix) + + #Sort eigenvectors according to eigenvalues + idx = self.eigenvalues.argsort()[::-1] + self.eigenvalues = self.eigenvalues[idx] + self.eigenvectors = self.eigenvectors[:,idx] + + #Assign P to the matrix of eigenvectors and D to the diagonal matrix with eigenvalues on the diagonal and values of zero everywhere else. + #The eigenvalues on the diagonal of D will be associated with the corresponding column in P. + D = np.diag(self.eigenvalues) + P = self.eigenvectors + + self.expectedInputs = len(self.eigenvalues) + + #1. Calculate the proportion of variance explained by each feature + sumOfEigenvalues = np.sum(self.eigenvalues) + self.proportional = [i/sumOfEigenvalues for i in self.eigenvalues] + #2. Calculate the cumulative variance + self.cumulative = [np.sum(self.proportional[:i+1]) for i in range(len(self.proportional))] + + def transform(self,data,selectedPCADimensions=0): + if (self.numberOfSamplesFittedOn==0): + print("Can't transform input with no PCA loaded ..") + return data + #Normalize input data + #data = data - self.mean #Apparently in python this allocates a new array.. (!?) + data -= self.mean + if (self.std!=0.0): + #data = data / self.std #Apparently in python this allocates a new array.. (!?) + np.divide(data,self.std,out=data) #<- attempt to divide in place ? + #Do transform.. + if (selectedPCADimensions==0): + #Transform using all PCA components + return np.dot(data,self.eigenvectors) + else: + #print("Selected Dims are ",selectedPCADimensions) + #print("data is ",data.shape[0]," x ",data.shape[1]) + #print("eigenvectors is ",self.eigenvectors.shape[0]," x ",self.eigenvectors.shape[1]) + result = data.dot(self.eigenvectors[:,:selectedPCADimensions]) + #print("result is ",result.shape[0]," x ",result.shape[1]) + return result + + def save(self,filename): + print("Saving PCA to ",filename) + outputDict = dict() + #------------------------------------------ + outputDict["numberOfSamplesFittedOn"]= str(self.numberOfSamplesFittedOn) + outputDict["expectedInputs"] = str(self.expectedInputs) + outputDict["mean"] = str(self.mean) + outputDict["std"] = str(self.std) + outputDict["eigenvalues"] = list() + outputDict["eigenvectors"] = list() + outputDict["scree_proportion"] = list() + outputDict["scree_cumulative"] = list() + #------------------------------------------ + for v in range(0,len(self.proportional)): + outputDict["scree_proportion"].append(str(self.proportional[v])) + outputDict["scree_cumulative"].append(str(self.cumulative[v])) + #------------------------------------------ + print("eigenvalues ",self.eigenvalues.shape[0]) + for v in range(0,self.eigenvalues.shape[0]): + outputDict["eigenvalues"].append(str(self.eigenvalues[v])) + #------------------------------------------ + print("eigenvectors ",self.eigenvectors.shape[0]," x ",self.eigenvectors.shape[1]) + for r in range(0,self.eigenvectors.shape[0]): + thisRow = list() + for c in range(0,self.eigenvectors.shape[1]): + thisRow.append(str(self.eigenvectors[r,c])) + outputDict["eigenvectors"].append(thisRow) + #------------------------------------------ + import json + json_obj = json.dumps(outputDict) + self.trackedFiles = list() + self.trackedFiles.append(filename) + file = open(filename,'w',encoding="utf-8") + file.write(json_obj) + file.close() + + def load(self,filename): + OKGREEN = '\033[92m' + FAIL = '\033[91m' + ENDC = '\033[0m' + print("Loading PCA from ",filename) + if (os.path.isfile(filename)): + self.trackedFiles = list() + self.trackedFiles.append(filename) + import json + file = open(filename,'r',encoding="utf-8") + data = json.load(file) + #----------------------------------------------------- + self.numberOfSamplesFittedOn = int(data["numberOfSamplesFittedOn"]) + self.expectedInputs = int(data["expectedInputs"]) + self.mean = float(data["mean"]) + self.std = float(data["std"]) + #----------------------------------------------------- + numberOfEigenValues = len(data["eigenvalues"]) + print("Eigen values = ",numberOfEigenValues) + self.eigenvalues = np.full([numberOfEigenValues],fill_value=0,dtype=np.complex_,order='C') + for i in range(0,numberOfEigenValues): + self.eigenvalues[i] = complex(data["eigenvalues"][i]) + #----------------------------------------------------- + numberOfEigenVectors = len(data["eigenvectors"]) + print("Eigen vectors = ",numberOfEigenVectors) + self.eigenvectors = np.full([numberOfEigenVectors,numberOfEigenVectors],fill_value=0,dtype=np.complex_,order='C') + for r in range(0,numberOfEigenVectors): + for c in range(0,numberOfEigenVectors): + self.eigenvectors[r,c] = complex(data["eigenvectors"][r][c]) + #----------------------------------------------------- + file.close() + print(OKGREEN,"Success Loading PCA from ",filename,ENDC) + return True + print(FAIL,"Failed Loading PCA from ",filename,ENDC) + return False + + + def visualize(self,data,saveToFile="",onlyScreePlotNDimensions=0,label="PCA",colors=list(),colorLabel="Highlighting PC-4",viewAzimuth=45,viewElevation=45,showScree=1): + import matplotlib.pyplot as plt + + font = { + 'family' : 'normal', + 'weight' : 'bold', + 'size' : 28} + + plt.rc('font', **font) + plt.rc('xtick', labelsize=15) + plt.rc('ytick', labelsize=15) + # === Plot ========================================================================= + fig = plt.figure() + fig.set_size_inches(19.2, 10.8, forward=True) + + if (showScree==1): + ax2 = fig.add_subplot(1, 2, 1) + ax1 = fig.add_subplot(1, 2, 2,projection='3d') + else: + ax1 = fig.add_subplot(1, 1, 1,projection='3d') + #=================================================================================== + + #Number of PCA components to plot on first plot (our plot is 3D so max is 4 if we dont have a color ..! ) + keepNDimensions = 3 + if (len(colors)==0): + keepNDimensions = 4 + + #Do transform of our input using the PCA dimensions as new basis + #=================================================================================== + transformedData = self.transform(data,selectedPCADimensions=keepNDimensions).real + #=================================================================================== + + if (len(colors)==0): + colors = transformedData[:,3] + colorLabel = "highlighting PC-4" + else: + print("Using provided set of colorValues") + keepNDimensions = 3 + + #If there is no limit on Scree plot dimensions then plot all + if (onlyScreePlotNDimensions==0): + onlyScreePlotNDimensions = len(eigenvalues) + #=================================================================================== + plottedEigenValues = self.eigenvalues + plottedEigenValues=list() + for i in range(0,onlyScreePlotNDimensions): + plottedEigenValues.append(self.eigenvalues[i]) + #=================================================================================== + #1. Calculate the proportion of variance explained by each feature + sum_eigenvalues = np.sum(plottedEigenValues) + prop_var = [i/sum_eigenvalues for i in plottedEigenValues] + #2. Calculate the cumulative variance + cum_var = [np.sum(prop_var[:i+1]) for i in range(len(prop_var))] + #=================================================================================== + + ax1.view_init(viewAzimuth,viewElevation) + #=================================================================================== + ax1.scatter(transformedData[:,0],transformedData[:,1],transformedData[:,2],c=colors) + #=================================================================================== + + # Adding title, xlabel and ylabel + ax1.set_title('PCA %s %s '%(label,colorLabel)) # Title of the plot + ax1.set_xlabel('PC-1 (%0.2f %%) '% (100.0*float(prop_var[0])),labelpad=30) # X-Label + ax1.set_ylabel('PC-2 (%0.2f %%) '% (100.0*float(prop_var[1])),labelpad=30) # Y-Label + ax1.set_zlabel('PC-3 (%0.2f %%) '% (100.0*float(prop_var[2])),labelpad=30) # Z-Label + #ax1.tick_params(axis='x', pad=5) #fine tune numbers of plot + #=================================================================================== + #=================================================================================== + #=================================================================================== + if (showScree==1): + # Plot scree plot from PCA + x_labels = ['PC{}'.format(i+1) for i in range(len(prop_var))] + ax2.plot(x_labels, prop_var, marker='o', markersize=6, color='skyblue', linewidth=2, label='Proportion of variance') + ax2.plot(x_labels, cum_var, marker='o', color='orange', linewidth=2, label="Cumulative variance") + ax2.legend() + ax2.set_title('Scree plot %s '%label) + ax2.set_xlabel('Principal components') + ax2.set_ylabel('Proportion of variance') + #=================================================================================== + #=================================================================================== + #=================================================================================== + + plt.subplots_adjust(left=0.05, right=0.95, top=0.95, bottom=0.08) + + if (saveToFile!=""): + fig.savefig(saveToFile) + else: + plt.show() + + +if __name__ == '__main__': + print("principleComponentAnalysis.py is a library") + + print("Test StD implementation against numpy : ") + print(test_calculate_standard_deviation(1000)) + + + pca = PCA(savedFile='dataset/combinedModel/mocapnet4/mode1/1.0/step1_upperbody_all/upperbody_all.pca') + sample = list() + sample.append(0.470052) #0 + sample.append(0.452315) #1 + sample.append(1.000000) #2 + sample.append(0.479688) #3 + sample.append(0.239352) #4 + sample.append(1.000000) #5 + sample.append(0.520312) #6 + sample.append(0.185185) #7 + sample.append(1.000000) #8 + sample.append(0.519271) #9 + sample.append(0.173148) #10 + sample.append(1.000000) #11 + sample.append(0.000000) #12 + sample.append(0.000000) #13 + sample.append(0.000000) #14 + sample.append(0.450000) #15 + sample.append(0.238889) #16 + sample.append(1.000000) #17 + sample.append(0.422917) #18 + sample.append(0.337037) #19 + sample.append(1.000000) #20 + sample.append(0.000000) #21 + sample.append(0.000000) #22 + sample.append(0.000000) #23 + sample.append(0.509375) #24 + sample.append(0.239815) #25 + sample.append(1.000000) #26 + sample.append(0.500000) #27 + sample.append(0.362963) #28 + sample.append(1.000000) #29 + sample.append(0.000000) #30 + sample.append(0.000000) #31 + sample.append(0.000000) #32 + sample.append(0.652332) #33 + sample.append(0.283472) #34 + sample.append(0.547378) #35 + sample.append(0.213181) #36 + sample.append(0.536087) #37 + sample.append(0.077135) #38 + sample.append(0.214366) #39 + sample.append(0.509478) #40 + sample.append(0.095500) #41 + sample.append(0.029691) #42 + sample.append(0.167750) #43 + sample.append(0.522894) #44 + sample.append(0.141564) #45 + sample.append(0.065052) #46 + sample.append(0.050908) #47 + sample.append(0.124542) #48 + sample.append(0.540789) #49 + sample.append(0.190115) #50 + sample.append(0.112984) #51 + sample.append(0.101816) #52 + sample.append(0.050908) #53 + sample.append(0.383942) #54 + sample.append(0.270394) #55 + sample.append(0.307847) #56 + sample.append(0.277424) #57 + sample.append(0.248705) #58 + sample.append(0.254739) #59 + sample.append(0.270394) #60 + sample.append(0.652332) #61 + sample.append(0.000000) #62 + sample.append(0.547378) #63 + sample.append(0.536087) #64 + sample.append(0.509478) #65 + sample.append(0.522894) #66 + sample.append(0.540789) #67 + sample.append(0.270394) #68 + sample.append(0.212132) #69 + sample.append(0.440391) #70 + sample.append(0.248585) #71 + sample.append(0.182055) #72 + sample.append(0.155387) #73 + sample.append(0.126341) #74 + sample.append(0.113851) #75 + sample.append(0.174184) #76 + sample.append(0.440391) #77 + sample.append(0.212132) #78 + sample.append(0.678899) #79 + sample.append(0.152507) #80 + sample.append(0.143071) #81 + sample.append(0.171002) #82 + sample.append(0.175392) #83 + sample.append(0.193561) #84 + sample.append(0.419609) #85 + sample.append(0.678899) #86 + sample.append(0.300000) #87 + sample.append(0.216108) #88 + sample.append(0.563004) #89 + sample.append(0.067397) #90 + sample.append(0.029691) #91 + sample.append(0.059382) #92 + sample.append(0.087379) #93 + sample.append(0.130105) #94 + sample.append(0.306329) #95 + sample.append(0.563004) #96 + sample.append(0.209537) #97 + sample.append(0.116016) #98 + sample.append(0.154849) #99 + sample.append(0.587831) #100 + sample.append(0.129067) #101 + sample.append(0.066885) #102 + sample.append(0.083048) #103 + sample.append(0.069538) #104 + sample.append(0.089203) #105 + sample.append(0.321928) #106 + sample.append(0.587831) #107 + sample.append(0.192741) #108 + sample.append(0.107269) #109 + sample.append(0.061752) #110 + sample.append(0.094237) #111 + sample.append(0.617853) #112 + sample.append(0.190791) #113 + sample.append(0.125269) #114 + sample.append(0.133770) #115 + sample.append(0.098298) #116 + sample.append(0.081326) #117 + sample.append(0.347944) #118 + sample.append(0.617853) #119 + sample.append(0.194984) #120 + sample.append(0.131074) #121 + sample.append(0.123504) #122 + sample.append(0.061752) #123 + sample.append(0.348961) #124 + sample.append(0.308926) #125 + sample.append(0.269400) #126 + sample.append(0.236866) #127 + sample.append(0.208076) #128 + sample.append(0.214721) #129 + sample.append(0.232589) #130 + sample.append(0.040663) #131 + sample.append(0.308926) #132 + sample.append(0.143338) #133 + sample.append(0.378955) #134 + sample.append(0.265854) #135 + sample.append(0.281502) #136 + sample.append(0.308926) #137 + sample.append(0.652332) #138 + sample.append(0.000000) #139 + sample.append(0.547378) #140 + sample.append(0.536087) #141 + sample.append(0.509478) #142 + sample.append(0.522894) #143 + sample.append(0.540789) #144 + sample.append(0.270394) #145 + sample.append(0.000000) #146 + sample.append(0.440391) #147 + sample.append(0.678899) #148 + sample.append(0.563004) #149 + sample.append(0.587831) #150 + sample.append(0.617853) #151 + sample.append(0.308926) #152 + sample.append(0.106590) #153 + sample.append(0.587454) #154 + sample.append(0.178302) #155 + sample.append(0.106590) #156 + sample.append(0.109798) #157 + sample.append(0.069458) #158 + sample.append(0.052693) #159 + sample.append(0.317531) #160 + sample.append(0.587454) #161 + sample.append(0.166465) #162 + sample.append(0.146195) #163 + sample.append(0.111492) #164 + sample.append(0.053520) #165 + sample.append(0.030413) #166 + sample.append(0.278528) #167 + sample.append(0.587454) #168 + sample.append(3.096379) #169 + sample.append(0.651328) #170 + sample.append(5.955375) #171 + sample.append(6.084674) #172 + sample.append(-0.059619) #173 + sample.append(0.048326) #174 + sample.append(0.234845) #175 + sample.append(0.585668) #176 + sample.append(0.651328) #177 + sample.append(0.686142) #178 + sample.append(5.398531) #179 + sample.append(5.946909) #180 + sample.append(5.904308) #181 + sample.append(5.806487) #182 + sample.append(0.529019) #183 + sample.append(0.651328) #184 + sample.append(6.084674) #185 + sample.append(3.792921) #186 + sample.append(3.694669) #187 + sample.append(4.237240) #188 + sample.append(4.096264) #189 + sample.append(4.071058) #190 + sample.append(3.975877) #191 + sample.append(3.886222) #192 + sample.append(3.886222) #193 + sample.append(0.000000) #194 + sample.append(3.776154) #195 + sample.append(4.110508) #196 + sample.append(4.119073) #197 + sample.append(4.020748) #198 + sample.append(3.931176) #199 + sample.append(3.931177) #200 + sample.append(0.000000) #201 + sample.append(3.929643) #202 + sample.append(2.813782) #203 + sample.append(1.095648) #204 + sample.append(3.325809) #205 + sample.append(2.449419) #206 + sample.append(2.176756) #207 + sample.append(2.363436) #208 + sample.append(2.456802) #209 + sample.append(1.402459) #210 + sample.append(1.095648) #211 + sample.append(2.003560) #212 + sample.append(3.639651) #213 + sample.append(2.840933) #214 + sample.append(2.875062) #215 + sample.append(2.887117) #216 + sample.append(1.448436) #217 + sample.append(1.095648) #218 + sample.append(2.736625) #219 + sample.append(2.943081) #220 + sample.append(0.954672) #221 + sample.append(5.591012) #222 + sample.append(3.255518) #223 + sample.append(1.401905) #224 + sample.append(2.261431) #225 + sample.append(2.461843) #226 + sample.append(1.159315) #227 + sample.append(0.954672) #228 + sample.append(1.819818) #229 + sample.append(4.163903) #230 + sample.append(4.543498) #231 + sample.append(3.371371) #232 + sample.append(3.151165) #233 + sample.append(1.170683) #234 + sample.append(0.954672) #235 + sample.append(2.943081) #236 + sample.append(3.081973) #237 + sample.append(0.929466) #238 + sample.append(5.318349) #239 + sample.append(4.543498) #240 + sample.append(3.256703) #241 + sample.append(2.719052) #242 + sample.append(2.719052) #243 + sample.append(1.130633) #244 + sample.append(0.929466) #245 + sample.append(1.897446) #246 + sample.append(4.228285) #247 + sample.append(4.543498) #248 + sample.append(3.707124) #249 + sample.append(3.371372) #250 + sample.append(1.137976) #251 + sample.append(0.929466) #252 + sample.append(3.216783) #253 + sample.append(3.189919) #254 + sample.append(0.834284) #255 + sample.append(5.505028) #256 + sample.append(5.403023) #257 + sample.append(5.860644) #258 + sample.append(3.256703) #259 + sample.append(2.719052) #260 + sample.append(0.929466) #261 + sample.append(0.834284) #262 + sample.append(1.597937) #263 + sample.append(4.522202) #264 + sample.append(5.142685) #265 + sample.append(4.364796) #266 + sample.append(3.691174) #267 + sample.append(0.898620) #268 + sample.append(0.834284) #269 + sample.append(3.574276) #270 + sample.append(3.376438) #271 + sample.append(0.744629) #272 + sample.append(5.598395) #273 + sample.append(5.603435) #274 + sample.append(5.860644) #275 + sample.append(5.860644) #276 + sample.append(3.166879) #277 + sample.append(0.744629) #278 + sample.append(0.744629) #279 + sample.append(1.183479) #280 + sample.append(4.781023) #281 + sample.append(5.403024) #282 + sample.append(4.970201) #283 + sample.append(4.234641) #284 + sample.append(0.684905) #285 + sample.append(0.744629) #286 + sample.append(4.391370) #287 + sample.append(3.727261) #288 + sample.append(0.744629) #289 + sample.append(4.544052) #290 + sample.append(4.300908) #291 + sample.append(4.272226) #292 + sample.append(4.071058) #293 + sample.append(3.886222) #294 + sample.append(3.166879) #295 + sample.append(0.744629) #296 + sample.append(3.604797) #297 + sample.append(4.254323) #298 + sample.append(4.324193) #299 + sample.append(4.133639) #300 + sample.append(3.966107) #301 + sample.append(4.234641) #302 + sample.append(0.744629) #303 + sample.append(3.966615) #304 + sample.append(3.792921) #305 + sample.append(0.000000) #306 + sample.append(4.237240) #307 + sample.append(4.096264) #308 + sample.append(4.071058) #309 + sample.append(3.975877) #310 + sample.append(3.886222) #311 + sample.append(3.886222) #312 + sample.append(3.694669) #313 + sample.append(3.776154) #314 + sample.append(4.110508) #315 + sample.append(4.119073) #316 + sample.append(4.020748) #317 + sample.append(3.931176) #318 + sample.append(3.931177) #319 + sample.append(0.000000) #320 + sample.append(3.929643) #321 + sample.append(3.827735) #322 + sample.append(0.634561) #323 + sample.append(5.145152) #324 + sample.append(4.961411) #325 + sample.append(5.039039) #326 + sample.append(4.739530) #327 + sample.append(4.325072) #328 + sample.append(0.463204) #329 + sample.append(0.634561) #330 + sample.append(0.000000) #331 + sample.append(4.613133) #332 + sample.append(4.903871) #333 + sample.append(4.607087) #334 + sample.append(4.287396) #335 + sample.append(0.295320) #336 + sample.append(0.634561) #337 + sample.append(4.346044) #338 + sample.append(2.256939) #339 + sample.append(0.968915) #340 + sample.append(0.498058) #341 + sample.append(1.022310) #342 + sample.append(1.086692) #343 + sample.append(1.380610) #344 + sample.append(1.639430) #345 + sample.append(1.112731) #346 + sample.append(0.968915) #347 + sample.append(1.471541) #348 + sample.append(0.000000) #349 + sample.append(0.927337) #350 + sample.append(1.482404) #351 + sample.append(1.967686) #352 + sample.append(1.114843) #353 + sample.append(0.968915) #354 + sample.append(1.776775) #355 + sample.append(2.805316) #356 + sample.append(0.977481) #357 + sample.append(5.982525) #358 + sample.append(1.401905) #359 + sample.append(1.401905) #360 + sample.append(2.001093) #361 + sample.append(2.261431) #362 + sample.append(1.182600) #363 + sample.append(0.977481) #364 + sample.append(1.762278) #365 + sample.append(4.068930) #366 + sample.append(3.258445) #367 + sample.append(2.912313) #368 + sample.append(2.912314) #369 + sample.append(1.196280) #370 + sample.append(0.977481) #371 + sample.append(2.673644) #372 + sample.append(2.762715) #373 + sample.append(0.879156) #374 + sample.append(6.016655) #375 + sample.append(0.229778) #376 + sample.append(0.565532) #377 + sample.append(1.223204) #378 + sample.append(1.828609) #379 + sample.append(0.992046) #380 + sample.append(0.879156) #381 + sample.append(1.465495) #382 + sample.append(4.623997) #383 + sample.append(6.053906) #384 + sample.append(3.258445) #385 + sample.append(2.912314) #386 + sample.append(0.977481) #387 + sample.append(0.879156) #388 + sample.append(2.397369) #389 + sample.append(2.664894) #390 + sample.append(0.789584) #391 + sample.append(6.028709) #392 + sample.append(0.009572) #393 + sample.append(0.229779) #394 + sample.append(0.549582) #395 + sample.append(1.093048) #396 + sample.append(0.824514) #397 + sample.append(0.789584) #398 + sample.append(1.145804) #399 + sample.append(5.109279) #400 + sample.append(6.053906) #401 + sample.append(6.053907) #402 + sample.append(3.136574) #403 + sample.append(0.789584) #404 + sample.append(0.789584) #405 + sample.append(0.819205) #406 + sample.append(3.670611) #407 + sample.append(0.789584) #408 + sample.append(4.590029) #409 + sample.append(4.312276) #410 + sample.append(4.279569) #411 + sample.append(4.040213) #412 + sample.append(3.826498) #413 + sample.append(1.093048) #414 + sample.append(0.789584) #415 + sample.append(3.436912) #416 + sample.append(4.256435) #417 + sample.append(4.337873) #418 + sample.append(4.119073) #419 + sample.append(3.931176) #420 + sample.append(3.136574) #421 + sample.append(0.789584) #422 + sample.append(3.927943) #423 + sample.append(3.792921) #424 + sample.append(0.000000) #425 + sample.append(4.237240) #426 + sample.append(4.096264) #427 + sample.append(4.071058) #428 + sample.append(3.975877) #429 + sample.append(3.886222) #430 + sample.append(3.886222) #431 + sample.append(0.000000) #432 + sample.append(3.776154) #433 + sample.append(4.110508) #434 + sample.append(4.119073) #435 + sample.append(4.020748) #436 + sample.append(3.931176) #437 + sample.append(3.931177) #438 + sample.append(3.694669) #439 + sample.append(3.929643) #440 + sample.append(2.943082) #441 + sample.append(0.788050) #442 + sample.append(5.878218) #443 + sample.append(6.084674) #444 + sample.append(0.075190) #445 + sample.append(0.432683) #446 + sample.append(1.249777) #447 + sample.append(0.825022) #448 + sample.append(0.788050) #449 + sample.append(1.204451) #450 + sample.append(4.918368) #451 + sample.append(5.815236) #452 + sample.append(5.538962) #453 + sample.append(3.960798) #454 + sample.append(0.786350) #455 + sample.append(0.788050) #456 + sample.append(3.255518) #457 + + + + out = pca.transform(np.asarray(sample),selectedPCADimensions=210) + for i in range(0,len(out)): + print(i," = ",out[i]) + + diff --git a/src/python/mnet4/principleComponentAnalysisTool.py b/src/python/mnet4/principleComponentAnalysisTool.py new file mode 100755 index 0000000..47fd66e --- /dev/null +++ b/src/python/mnet4/principleComponentAnalysisTool.py @@ -0,0 +1,287 @@ +#!/usr/bin/python3 + +""" +Author : "Ammar Qammaz" +Copyright : "2022 Foundation of Research and Technology, Computer Science Department Greece, See license.txt" +License : "FORTH" +""" +import sys +import numpy as np + +from readCSV import parseConfiguration,splitNumpyArray,readGroundTruthFile +from DNNModel import setupDNNModelsUsingJSONConfiguration + +OKGREEN = '\033[92m' +ENDC = '\033[0m' +FAIL = '\033[91m' + +""" +Retreive the available system RAM +""" +def getRAMInformation(): + """ + Get node total memory and memory usage + """ + with open('/proc/meminfo', 'r') as mem: + ret = {} + tmp = 0 + for i in mem: + sline = i.split() + if str(sline[0]) == 'MemTotal:': + ret['total'] = int(sline[1]) + elif str(sline[0]) in ('MemFree:', 'Buffers:', 'Cached:'): + tmp += int(sline[1]) + ret['free'] = int(tmp) + ret['used'] = int(ret['total']) - int(ret['free']) + return ret + + +if __name__ == '__main__': + + print(""" +██████╗ ███████╗ ██████╗ ██████╗ ███╗ ███╗██████╗ ██████╗ ███████╗███████╗ +██╔══██╗██╔════╝██╔════╝██╔═══██╗████╗ ████║██╔══██╗██╔═══██╗██╔════╝██╔════╝ +██║ ██║█████╗ ██║ ██║ ██║██╔████╔██║██████╔╝██║ ██║███████╗█████╗ +██║ ██║██╔══╝ ██║ ██║ ██║██║╚██╔╝██║██╔═══╝ ██║ ██║╚════██║██╔══╝ +██████╔╝███████╗╚██████╗╚██████╔╝██║ ╚═╝ ██║██║ ╚██████╔╝███████║███████╗ +╚═════╝ ╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═════╝ ╚══════╝╚══════╝""") + + configuration = [] + configurationPath="" + + #Use the whole training Dataset by default + memPercentage=1.0 + + #networkCompression=1.0 + useHalfFloats = 0 + useRadians = 0 + + #I/O settings + outputDirectoryForTrainedModels = "step0FrontBody" + dataFile = "body" + testFile = "body_test" + hierarchyPartName = "body" + outputMode = "bvh" + + + # Sample call with some example parameters : + # python3 principleComponentAnalysisTool.py --config dataset/body_configuration.json --mem 1000 --highlight 3 --all body --show --mode 3 + + highlightChannel = -1 #<- default shows PC4 + batch = 0 + mode = 1 + dimensions = 0 + decompositionType = "pca" + viewA = 45 + viewB = 45 + + visualize = 0 + dumpRules = 1 + addScreePlot = 0 + + label = "not defined" + if (len(sys.argv)>1): + #print('Argument List:', str(sys.argv)) + for i in range(0, len(sys.argv)): + if (sys.argv[i]=="--config"): + configurationPath=sys.argv[i+1] + configuration = parseConfiguration(configurationPath) + setupDNNModelsUsingJSONConfiguration(configuration) + decompositionType = configuration['decompositionType'] + dimensions = int(configuration['PCADimensionsKept']) + if (configuration['precision']=="fp16"): + useHalfFloats=1 + configuration['rememberWeights']=0 + setBackendToHalfFloats() + elif (configuration['precision']!="fp32"): + print(bcolors.WARNING,"Cannot use ",configuration['precision']," precision\n",bcolors.ENDC) + if (sys.argv[i]=="--mem"): + print("\nMemory usage ",sys.argv[i+1]); + memPercentage=float(sys.argv[i+1]) + #----------------------------------------------- + # New 4way split + #----------------------------------------------- + if (sys.argv[i]=="--dataset"): + print("\nOverriding dataset ",dataFile," and using ",sys.argv[i+1]); + dataFile=sys.argv[i+1] + if (sys.argv[i]=="--all"): + #Don't mix this --all with the step2_OrientatonClassifier.py --all + hierarchyPartName=sys.argv[i+1] + dataFile="%s_all" % hierarchyPartName + outputDirectoryForTrainedModels="step0_%s" % dataFile + if (sys.argv[i]=="--back"): + hierarchyPartName=sys.argv[i+1] + dataFile="%s_back" % hierarchyPartName + outputDirectoryForTrainedModels="step0_%s" % dataFile + if (sys.argv[i]=="--front"): + hierarchyPartName=sys.argv[i+1] + dataFile="%s_front" % hierarchyPartName + outputDirectoryForTrainedModels="step0_%s" % dataFile + if (sys.argv[i]=="--left"): + hierarchyPartName=sys.argv[i+1] + dataFile="%s_left" % hierarchyPartName + outputDirectoryForTrainedModels="step0_%s" % dataFile + if (sys.argv[i]=="--right"): + hierarchyPartName=sys.argv[i+1] + dataFile="%s_right" % hierarchyPartName + outputDirectoryForTrainedModels="step0_%s" % dataFile + #----------------------------------------------- + if (sys.argv[i]=="--type"): + print("\nSet Mode to ",sys.argv[i+1]); + decompositionType=sys.argv[i+1] + configuration['decompositionType'] = decompositionType + if (sys.argv[i]=="--dims"): + print("\nSet Dimensions to ",sys.argv[i+1]); + dimensions=int(sys.argv[i+1]) + configuration['PCADimensionsKept']=str(dimensions) + if (sys.argv[i]=="--mode"): + print("\nSet Mode to ",sys.argv[i+1]); + mode=int(sys.argv[i+1]) + if (sys.argv[i]=="--highlight"): + print("\nSet Highlight to ",sys.argv[i+1]); + highlightChannel=int(sys.argv[i+1]) + if (sys.argv[i]=="--view"): + print("\nSet View to ",sys.argv[i+1],sys.argv[i+2]); + viewA=int(sys.argv[i+1]) + viewB=int(sys.argv[i+2]) + if (sys.argv[i]=="--batch"): + batch=1 + visualize = 1 + if (sys.argv[i]=="--nosave"): + dumpRules=0 + if (sys.argv[i]=="--show"): + visualize=1 + if (sys.argv[i]=="--noscree"): + addScreePlot=0 + + + if (decompositionType==""): + print(OKGREEN,"Not doing everything because no decomposition type requested",ENDC) + sys.exit(0) + + + #The default compatibility setting is the BMVC2019 2channel NSDM, however nowadays we use NSRM + numberOfChannelsPerNSDMElement=2 + if (configuration['NSDMAlsoUseAlignmentAngles']==1): + numberOfChannelsPerNSDMElement=1 + print("Number of Channels Per NSDM element ",numberOfChannelsPerNSDMElement) + + + #Hardcoded path for dataset if using CSV file + #datasetDirectory="/home/ammar/Documents/Programming/DNNTracker/DNNTracker/dataset" + #datasetDirectory="../../DNNTracker/dataset" + datasetDirectory="dataset/generated/" + + + RAMBefore = getRAMInformation() + print("Initial | free RAM ",RAMBefore['free']," KB"); + + groundTruthTrain = readGroundTruthFile( + configuration, + "%s Train data for decomposition" % (decompositionType), + "%s/2d_%s.csv" % (datasetDirectory,dataFile), + "%s/%s_%s.csv" % (datasetDirectory,outputMode,dataFile), #outputMode is either bvh or 3d + memPercentage, + numberOfChannelsPerNSDMElement, + useRadians, + useHalfFloats, + completelyDisablePCACode = 1 #If we do PCA while trying to do PCA it will be madness + ) + + RAMAfter = getRAMInformation() + print("After reading the dataset | free RAM ",RAMAfter['free']," KB"); + #---------------------------------------------------------------------------------------------------------- + startOfNSxMColumn = len(groundTruthTrain['labelInNoNSDM']) + numberOfNSxMColumns = len(groundTruthTrain['labelIn'])-startOfNSxMColumn + print("2D data are 0 to ",startOfNSxMColumn) + print("NSRM data are ",startOfNSxMColumn , " to ",numberOfNSxMColumns) + + if (mode==1): + NSxM = groundTruthTrain['in'] + label = "on 2D+NSRM data" + elif (mode==2): + NSxM = splitNumpyArray(groundTruthTrain['in'],startOfNSxMColumn,numberOfNSxMColumns,useHalfFloats) + label = "on NSRM data" + elif (mode==3): + NSxM = splitNumpyArray(groundTruthTrain['in'],0,startOfNSxMColumn,useHalfFloats) + label = "on 2D data" + elif (mode==4): + NSxM = groundTruthTrain['out'] + label = "on all BVH Data" + elif (mode==5): + onlyRotationalBVHOutputs = len(groundTruthTrain['labelOut']) - 3 + print("Will take output from 3-",onlyRotationalBVHOutputs,"/",len(groundTruthTrain['labelOut'])) + NSxM = splitNumpyArray(groundTruthTrain['out'],3,onlyRotationalBVHOutputs,useHalfFloats) + label = "on rot. BVH Data" + elif (mode==6): + onlyLimbRotationalBVHOutputs = len(groundTruthTrain['labelOut']) - 6 + print("Will take output from 6-",onlyLimbRotationalBVHOutputs,"/",len(groundTruthTrain['labelOut'])) + NSxM = splitNumpyArray(groundTruthTrain['out'],6,onlyLimbRotationalBVHOutputs,useHalfFloats) + label = "on limb rot. BVH Data" + + #---------------------------------------------------------------------------------------------------------- + RAMSplit = getRAMInformation() + print("After splitting the dataset | free RAM ",RAMSplit['free']," KB"); + del groundTruthTrain['in'] + if (not visualize): + del groundTruthTrain['out'] + RAMFree = getRAMInformation() + print("After freeing original input | free RAM ",RAMFree['free']," KB"); + + print(OKGREEN,"Will perform dimensionality reduction on ",label,ENDC) + + if (decompositionType=="pca"): + print("Attempting PCA using my code with ",dimensions," elements") + print("Our Input Shape is ",NSxM.shape[0],"x",NSxM.shape[1]) + from principleComponentAnalysis import PCA + ourDecomposition = PCA(inputData=NSxM,decompositionType=decompositionType) + for i in range(0,len(ourDecomposition.cumulative)): + thisValue = 0.0 + try: + thisValue = ourDecomposition.cumulative[i].real + except: + thisValue = ourDecomposition.cumulative[i] + #--------------------------------------------------- + if (thisValue>0.99): + print(OKGREEN,"Useless? PCA Dim #",i," - ",thisValue*100,"%",ENDC) + else: + print("PCA Dim #",i," - Explanation of data until here ",thisValue*100,"%") + if (dimensions==i): + print(FAIL," ------------- OUR LIMIT IS SET HERE (",dimensions,") ------------- ",ENDC) + else: + from dataDecomposition import Decomposition + print("Attempting Generic Decomposition ",decompositionType," with ",dimensions," elements") + print("Our Input Shape is ",NSxM.shape[0],"x",NSxM.shape[1]) + ourDecomposition = Decomposition(inputData=NSxM,decompositionType=decompositionType,selectedPCADimensions=dimensions) + + if (dumpRules): + print("Dumping rules to json file..") + x = configuration['doPCA'].split("$") + if (len(x)>0): + #If there is a $ character this is our place holder to autocomplete our dataFile + print("We resolve ",configuration['doPCA']) + print(" to ") + configuration['doPCA']=x[0]+dataFile+".pca" + print(configuration['doPCA']) + ourDecomposition.save(configuration['doPCA']) + + #Number of PCA components to plot on second plot + #----------------------------------------------------------------------------- + onlyScreePlotNDimensions=8 + if (batch==1) or (visualize): + if (highlightChannel<0): + colors = list() + colorLabel = "highlighting PC-4" + else: + colorLabel="highlighting "+ groundTruthTrain['labelOut'][highlightChannel] + colors = groundTruthTrain['out'][:,highlightChannel] + #----------------------------------------------------------------------------- + filename = "" + if (batch==1): + filename = "%s_%s_%s_%u_v%u%u.png"%(decompositionType,label,colorLabel,int(memPercentage),viewA,viewB) + filename = filename.replace(' ','_') + #----------------------------------------------------------------------------- + if (visualize): + ourDecomposition.visualize(NSxM,saveToFile=filename,onlyScreePlotNDimensions=onlyScreePlotNDimensions,label=label,colors=colors,colorLabel=colorLabel,viewAzimuth=viewA,viewElevation=viewB,showScree=addScreePlot) + + print(OKGREEN,"Finished with ",decompositionType," code!",ENDC) diff --git a/src/python/mnet4/readCSV.py b/src/python/mnet4/readCSV.py new file mode 100755 index 0000000..c2e9777 --- /dev/null +++ b/src/python/mnet4/readCSV.py @@ -0,0 +1,1302 @@ +#!/usr/bin/python3 + +""" +Author : "Ammar Qammaz" +Copyright : "2022 Foundation of Research and Technology, Computer Science Department Greece, See license.txt" +License : "FORTH" +""" + +import numpy as np +import os +import csv +import gc +import time +import array +import sys + +from NSDM import NSDMLabels,createNSDMUsingRules,performNSRMAlignment,getJoint2DXYV +from EDM import EDMLabels,createEDMUsingRules +from tools import bcolors,checkIfFileExists,getNumberOfLines,getNumberOfLinesOS,convert_bytes +from dataDecomposition import EmptyDecomposition + + +""" + typically we work with data[NUMBER_OF_SAMPLES,NUMBER_OF_COLUMNS] + calling splitNumpyArray(data,4,4,0) means get back columns 4,5,6,7 so it will return a data[NUMBER_OF_SAMPLES,4] +""" +def splitNumpyArray(data,column,columnsToTake,useHalfFloats): + #--------------------------------------------------------------------------------------------- + numberOfSamples=len(data) + #--------------------------------------------------------------------------------------------- + if (useHalfFloats): + npOutput = np.full([numberOfSamples,columnsToTake],fill_value=0,dtype=np.float16,order='C') + for outCol in range(0,columnsToTake): + for num in range(0,numberOfSamples): + npOutput[num,outCol]=np.float16(data[num,column+outCol]) + #--------------------------------------------------------------------------------------------- + else: + npOutput = np.full([numberOfSamples,columnsToTake],fill_value=0,dtype=np.float32,order='C') + for outCol in range(0,columnsToTake): + for num in range(0,numberOfSamples): + npOutput[num,outCol]=np.float32(data[num,column+outCol]) + #--------------------------------------------------------------------------------------------- + return npOutput; + + +""" + typically we work with data[NUMBER_OF_SAMPLES,NUMBER_OF_COLUMNS] + calling selectOnlySpecificColumnsOfNumpyArray(data,list,0) means get back only columns listed with 0 on the list +""" +def selectOnlySpecificColumnsOfNumpyArray(data,columnListWithSelections,useHalfFloats): + numberOfSamples=len(data) + numberOfOriginalColumns = len(data[0]) + numberOfSelectedColumns = sum(columnListWithSelections) + print("We will select ",numberOfSelectedColumns,"/",numberOfOriginalColumns," columns") + outCol=0 + + #--------------------------------------------------------------------------------------------- + if (useHalfFloats): + npOutput = np.full([numberOfSamples,numberOfSelectedColumns],fill_value=0,dtype=np.float16,order='C') + for inCol in range(0,numberOfOriginalColumns): + if (columnListWithSelections[inCol]!=0): + for num in range(0,numberOfSamples): + npOutput[num,outCol]=np.float16(data[num,outCol]) + outCol=outCol+1 + #--------------------------------------------------------------------------------------------- + else: + npOutput = np.full([numberOfSamples,numberOfSelectedColumns],fill_value=0,dtype=np.float32,order='C') + for inCol in range(0,numberOfOriginalColumns): + if (columnListWithSelections[inCol]!=0): + for num in range(0,numberOfSamples): + npOutput[num,outCol]=np.float32(data[num,outCol]) + outCol=outCol+1 + #--------------------------------------------------------------------------------------------- + return npOutput; + + +""" + Perform a series of checks on testList and trainList to make sure they correspond correctly +""" +def checkIfTestAndTrainListsAreOk(testList,trainList): + return 1 + if (len(trainList['labelIn'])!=len(trainList['in'])): + print('Mismatch of training labels in (',len(trainList['labelIn']),') and actual input (',len(trainList['in']),') ') + sys.exit(0) + if (len(trainList['labelOut'])!=len(trainList['out'])): + print('Mismatch of training labels out (',len(trainList['labelOut']),') and actual output (',len(trainList['out']),') ') + sys.exit(0) + if (len(trainList['in'])!=len(trainList['out'])): + print('Mismatch of training labels in input (',len(trainList['in']),') and output (',len(trainList['out']),') ') + sys.exit(0) + if (len(testList['labelIn'])!=len(testList['in'])): + print('Mismatch of testing labels in (',len(testList['labelIn']),') and actual input (',len(testList['in']),') ') + sys.exit(0) + if (len(testList['labelOut'])!=len(testList['out'])): + print('Mismatch of testing labels out (',len(testList['labelOut']),') and actual output (',len(testList['out']),') ') + sys.exit(0) + if (len(testList['in'])!=len(testList['out'])): + print('Mismatch of testing labels in input (',len(testList['in']),') and output (',len(testList['out']),') ') + sys.exit(0) + return 1; + +""" + Our Ground Truth has perfect knowledge of all joints, however based on the settings it also has invisibility flags + so that we can hide joints and simulate occlusions for the neural network. This function zeroes out X and Y coordinates + based on the invisibility flag. +""" +def zeroOutXYJointsThatAreInvisible(thisInput,rules): + if (len(thisInput)==0): + print("zeroOutXYJointsThatAreInvisible called with no input for element ") + return thisInput + + + if (not rules['inputJointMap'].checkJointListDimensions(thisInput)): + print("zeroOutXYJointsThatAreInvisible called with incorrect input size ") + return thisInput + + #checkIfConfigurationHierarchyIsTheSameAsLabelList + #head -n 1 dataset/2d_body_all.csv + + for z in range(0,len(rules['NSDM'])): + #-------------------------------------------------------- + jointName=rules['NSDM'][z]['joint'] + jID_X = rules['inputJointMap'].getJointID_2DX(jointName) + jID_Y = rules['inputJointMap'].getJointID_2DY(jointName) + jID_Vis = rules['inputJointMap'].getJointID_Visibility(jointName) + #-------------------------------------------------------- + if (thisInput[jID_Vis]==0.0): + #We have a 2D joint that is marked as invisible! + if (rules["ignoreOcclusions"]): + #If the master setting ignore occlusions is we set all visibility flags as visible.. + thisInput[jID_Vis] = 1.0 + #BUG 24/6/2023 lookup was looking at hierarchy instead of NSDM part for immuneToSelfOcclusions: + elif (not 'immuneToSelfOcclusions' in rules['NSDM'][z]) or (rules['NSDM'][z]['immuneToSelfOcclusions']==0): + #If the master setting for the particular hierarchy is not immune to invisibility, we erase it .. + thisInput[jID_X] = 0.0 + thisInput[jID_Y] = 0.0 + thisInput[jID_Vis] = 0.0 #This is already zero but leaving it here for easier inspection + return thisInput + + + +""" + Our Input has the form of 2DX_joint,2DY_joint,visibility_joint, .... , + 2D x and y coordinates are normalized in the range of [0,1], however using this function they can be balanced in the range [-0.5,0.5] + to study the effects on the neural network. +""" +def balance2DXYVis(thisInput,rules): + if (len(thisInput)==0): + print("balance2DXYVis called with no input for element ") + return thisInput + + if (not rules['inputJointMap'].checkJointListDimensions(thisInput)): + print("balance2DXYVis called with incorrect input size ") + return thisInput + + #checkIfConfigurationHierarchyIsTheSameAsLabelList + #head -n 1 dataset/2d_body_all.csv + isVisible=0 + for z in range(0,len(rules['NSDM'])): + #-------------------------------------------------------- + jointName = rules['NSDM'][z]['joint'] + jID_X = rules['inputJointMap'].getJointID_2DX(jointName) + jID_Y = rules['inputJointMap'].getJointID_2DY(jointName) + jID_Vis = rules['inputJointMap'].getJointID_Visibility(jointName) + #-------------------------------------------------------- + isVisible = thisInput[jID_Vis] + if (isVisible==1) or (rules["ignoreOcclusions"]): + thisInput[jID_X] = thisInput[jID_X] - 0.5 + thisInput[jID_Y] = thisInput[jID_Y] - 0.5 + return thisInput + + +""" + This function rearranges the 2D input discarding the visibility flag +""" +def rearrange2DInput(thisInput,rules): + if (len(thisInput)==0): + print("rearrange2DInput called with no input for element ") + return thisInput + + if (not rules['inputJointMap'].checkJointListDimensions(thisInput)): + print("rearrange2DInput called with incorrect input size ") + return thisInput + + rearrangedInput = list() + for z in range(0,len(rules['NSDM'])): + #-------------------------------------------------------- + jointName = rules['NSDM'][z]['joint'] + jID_X = rules['inputJointMap'].getJointID_2DX(jointName) + jID_Y = rules['inputJointMap'].getJointID_2DY(jointName) + jID_Vis = rules['inputJointMap'].getJointID_Visibility(jointName) + #-------------------------------------------------------- + rearrangedInput.append(np.float32(thisInput[jID_X])) + rearrangedInput.append(np.float32(thisInput[jID_Y])) + if ('include2DInputVisibilityFlags' in rules) and (rules['include2DInputVisibilityFlags']==1): #Was flipped, bug 8/5/2023 + rearrangedInput.append(np.float32(thisInput[jID_Vis])) + #-------------------------------------------------------- + return rearrangedInput + + + + +""" + A sanity check counting if the configuration file corresponds to our label list! +""" +def checkIfConfigurationHierarchyIsTheSameAsLabelList(configuration,labelList,useNSDM): + #Manual check using : + # head -1 dataset/2d_body_all.csv + # and : + # cat dataset/body_configuration.json | grep joint + # + #This code part checks if our new JSON configuration corresponds to a labelList we encountered + #The label list will have two parts, a normal one that should have 1-1 correspondance with configuration['hierarchy'] + #and a second one that should have 1-1 correspondance with the NSDM list extracted from the configuraton using the NSDMLabels call + numberOfInputElements=len(labelList) + numberOfElementsInRulesHierarchy=len(configuration['hierarchy']) + + #We begin by copying the configuration to an (initially) empty list + configurationHierarchyList=list() + for i in range(0,numberOfElementsInRulesHierarchy): + configurationHierarchyList.append("2DX_"+configuration['hierarchy'][i]['joint']) + configurationHierarchyList.append("2DY_"+configuration['hierarchy'][i]['joint']) + configurationHierarchyList.append("visible_"+configuration['hierarchy'][i]['joint']) + + if (useNSDM): + #We can also add the NSDM elements if it is wanted.. + configurationHierarchyList.extend(NSDMLabels(configuration)) + + if ( (configuration['doPCA']!="") ): + print("PCA will fake everything to get past check") + configurationHierarchyList=list() + for i in range(0,int(configuration['PCADimensionsKept'])): + configurationHierarchyList.append("PCA-"+str(i)) + return 1 + + + #At this point the two lists should be identical.. + numberOfElementsInRulesHierarchy=len(configurationHierarchyList) + + if (numberOfElementsInRulesHierarchy!=numberOfInputElements): + print(bcolors.FAIL,'Rule file inconsistency',bcolors.ENDC) + print(bcolors.FAIL,'numberOfElementsInRulesHierarchy = ',numberOfElementsInRulesHierarchy,bcolors.ENDC) + print(bcolors.FAIL,'numberOfInputElements = ',numberOfInputElements,bcolors.ENDC) + print('Number of Configuration Rules ',numberOfElementsInRulesHierarchy,' Number of Encountered Input Elements ',numberOfInputElements) + print('Configuration from JSON ',configurationHierarchyList) + print('Data we encountered ',labelList) + return 0 + + + success=1 + for i in range(0,numberOfElementsInRulesHierarchy): + nB=labelList[i] + nA=configurationHierarchyList[i] + + if (nA!=nB): + print (bcolors.FAIL,"#",i," json(",nA,") != (",nB,")",bcolors.ENDC) + success=0 + + del configurationHierarchyList + return success + + + +""" + Return the configuration from a filename pointing to a .json file..! +""" +def parseConfiguration(filename): + import json + configuration = dict() + with open(filename) as f: + s = f.read() + s = s.replace('\t','') + s = s.replace('\n','') + s = s.replace(',}','}') + s = s.replace(',]',']') + configuration = json.loads(s) + + #Backwards compatibility + #---------------------------------------------- + if not 'useRadians' in configuration: + configuration['useRadians']=0 + if (configuration['useRadians']==1): + #If we are using radians everything will be smaller so scale accordingly early stopping deltas! + configuration['minEarlyStoppingDelta']=float(configuration['minEarlyStoppingDelta']) * float(0.01) + if not 'outputMode' in configuration: + configuration['outputMode']="bvh" + if not 'padEnsembleInput' in configuration: + configuration['padEnsembleInput']=0 + if not 'probabilisticOutput' in configuration: + configuration['probabilisticOutput']=0 + if not ('decompositionType' in configuration) and ('doPCA' in configuration): + configuration['decompositionType']='pca' + if not ('outputMultiplier' in configuration): + configuration['outputMultiplier']=float(1.0) + #---------------------------------------------- + if not 'include2DInputVisibilityFlags' in configuration: + configuration['include2DInputVisibilityFlags']=1 + #---------------------------------------------- + if (float(configuration['outputMultiplier'])>1.0): + print("Rescaling early stopping delta from ",configuration['minEarlyStoppingDelta']) + configuration['minEarlyStoppingDelta']=float(configuration['minEarlyStoppingDelta']) * float(configuration['outputMultiplier']) + print("To an early stopping delta of ",configuration['minEarlyStoppingDelta']) + #---------------------------------------------- + if not 'setConstantSeedForReproducibleTraining' in configuration: + configuration["setConstantSeedForReproducibleTraining"]=1 + #---------------------------------------------- + if not 'weightRandomizationFunction' in configuration: + configuration["weightRandomizationFunction"]="auto" + #print(bcolors.FAIL +"Please declare a weightRandomizationFunction in ",filename," "+bcolors.ENDC) + #sys.exit(1) + #---------------------------------------------- + if (configuration["weightRandomizationFunction"]=="auto"): + print("Will use automatically selected randomization function (old code)") + elif (configuration["weightRandomizationFunction"]=="glorot_uniform"): + print("Using Glorot/Xavier randomization (https://www.tensorflow.org/api_docs/python/tf/keras/initializers/GlorotUniform)") + elif (configuration["weightRandomizationFunction"]=="lecun_normal"): + print("Using Lecun Normal randomization (https://www.tensorflow.org/api_docs/python/tf/keras/initializers/LecunNormal)") + elif (configuration["weightRandomizationFunction"]=="he_uniform"): + print("Using He Uniform randomization (https://www.tensorflow.org/api_docs/python/tf/keras/initializers/HeUniform)") + else: + print(bcolors.FAIL+"The randomization function you used ",configuration["weightRandomizationFunction"]," is not in whitelist"+bcolors.ENDC) + sys.exit(1) + #---------------------------------------------- + if not ('outputValueDistribution' in configuration): + print("Weird, no outputValueDistribution in configuration ~-> ",filename) + configuration['outputValueDistribution']='default' + #sys.exit(1) + else: + if (configuration['outputValueDistribution']!='balanced'): + pass + elif (configuration['outputValueDistribution']!='positive'): + pass + elif (configuration['outputValueDistribution']!='negative'): + pass + elif (configuration['outputValueDistribution']!='default'): + pass + else: + print(bcolors.FAIL+"Configuration error: outputValueDistribution valid options are balanced|positive|negative|default"+bcolors.ENDC) + sys.exit(1) + + from datetime import datetime + # Get the current date and time + now = datetime.now() + # Format the date string + configuration['date']= now.strftime("%d/%m/%Y") + + + return configuration + + + + + +""" + New code on jointMap.py that uses a dict to resolve 2D CSV input labels to the correct column that has the data +""" +def parseConfigurationInputJointMap(configuration,labelsInput): + from jointMap import JointDataMapper + configuration['inputJointMap'] = JointDataMapper("foo",configuration,labelsInput) + return configuration + + + +""" + This is a mini-function to initialize the decomposition code inside the readGroundTruthFile for an execution engine and make code more compact.. +""" +def initializeDecompositionForExecutionEngine(configuration,modelDirectory,partName,disablePCACode=False): + print("initializeDecompositionForExecutionEngine") + from dataDecomposition import EmptyDecomposition + ourDecomposition = EmptyDecomposition() + + if (configuration['decompositionType']==""): + return ourDecomposition + + + if (not disablePCACode): + PCAFile = "" + x = configuration['doPCA'].split("$") + if (len(x)>0): + #If there is a $ character this is our place holder to autocomplete our dataFile + print("We resolve ",configuration['doPCA']) + print(" to ") + PCAFile=x[0]+partName+".pca" + print(PCAFile) + print("Simple is ",PCAFile) + #We use the model pca file + PCAFile=modelDirectory+"/"+partName+".pca" + print("Per model is ",PCAFile) + + if (not 'decompositionType' in configuration) or (configuration['decompositionType']=="pca"): + from principleComponentAnalysis import PCA + ourDecomposition = PCA(decompositionType='pca',savedFile=PCAFile) + else: + from dataDecomposition import Decomposition + ourDecomposition = Decomposition(decompositionType=configuration['decompositionType'],selectedPCADimensions=configuration['PCADimensionsKept']) + + + if ( (configuration['decompositionType']!="") and (configuration['doPCA']!="") and ( int(configuration['PCADimensionsKept'])>0 ) ): + if (ourDecomposition.load(PCAFile)): + print(bcolors.OKGREEN + "Opened Decomposition file "+ PCAFile + bcolors.ENDC) + #Important our decomposition engine exposed to the rest of the code + configuration['decompositionEngine']=ourDecomposition + if (ourDecomposition.numberOfSamplesFittedOn==0): + print(bcolors.WARNING + "Could not load PCA file "+ configuration['doPCA'] + bcolors.ENDC) + else: + print(bcolors.OKGREEN + "Decomposition is disabled" + bcolors.ENDC) + + return ourDecomposition + #-------------------------------------------- + + + +""" + Return an ARRAY ( not a list or a dict ) with the input of our Neural Network after constructing all other required descriptors according to our configuration +""" +def transformNetworkInput( + configuration, + ourDecomposition, + inputToTransform, + dtypeSelected, + completelyDisablePCACode = 0, + completelyDisableEigenposes = 0 + ): + from array import array + + # inputToTransform contains the input we want to transform, + # we also suppose that configuration['inputJointMap'] holds our joint mapping information.. + # we need to perform many operations, which will gradually transform our input to network input according to our configuration + + #INPUT IS : inputToTransform + #----------------------------------------------------------------------------------------------------------------------------- + # First : Hide invisible joints if allowed by our configuration + #----------------------------------------------------------------------------------------------------------------------------- + processedInputToTransform = zeroOutXYJointsThatAreInvisible(inputToTransform,configuration) + #----------------------------------------------------------------------------------------------------------------------------- + # Second : Align "processedInputToTransform" using our pivot and reference point ( if eNSRM is set to 1 in .json settings ) + #----------------------------------------------------------------------------------------------------------------------------- + angleToRotate,processedInputToTransform = performNSRMAlignment(processedInputToTransform,configuration) + #----------------------------------------------------------------------------------------------------------------------------- + # Third : re balance 2D input from [0..1] to [-0.5..0.5] ( THIS WORKS VERY BADLY ) + if ('balanced2DInputs' in configuration) and (configuration['balanced2DInputs']==1): + print("balanced2DInputs WORKS VERY BADLY for some reason..") + processedInputToTransform = balance2DXYVis(processedInputToTransform,configuration) + #----------------------------------------------------------------------------------------------------------------------------- + # Fourth : ALWAYS! REPACK 2D input to make sure visibility flags are included (or not) no matter what data inputToTransform holds + # WARNING: do not use rearranged2DInput for anything else.. + rearranged2DInput = rearrange2DInput(processedInputToTransform,configuration) + #Convert 2D input to an array with just repacked 2D inputs + rearranged2DInputArray = array("f", rearranged2DInput) #<- THIS PART OF THE ARRAY ONLY HAS OUR 2D INPUTS + #----------------------------------------------------------------------------------------------------------------------------- + #OUTPUT IS : rearranged2DInput + + + #------------------------------------------- + # Collect descriptors + #------------------------------------------- + #INPUT is processedInputToTransform + thisDescriptor = list() + # Add an EDM descriptor + if ('EDM' in configuration) and (int(configuration['EDM'])==1): + thisDescriptor.extend(createEDMUsingRules(configuration,processedInputToTransform)) #important to use processedInputToTransform + thisDescriptor.append(angleToRotate) + # Add an NSDM descriptor + if (int(configuration['eNSRM'])==1) or (int(configuration['NSDMNormalizationMasterSwitch'])==1) or (int(configuration['NSDMAlsoUseAlignmentAngles'])==1): + thisDescriptor.extend(createNSDMUsingRules(configuration,processedInputToTransform,angleToRotate) ) #important to use processedInputToTransform + thisDescriptorArray = array("f", thisDescriptor) + #OUTPUT is thisDescriptor + #------------------------------------------- + + #------------------------------------------- + # Gather full input + #------------------------------------------- + thisFullInputArray = array("f") + thisFullInputArray.extend(rearranged2DInputArray) #only use 2D input as absolute vector, do not feed it to things depending on JointMap + thisFullInputArray.extend(thisDescriptorArray) + #------------------------------------------- + + #------------------------------------------- + # Pack in a PCA + #------------------------------------------- + if (not completelyDisablePCACode): + if ( (configuration['decompositionType']!="") and (configuration['doPCA']!="") and (ourDecomposition.numberOfSamplesFittedOn!=0) and ( int(configuration['PCADimensionsKept'])>0 ) ): + #print("ourDecomposition.transform ",len(thisFullInputArray)," elements ") + transformedInput = ourDecomposition.transform(np.asarray([thisFullInputArray],dtype=dtypeSelected),selectedPCADimensions=int(configuration['PCADimensionsKept'])) + transformedInputList = transformedInput[0].real.tolist() + transformedInputArray = array("f",transformedInputList) + #--------------------------------------------------------------------------------- + if ('PCAAlsoKeepRawData' in configuration) and (configuration['PCAAlsoKeepRawData']==1): + #We will also include the RAW UNROTATED DATA! + thisFullInputArray = rearranged2DInputArray #only use 2D input as absolute vector, do not feed it to things depending on JointMap + thisFullInputArray.extend(transformedInputArray) + else: + #We will not keep raw data so we will serve ONLY THE PCA DATA! + thisFullInputArray = transformedInputArray + #--------------------------------------------------------------------------------- + if (len(transformedInput[0])!=int(configuration['PCADimensionsKept'])): + print(bcolors.WARNING +"Inconsistent size of input after dimensionality reduction, expected %u and got %u "%( int(configuration['PCADimensionsKept']) , len(thisFullInputArray) ) + bcolors.ENDC) + sys.exit(1) + #------------------------------------------- + + #------------------------------------------- + # Add EigenPose Data + #------------------------------------------- + if ('eigenPoses' in configuration) and (int(configuration['eigenPoses'])==1) and (completelyDisableEigenposes==0): + from EigenPoses import createEigenPosesUsingRules + thisFullInputArray.extend(createEigenPosesUsingRules(configuration,thisFullInputArray)) + #------------------------------------------- + return thisFullInputArray, thisDescriptorArray, rearranged2DInputArray, angleToRotate + + + +def prepareInputG(input2D :dict,configuration : dict, expectedInputList,dummyUnneededInput,part,decompositionEngine,disablePCACode): + thisInput = list() + + #input2D is a dictionary that has 2D information on all the body. We need to make a list that directly corresponds to the expected Input List + totalJoints = 0 + existingJoints = 0 + #We gather all elements in expectedInputList + for element in expectedInputList: + totalJoints = totalJoints + 1 + if element in input2D: + #If the element exists then we append it + thisInput.append(np.float32(input2D[element])) + existingJoints = existingJoints + 1 + else: + #If it does not we append a zero, to maintain the vector dimensions + print(bcolors.FAIL,"prepareInputG: NSRM Input Element `",element,"` is missing",bcolors.ENDC) + thisInput.append(np.float32(0.0)) + + #After trying all elements in our input dictionary we now know how much data is missing + missingRatio = float ( 1.0 - (existingJoints/totalJoints) ) + if (missingRatio>0.3): + #print("Input : ",input2D) + print(bcolors.FAIL,"Too many elements missing for ",part," missingRatio : ",missingRatio,"..",bcolors.ENDC) + + #thisInput now holds all of our data, lets transform it! + #----------------------------------------------------------------------------------------------------------------------------------------------- + thisFullInput, thisNSDM, thisInput, angleToRotate = transformNetworkInput(configuration,decompositionEngine,thisInput,np.float32,disablePCACode) + #----------------------------------------------------------------------------------------------------------------------------------------------- + #print(bcolors.WARNING,"prepareInputG: transformed ",input2D," to ",thisFullInput,bcolors.ENDC) + return thisFullInput, thisNSDM, thisInput, angleToRotate + + + +""" + Attempt to count how many elemnts transformNetworkInput produces and make them a list of strings +""" +def transformNetworkInputLabels(configuration,inputLabelsOnly,inputNumberOfColumns,dtypeSelected,ourDecomposition,thisInput,completelyDisablePCACode = 0,completelyDisableEigenposes = 0): + thisLabels = list() + + if (completelyDisablePCACode): + print("transformNetworkInputLabels doesnt know how to work with PCA code disabled :(") + + emptyInput = list() + for i in range(0,inputNumberOfColumns): + emptyInput.append(0.0) + + print("Empty input for labels has %u elements " % len(emptyInput)) + emptyFullInput, emptyNSDMArray, emptyInputArray, emptyAngleToRotate = transformNetworkInput( + configuration, + ourDecomposition, + emptyInput, + dtypeSelected, + completelyDisablePCACode = completelyDisablePCACode, + completelyDisableEigenposes = completelyDisableEigenposes + ) + + for i in range(0,len(emptyFullInput)): + thisLabels.append("in%u"%i) + + print("Generated labels have %u elements " % len(thisLabels)) + print("Full input has %u elements " % len(emptyFullInput)) + print("NSxM has %u elements " % len(emptyNSDMArray)) + print("2D data have %u elements " % len(emptyInputArray)) + #----------------------------------------------------------------------------------------------------------------------------------------------- + return thisLabels + + + +def resolveNormalizationRules(configuration,label): + column = 0 + resolved = False + labelLowerCase = label.lower() + #---------------------------------------------------------- + for labelSearch in configuration["outputOffsetLabels"]: + if (labelLowerCase==labelSearch.lower()): + resolved = True + break; + else: + column += 1 + #---------------------------------------------------------- + if (not resolved): + print(bcolors.FAIL,"Could not resolve ",label,bcolors.ENDC) + return column,resolved + + + +""" + This function applies the offsets stored in configuration["outputOffsetValues"] +""" +def executeNormalization(configuration,data): + #-------------------------------------------------------------- + if ("out" in data) and ("outputOffsetValues" in configuration) and ("outputScalarValues" in configuration): + offsets = configuration["outputOffsetValues"] + scalars = configuration["outputScalarValues"] + minima = configuration["outputOffsetMinima"] + maxima = configuration["outputOffsetMaxima"] + numberOfSamples = data["out"].shape[0] + numberOfColumns = data["out"].shape[1] + print("executeNormalization: Output has ",numberOfColumns," columns ") + for column in range(numberOfColumns): + #Actual normalization of column + #--------------------------------------------- + for sample in range(numberOfSamples): + data["out"][sample][column] = np.float32( ( data["out"][sample][column] - np.float32(offsets[column]) ) * np.float32(scalars[column]) ) + #--------------------------------------------- + + #Get statistics to make sure everything went ok..! + for column in range(data["out"].shape[1]): + #--------------------------------------------- + titleString="%s | Offset %0.2f Scalar %0.2f | Min=%0.2f,Max=%0.2f |" % (data["labelOut"][column],offsets[column],scalars[column],minima[column],maxima[column]) + print(bcolors.OKGREEN," %s " % titleString,bcolors.ENDC) + #--------------------------------------------- + #-------------------------------------------------------------- + print(bcolors.OKGREEN," Done normalizing output ",bcolors.ENDC) + #-------------------------------------------------------------- + return data + + + +""" + This function just calculates the offsets and stores them in configuration["outputOffsetValues"] +""" +def calculateOutputNormalization(configuration,outputDirectoryForTrainedModels,data): + if ("out" in data): + numberOfSamples = data["out"].shape[0] + numberOfOutputs = data["out"].shape[1] + print("calculateOutputNormalization: Output has ",numberOfOutputs," columns ") + minima = list() + maxima = list() + offsets = list() + scalars = list() + scalarsFraction=list() + for column in range(numberOfOutputs): + #Calculate offset for each column.. + #--------------------------------------------- + thisOffset=np.float32(0.0) + thisMinima=np.float32(0.0) + thisMaxima=np.float32(0.0) + if (numberOfSamples>0): + #We have available elements so lets do a proper min/max initialization + thisMinima=np.float32(data["out"][0][column]) + thisMaxima=np.float32(data["out"][0][column]) + #Actually calculate min/max for all samples + #--------------------------------------------- + for sample in range(numberOfSamples): + if (thisMinima>data["out"][sample][column]): + thisMinima = np.float32(data["out"][sample][column]) + if (thisMaxima ",correctOffsets["label"][jointID]," <-> ",correctOffsets["body"][0][jointID]," <-> ",outputNames[resolvedPosition]) + initialValues[resolvedPosition] = np.float32(correctOffsets["body"][0][jointID]) + else: + print(bcolors.FAIL,"Unresolved loading ",jointID," - ",correctOffsets["label"][jointID],bcolors.ENDC) + #-------------------------------------------------------------------------------------------------------------- + return initialValues + + + + + +def checkDataForNaN(data): + #Check for NaNs and Inf values that might influence data fitting + #---------------------------------------------------------------------------------------------------------------------------- + if ( np.isnan(data['in']).any() ) or ( np.isinf(data['in']).any() ): + print(bcolors.FAIL,"Fatal : Our input contains NaN or Inf values will not carry on with erroneous input ",bcolors.ENDC) + sys.exit(1) + else: + print(bcolors.OKGREEN,"Our input is NaN/Inf free ",bcolors.ENDC) + #---------------------------------------------------------------------------------------------------------------------------- + if ( np.isnan(data['out']).any() ) or ( np.isinf(data['out']).any() ): + print(bcolors.FAIL,"Fatal : Our output contains NaN or Inf values will not carry on with erroneous input ",bcolors.ENDC) + sys.exit(1) + else: + print(bcolors.OKGREEN,"Our output is NaN/Inf free ",bcolors.ENDC) + #---------------------------------------------------------------------------------------------------------------------------- + + + + + +def filterOutEmptyInputSamples(data,sampleNumber): + #print("Will now attempt to filter out empty input samples") + numberOfOutputs = len(data) + emptyInputsInThisSample = 0 + for output in range(0,numberOfOutputs): + if (data[output]==0.0): + emptyInputsInThisSample = emptyInputsInThisSample + 1 + if (emptyInputsInThisSample==numberOfOutputs): + #print("Sample ",sampleNumber," has ",float(emptyInputsInThisSample/numberOfOutputs)," empty outputs") + return True + return False + + + + +def readGroundTruthFile(configuration,typeOfGroundTruthFile,filenameInput,filenameOutput,memPercentage,numberOfNSDMElements,useRadians,useHalfFloats,completelyDisablePCACode = 0,externalDecomposition=EmptyDecomposition): + print("Accessing ",typeOfGroundTruthFile," file In:",filenameInput," file Out:",filenameOutput,"..\n") + start = time.time() + #------------------------------------------------------------------------------------------------------------------------------------------------ + if (not checkIfFileExists(filenameInput)): + print( bcolors.FAIL + "Input file "+filenameInput+" does not exist, cannot read ground truth "+typeOfGroundTruthFile+".." + bcolors.ENDC) + print("Current Directory was "+os.getcwd()) + sys.exit(1) + if (not checkIfFileExists(filenameOutput)): + print( bcolors.FAIL + "Output file "+filenameOutput+" does not exist, cannot read ground truth "+typeOfGroundTruthFile+".." + bcolors.ENDC) + print("Current Directory was "+os.getcwd()) + sys.exit(1) + #------------------------------------------------------------------------------------------------------------------------------------------------ + if (not 'outputOffsetLabels' in configuration) or (not 'outputOffsetMinima' in configuration) or (not 'outputOffsetMaxima' in configuration) or (not 'outputOffsetValues' in configuration) : + print( bcolors.OKGREEN + "No Min/Max Offset/Scalar normalization data during this load operation.." + bcolors.ENDC) + #------------------------------------------------------------------------------------------------------------------------------------------------ + + + + #Make sure ourDecomposition points to the "correct" decomposition class based on our configuration + if (type(externalDecomposition)==type(EmptyDecomposition)): + #------------------------------------------------------------------------------------------------------------------------------------------------ + if (not 'decompositionType' in configuration) or (configuration['decompositionType']=="pca"): + from principleComponentAnalysis import PCA + print(bcolors.OKGREEN + "Using my PCA code.." + bcolors.ENDC) + ourDecomposition = PCA(decompositionType='pca') + else: + from dataDecomposition import Decomposition + print(bcolors.OKGREEN + "Using Sk-Learn decomposition code.." + bcolors.ENDC) + ourDecomposition = Decomposition(decompositionType=configuration['decompositionType'],selectedPCADimensions=configuration['PCADimensionsKept']) + #------------------------------------------------------------------------------------------------------------------------------------------------ + if (not completelyDisablePCACode): + if ( (configuration['decompositionType']!="") and (configuration['doPCA']!="") and ( int(configuration['PCADimensionsKept'])>0 ) ): + if (ourDecomposition.load(configuration['doPCA'])): + print(bcolors.OKGREEN + "Opened PCA file "+ configuration['doPCA'] + bcolors.ENDC) + #Important our decomposition engine exposed to the rest of the code + configuration['decompositionEngine']=ourDecomposition + if (ourDecomposition.numberOfSamplesFittedOn==0): + print(bcolors.WARNING + "Could not load decomposition file "+ configuration['doPCA'] + bcolors.ENDC) + else: + print(bcolors.OKGREEN + "PCA is disabled, change \"doPCA\": \"dataset/$.pca\" in configuration to re-enable it" + bcolors.ENDC) + #------------------------------------------------------------------------------------------------------------------------------------------------ + else: + if (not completelyDisablePCACode): + ourDecomposition = externalDecomposition + print(bcolors.OKGREEN + "Using externally initialized decomposition code" + bcolors.ENDC) + #------------------------------------------------------------------------------------------------------------------------------------------------ + + + #Disable eigenposes if we are reading eigenposes :P + #------------------------------------------------------------------------------------------------- + completelyDisableEigenposes = 1 + if (not "Eigenposes"==typeOfGroundTruthFile): + if ("eigenPoses" in configuration) and (configuration['eigenPoses']==1): + completelyDisableEigenposes = 0 + #------------------------------------------------------------------------------------------------- + + + #------------------------------------------------------------------------------------------------- + print(bcolors.OKGREEN + "Input/Output files "+filenameInput+","+filenameOutput+" exist in filesystem" + bcolors.ENDC) + #------------------------------------------------------------------------------------------------- + dtypeSelected=np.dtype(np.float32) + dtypeSelectedByteSize=int(dtypeSelected.itemsize) + if (useHalfFloats): + dtypeSelected=np.dtype(np.float16) + dtypeSelectedByteSize=int(dtypeSelected.itemsize) + #------------------------------------------------------------------------------------------------- + progress = 0.0 + sampleNumber = 0 + CSVInputNumberOfColumns = 0 + CSVOutputNumberOfColumns = 0 + receivedHeader = False + #------------------------------------------------------------------------------------------------- + inputLabels = list() + inputLabelsClean = list() + outputLabels = list() + #------------------------------------------------------------------------------------------------- + numberOfSamplesInput = getNumberOfLines(filenameInput)-2 + numberOfSamplesOutput = getNumberOfLines(filenameOutput)-2 + #------------------------------------------------------------------------------------------------- + print(typeOfGroundTruthFile," Input file has ",numberOfSamplesInput," training samples\n") + print(typeOfGroundTruthFile," Output file has ",numberOfSamplesOutput," training samples\n") + #------------------------------------------------------------------------------------------------- + if (numberOfSamplesInput!=numberOfSamplesOutput): + print(bcolors.WARNING +"Input files (",filenameInput," and ",filenameOutput,") are inconsistent cannot continue"+ bcolors.ENDC) + sys.exit(1) + #------------------------------------------------------------------------------------------------- + + numberOfSamples = numberOfSamplesInput + numberOfSamplesLimit=int(numberOfSamples*memPercentage) + #------------------------------------------------------------------------------------------------- + if (memPercentage==0.0): + print("readGroundTruthFile was asked to occupy 0 memory so this probably means we just want one record") + numberOfSamplesLimit=2 + if (memPercentage>1.0): + print("Memory Limit will be interpreted as a raw value..") + numberOfSamplesLimit=int(memPercentage) + #------------------------------------------------------------------------------------------------- + + + encounteredSamplesThatAreEmpty = list() + #--------------------------------- + numberOfInputElements = 0 + #--------------------------------- + thisInput = array.array('f') + thisOutput = array.array('f') + #--------------------------------- + fi = open(filenameInput, "r") + fo = open(filenameOutput, "r") + #--------------------------------- + readerIn = csv.reader( fi , delimiter =',', skipinitialspace=True) + readerOut = csv.reader( fo , delimiter =',', skipinitialspace=True) + #--------------------------------- + for rowIn,rowOut in zip(readerIn,readerOut): + #------------------------------------------------------ + if (not receivedHeader): #use header to get labels + #------------------------------------------------------ + CSVInputNumberOfColumns = len(rowIn) + CSVOutputNumberOfColumns = len(rowOut) + print("Number of Input Columns : " ,CSVInputNumberOfColumns) + print("Number of Output Columns : ",CSVOutputNumberOfColumns) + #--------------------------------- + #Add labels for pure joints + #--------------------------------- + inputLabelsOnly = list(rowIn[i] for i in range(0,CSVInputNumberOfColumns) ) + #As soon as we have the 2D input labels populate the configuration with the mapping ..! + configuration = parseConfigurationInputJointMap(configuration,inputLabelsOnly) + #--------------------------------- + inputLabelsClean= inputLabelsOnly + print("Number of Input elements without NSRM/NSDM, just joint 2D data : ",len(inputLabels)) + + inputLabels = transformNetworkInputLabels( + configuration, + inputLabelsOnly, + CSVInputNumberOfColumns, + dtypeSelected, + ourDecomposition, + thisInput, + completelyDisablePCACode = completelyDisablePCACode, + completelyDisableEigenposes = completelyDisableEigenposes + ) + print("inputLabels:",inputLabels) + numberOfInputElements = len(inputLabels) + + print("\n\n\nNumber of Input elements with NSRM/NSDM: ",len(inputLabels),"\n\n\n") + outputLabels = list(rowOut[i] for i in range(0,CSVOutputNumberOfColumns) ) + print("Input : ",inputLabels,"\n") + #------------------------------------------------------ + print("Number of Output elements : ",len(outputLabels)) + print("Output : ",outputLabels,"\n") + print("\n Now reading ",typeOfGroundTruthFile," files.. \n") + #------------------------------------------------------ + if (memPercentage==0): + print("Will only return labels\n") + return {'labelIn':inputLabels, 'labelOut':outputLabels}; + + #------------------------------------------ + # Allocate enough elements in the arrays + #------------------------------------------ + print(bcolors.WARNING,"Allocated input will have %u items / line in is %u items long "%(CSVInputNumberOfColumns,len(rowIn)) , bcolors.ENDC) + for i in range(CSVInputNumberOfColumns): # + thisInput.append(np.float32(0.0)) + #------------------------------------------ + for i in range(CSVOutputNumberOfColumns): + thisOutput.append(np.float32(0.0)) + #------------------------------------------- + + #------------------------------------------- + # Allocate Numpy Arrays + #------------------------------------------- + inputSize = len(inputLabels) + #------------------------------------------- + + #-------------------------------------------------------------------------------------------------------------------------- + npInputBytesize=0+numberOfSamplesLimit * inputSize * dtypeSelectedByteSize + print("Input file on disk : ",filenameInput) + print(typeOfGroundTruthFile," Input file on disk has a shape of [",numberOfSamples,",",inputSize,"]") + print(typeOfGroundTruthFile," Input we will read has a shape of [",numberOfSamplesLimit,",",inputSize,"]") + print(typeOfGroundTruthFile," Input will occupy ",convert_bytes(npInputBytesize)," of RAM\n") + npInput = np.full([numberOfSamplesLimit,inputSize],fill_value=0,dtype=dtypeSelected,order='C') + #-------------------------------------------------------------------------------------------------------------------------- + npOutputByteSize=0+numberOfSamplesLimit * CSVOutputNumberOfColumns * dtypeSelectedByteSize + print("Output file on disk : ",filenameOutput) + print(typeOfGroundTruthFile," Output file on disk has a shape of [",numberOfSamples,",",CSVOutputNumberOfColumns,"]") + print(typeOfGroundTruthFile," Output we will read has a shape of [",numberOfSamplesLimit,",",CSVOutputNumberOfColumns,"]") + print(typeOfGroundTruthFile," Output will occupy ",convert_bytes(npOutputByteSize)," of RAM") + npOutput = np.full([numberOfSamplesLimit,CSVOutputNumberOfColumns],fill_value=0,dtype=dtypeSelected,order='C') + #-------------------------------------------------------------------------------------------------------------------------- + print("\n Total occupied memory ",convert_bytes(npInputBytesize+npOutputByteSize)," of RAM\n") + + receivedHeader=True + else: + #------------------------------------------- + # First convert our string INPUT to floats + #------------------------------------------- + if (len(thisInput)!=len(rowIn)): + print(bcolors.FAIL,"Inconsistency : array allocated %u elements, ParsedInput has %u elements , CSVInputNumberOfColumns has %u elements "%(len(thisInput),len(rowIn),CSVInputNumberOfColumns),bcolors.ENDC) + for i in range(CSVInputNumberOfColumns): + thisInput[i]=(np.float32(rowIn[i])) + #-------------------------------------------------------------------------------------------------------------------------- + if (filterOutEmptyInputSamples(thisInput,sampleNumber)): + encounteredSamplesThatAreEmpty.append(sampleNumber) + + thisFullInput, thisNSDM, thisRearrangedInput, angleToRotate = transformNetworkInput( + configuration, + ourDecomposition, + thisInput, + dtypeSelected, + completelyDisablePCACode, + completelyDisableEigenposes + ) + #-------------------------------------------------------------------------------------------------------------------------- + + #------------------------------------------- + # Then convert our string OUTPUT to floats + #------------------------------------------- + for i in range(CSVOutputNumberOfColumns): + thisOutput[i]=np.float32(rowOut[i]) + #-------------------------------------------------------------------------------------- + + #======================================================= + # ACTUAL STORE IN NUMPY ARRAYS + #======================================================= + #======================================================= + if (useHalfFloats): + #Populate data in the correct order in the final NumPy array + #------------------------------------------- + #Convert Input + for num in range(0,numberOfInputElements): + npInput[sampleNumber,num] = np.float16(thisFullInput[num]); + #------------------------------------------- + #Convert Output + for num in range(0,CSVOutputNumberOfColumns): + npOutput[sampleNumber,num] = np.float16(thisOutput[num]); + #------------------------------------------- + else: + #Populate data in the correct order in the final NumPy array + #------------------------------------------- + #Convert Input + for num in range(0,numberOfInputElements): + npInput[sampleNumber,num] = np.float32(thisFullInput[num]); + #------------------------------------------- + for num in range(0,CSVOutputNumberOfColumns): + npOutput[sampleNumber,num] = np.float32(thisOutput[num]); + #------------------------------------------- + #======================================================= + #======================================================= + + sampleNumber=sampleNumber+1 + + if (numberOfSamples>0): + progress=sampleNumber/numberOfSamplesLimit + if (sampleNumber%1000==0) : + progressString = "%0.2f"%float(100*progress) + print("\rReading ",typeOfGroundTruthFile," from disk (",sampleNumber,") - ",progressString," % \r", end="", flush=True) + + if (numberOfSamplesLimit<=sampleNumber): + print("\rStopping reading file to obey memory limit given by parameter --mem ",memPercentage,"\n") + break + #------------------------------------------- + fi.close() + fo.close() + del readerIn + del readerOut + gc.collect() + + #------------------------------------------- + print("\n",typeOfGroundTruthFile," read, Samples: ",sampleNumber,", was expecting ",numberOfSamples," samples\n") + print(npInput.shape) + print(npOutput.shape) + #------------------------------------------- + totalNumberOfBytes=npInput.nbytes+npOutput.nbytes; + totalNumberOfGigaBytes=totalNumberOfBytes/1073741824; + print("GPU Size Occupied by ",typeOfGroundTruthFile," data = ",totalNumberOfGigaBytes," GB \n") + #------------------------------------------- + end = time.time() + + print("Time elapsed : ",(end-start)/60," mins") + + + #Filter out / Delete samples that are empty.. + print("Encountered ",len(encounteredSamplesThatAreEmpty)," empty samples :") + print(encounteredSamplesThatAreEmpty) + encounteredSamplesThatAreEmpty.reverse() #if not reversed we will screw up ordering..! + for rowToDelete in encounteredSamplesThatAreEmpty: + print("Deleting sample ",rowToDelete) + npInput = np.delete(npInput, (int(rowToDelete)), axis=0) + npOutput = np.delete(npOutput, (int(rowToDelete)), axis=0) + + + + #------------------------------------------- + data = { 'labelInNoNSDM':inputLabelsOnly, 'labelIn':inputLabels, 'labelInClean':inputLabelsClean, 'in':npInput, 'labelOut':outputLabels, 'out':npOutput } + #------------------------------------------- + checkDataForNaN(data) + #------------------------------------------- + + return data + + + + +#--------------------------------------------------------------------------------------------------------------------------------------------- +#--------------------------------------------------------------------------------------------------------------------------------------------- +#--------------------------------------------------------------------------------------------------------------------------------------------- +#--------------------------------------------------------------------------------------------------------------------------------------------- +#--------------------------------------------------------------------------------------------------------------------------------------------- +#--------------------------------------------------------------------------------------------------------------------------------------------- +#--------------------------------------------------------------------------------------------------------------------------------------------- +#--------------------------------------------------------------------------------------------------------------------------------------------- +#--------------------------------------------------------------------------------------------------------------------------------------------- +#--------------------------------------------------------------------------------------------------------------------------------------------- +#--------------------------------------------------------------------------------------------------------------------------------------------- +#--------------------------------------------------------------------------------------------------------------------------------------------- +#--------------------------------------------------------------------------------------------------------------------------------------------- +#--------------------------------------------------------------------------------------------------------------------------------------------- +#--------------------------------------------------------------------------------------------------------------------------------------------- +#--------------------------------------------------------------------------------------------------------------------------------------------- + +""" + readCSVLabels opens the first line of a CSV file and gives back a list with its labels +""" +def readCSVLabels(filename): + if (not checkIfFileExists(filename)): + print( bcolors.FAIL + "readCSVLabels: Input file "+filename+" does not exist, cannot read ground truth.." + bcolors.ENDC) + print("Current Directory was "+os.getcwd()) + sys.exit(1) + + print(bcolors.OKGREEN,"readCSVLabels: file ",filename," exists in filesystem",bcolors.ENDC) + labels=list() + fi = open(filename, "r") + readerIn = csv.reader( fi , delimiter =',', skipinitialspace=True) + for rowIn in readerIn: + numberOfColumns=len(rowIn) + labels = list(rowIn[i] for i in range(0,numberOfColumns) ) + break; + fi.close() + + #-------------------------------------------------------------- + originalRetrievedSize = len(labels) + print(bcolors.OKGREEN,"it has ",originalRetrievedSize," elements",bcolors.ENDC) + #Remove any empty occurances.. + try: + labels.remove('') + except: + print("CSV files doesnt seem to have blanks") + + if (originalRetrievedSize!=len(labels)): + print(bcolors.FAIL,"Incorrect CSV line endings..",bcolors.ENDC) + print(bcolors.FAIL,"Check if `head -n 1 ",filename,"` ends with a , ",bcolors.ENDC) + #-------------------------------------------------------------- + + return labels + +#--------------------------------------------------------------------------------------------------------------------------------------------- +#--------------------------------------------------------------------------------------------------------------------------------------------- + +""" + writeCSVFile Reads a CSV file where the body is floats.! +""" +def writeCSVFile(filename,data): + f = open(filename, 'w') + labels = data['label'] + body = data['body'] + #Write header.. + #------------------------------------------------------------------------ + for column in range(len(data['label'])): + if (column>0): + f.write(',') + f.write("%s"%(data['label'][column])) + f.write('\n') + #------------------------------------------------------------------------ + #Write body.. + #------------------------------------------------------------------------ + bodyLength = data['body'].shape[0] + for frame in range(bodyLength): + for column in range(len(data['label'])): + if (column>0): + f.write(',') + f.write("%f"%(data['body'][frame][column])) + f.write('\n') + #------------------------------------------------------------------------ + f.close() + + + + +""" + readCSVFile Reads a CSV file where the body is floats.! +""" +def readCSVFile(filename,memPercentage=1.0,useHalfFloats=False): + print("CSV file :",filename,"..\n") + + if (not checkIfFileExists(filename)): + print( bcolors.FAIL + "Input file "+filename+" does not exist, cannot read ground truth.." + bcolors.ENDC) + print("Current Directory was "+os.getcwd()) + return dict() + start = time.time() + + dtypeSelected=np.dtype(np.float32) + dtypeSelectedByteSize=int(dtypeSelected.itemsize) + if (useHalfFloats): + dtypeSelected=np.dtype(np.float16) + dtypeSelectedByteSize=int(dtypeSelected.itemsize) + + progress=0.0 + sampleNumber=0 + receivedHeader=False + inputNumberOfColumns=0 + outputNumberOfColumns=0 + + inputLabels=list() + + #------------------------------------------------------------------------------------------------- + numberOfSamplesInput=getNumberOfLines(filename)-1 + print(" Input file has ",numberOfSamplesInput," training samples\n") + #------------------------------------------------------------------------------------------------- + + + numberOfSamples = numberOfSamplesInput + numberOfSamplesLimit=int(numberOfSamples*memPercentage) + #------------------------------------------------------------------------------------------------- + if (memPercentage==0.0): + print("readCSVFile was asked to occupy 0 memory so this probably means we just want one record") + numberOfSamplesLimit=2 + if (memPercentage>1.0): + print("Memory Limit will be interpreted as a raw value..") + numberOfSamplesLimit=int(memPercentage) + #------------------------------------------------------------------------------------------------- + + + #--------------------------------- + thisInput = array.array('f') + #--------------------------------- + + fi = open(filename, "r") + readerIn = csv.reader( fi , delimiter =',', skipinitialspace=True) + for rowIn in readerIn: + #------------------------------------------------------ + if (not receivedHeader): #use header to get labels + #------------------------------------------------------ + inputNumberOfColumns=len(rowIn) + inputLabels = list(rowIn[i] for i in range(0,inputNumberOfColumns) ) + print("Number of Input elements : ",len(inputLabels)) + #------------------------------------------------------ + + if (memPercentage==0): + print("Will only return labels\n") + return {'label':inputLabels}; + + #--------------------------------- + # Allocate Lists + #--------------------------------- + for i in range(inputNumberOfColumns): + thisInput.append(np.float32(0.0)) + #--------------------------------- + + + #--------------------------------- + # Allocate Numpy Arrays + #--------------------------------- + inputSize=0 + startCompressed=0 + inputSize=inputNumberOfColumns + startCompressed=inputNumberOfColumns + + npInputBytesize=0+numberOfSamplesLimit * inputSize * dtypeSelectedByteSize + print(" Input file on disk has a shape of [",numberOfSamples,",",inputSize,"]") + print(" Input we will read has a shape of [",numberOfSamplesLimit,",",inputSize,"]") + print(" Input will occupy ",convert_bytes(npInputBytesize)," of RAM\n") + npInput = np.full([numberOfSamplesLimit,inputSize],fill_value=0,dtype=dtypeSelected,order='C') + #---------------------------------------------------------------------------------------------------------- + receivedHeader=True + #---------------------------------------------------------------------------------------------------------- + else: + #------------------------------------------- + # First convert our string INPUT to floats + #------------------------------------------- + for i in range(inputNumberOfColumns): + try: + thisInput[i]=np.float32(rowIn[i]) + except: + print("Could not parse ",rowIn[i]," to a float ") + thisInput[i]=np.float32(0.0) + #------------------------------------------- + for num in range(0,inputNumberOfColumns): + npInput[sampleNumber,num]=np.float32(thisInput[num]); + #------------------------------------------- + sampleNumber=sampleNumber+1 + + if (numberOfSamples>0): + progress=sampleNumber/numberOfSamplesLimit + + if (sampleNumber%1000==0) : + progressString = "%0.2f"%float(100*progress) + print("\rReading from disk (",sampleNumber,") - ",progressString," % \r", end="", flush=True) + + if (numberOfSamplesLimit<=sampleNumber): + print("\rStopping reading file to obey memory limit given by parameter --mem ",memPercentage,"\n") + break + #------------------------------------------- + fi.close() + del readerIn + gc.collect() + + + print("\n read, Samples: ",sampleNumber,", was expecting ",numberOfSamples," samples\n") + print(npInput.shape) + + totalNumberOfBytes=npInput.nbytes; + totalNumberOfGigaBytes=totalNumberOfBytes/1073741824; + print("GPU Size Occupied by data = ",totalNumberOfGigaBytes," GB \n") + + end = time.time() + print("Time elapsed : ",(end-start)/60," mins") + + labelDict = dict() + for i in range(0,len(inputLabels)): + labelDict[inputLabels[i]]=i + #--------------------------------------------------------------------- + return {'label':inputLabels, 'labelLookup':labelDict, 'body':npInput }; + + + +if __name__ == '__main__': + print("readCSV.py is a library it cannot be run standalone") + diff --git a/src/python/mnet4/setup.sh b/src/python/mnet4/setup.sh new file mode 100644 index 0000000..6c1d511 --- /dev/null +++ b/src/python/mnet4/setup.sh @@ -0,0 +1,44 @@ +#!/bin/bash +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "$DIR" + +#Simple dependency checker that will apt-get stuff if something is missing +# sudo apt-get install python3-venv python3-pip +SYSTEM_DEPENDENCIES="python3-venv python3-pip" + +for REQUIRED_PKG in $SYSTEM_DEPENDENCIES +do +PKG_OK=$(dpkg-query -W --showformat='${Status}\n' $REQUIRED_PKG|grep "install ok installed") +echo "Checking for $REQUIRED_PKG: $PKG_OK" +if [ "" = "$PKG_OK" ]; then + + echo "No $REQUIRED_PKG. Setting up $REQUIRED_PKG." + + #If this is uncommented then only packages that are missing will get prompted.. + #sudo apt-get --yes install $REQUIRED_PKG + + #if this is uncommented then if one package is missing then all missing packages are immediately installed.. + sudo apt-get install $SYSTEM_DEPENDENCIES + break +fi +done +#------------------------------------------------------------------------------ + + #ONNX needs protobuf AI need avdevice ? + sudo apt-get install protobuf-compiler libprotobuf-dev libavdevice-dev + + python3 -m venv pythonVirtualEnvironment + source pythonVirtualEnvironment/bin/activate + + python3 -m pip install --upgrade pip #2.8.0 nvidia-tensorrt + python3 -m pip install tensorflow numpy numba tensorboard_plugin_profile tensorflow-model-optimization keras pillow tf2onnx onnxruntime onnx matplotlib pydot mediapipe opencv-python scikit-learn + + echo "To enable Tensorflow profiling for non-root users please add : " + echo "options nvidia \"NVreg_RestrictProfilingToAdminUsers=0\"" + echo "to /etc/modprobe.d/nvidia-graphics-drivers.conf" + echo " " + echo "Ready for use" + echo "From now on to work with MocapNET you can use : " + echo "source pythonVirtualEnvironment/bin/activate" + echo "python3 mediapipeHolisticWebcamMocapNET.py --from webcam" + diff --git a/src/python/mnet4/sobolRandomDatasetGenerator.py b/src/python/mnet4/sobolRandomDatasetGenerator.py new file mode 100644 index 0000000..8bfdb94 --- /dev/null +++ b/src/python/mnet4/sobolRandomDatasetGenerator.py @@ -0,0 +1,542 @@ +from scipy.stats import qmc +import numpy as np +import random + + +""" +python3 sobolRandomDatasetGenerator.py --upperbody --exponent 21 +rm dataset/generated/2d_upperbody_all.csv dataset/generated/bvh_upperbody_all.csv +./GroundTruthDumper --from dataset/headerWithHeadAndOneMotion.bvh --importCSVPoses sobolUpperbody_2097152.csv --filterOccludedJoints --csv dataset/generated/ upperbody_all.csv 2d+3d+bvh + +python3 sobolRandomDatasetGenerator.py --lowerbody --exponent 21 +rm dataset/generated/2d_lowerbody_all.csv dataset/generated/bvh_lowerbody_all.csv +./GroundTruthDumper --from dataset/headerWithHeadAndOneMotion.bvh --importCSVPoses sobolLowerbody_2097152.csv --csv dataset/generated/ lowerbody_all.csv 2d+3d+bvh + +python3 sobolRandomDatasetGenerator.py --lhand --exponent 21 +rm dataset/generated/2d_lhand_all.csv dataset/generated/bvh_lhand_all.csv +./GroundTruthDumper --from dataset/lhand.qbvh --importCSVPoses sobolLHand_2097152.csv --csv dataset/generated/ lhand_all.csv 2d+3d+bvh + +scp -P 2234 dataset/generated/2d_upperbody_all.csv dataset/generated/bvh_upperbody_all.csv dataset/generated/2d_lowerbody_all.csv dataset/generated/bvh_lowerbody_all.csv dataset/generated/2d_lhand_all.csv dataset/generated/bvh_lhand_all.csv ammar@cvrldemo:/home/ammar/Documents/Programming/DNNTracker/tensorflow2GPU/src/dataset/generated +""" + +#python3 -m csvNET --from SCANREYE.csv --reye --nobody --plot --save +def scanReye(filename): + f = open(filename, 'w') + f.write("2DX_head_reye_0,2DY_head_reye_0,visible_head_reye_0,2DX_head_reye_1,2DY_head_reye_1,visible_head_reye_1,2DX_head_reye_2,2DY_head_reye_2,visible_head_reye_2,2DX_head_reye_3,2DY_head_reye_3,visible_head_reye_3,2DX_head_reye_4,2DY_head_reye_4,visible_head_reye_4,2DX_head_reye_5,2DY_head_reye_5,visible_head_reye_5,2DX_head_nostrills_2,2DY_head_nostrills_2,visible_head_nostrills_2,2DX_head_rchin_0,2DY_head_rchin_0,visible_head_rchin_0,2DX_head_chin,2DY_head_chin,visible_head_chin,2DX_head_reyebrow_0,2DY_head_reyebrow_0,visible_head_reyebrow_0,2DX_head_reyebrow_1,2DY_head_reyebrow_1,visible_head_reyebrow_1,2DX_head_reyebrow_2,2DY_head_reyebrow_2,visible_head_reyebrow_2,2DX_head_reyebrow_3,2DY_head_reyebrow_3,visible_head_reyebrow_3,2DX_head_reyebrow_4,2DY_head_reyebrow_4,visible_head_reyebrow_4,2DX_head_reye,2DY_head_reye,visible_head_reye\n") + + for x in range(710,740): #710 - 750 + for y in range(270,280): #range(270,271): #260 - 290 + xF = float(x / 1000) + yF = float(y / 1000) + f.write("0.706034,0.271945,1,0.713014,0.265075,1,0.724606,0.264025,1,0.734642,0.276731,1,0.723602,0.280737,1,0.71263,0.277377,1,0.755537,0.331723,1,0.688397,0.274243,1,0.749762,0.466904,1,0.690998,0.262801,1,0.703323,0.247993,1,0.713234,0.238449,1,0.731472,0.246738,1,0.742246,0.255172,1,%f,%f,1\n" % (xF,yF) ) + f.close() + import sys + sys.exit(0) + +def getUpperbodyList(): + #----------------------------------- + minima = list() + maxima = list() + joints = list() + constants = dict() + rootDofs = list() + minR = -179.90 + maxR = 179.90 + dof = 0 + #----------------------------------- + #joints.append("hip_Xposition"); minima.append(-340.0); maxima.append(340.0); rootDofs.append(dof); dof+=1 # -529 529 original + #joints.append("hip_Yposition"); minima.append(-80.0); maxima.append(80.0); rootDofs.append(dof); dof+=1 # -207 208 original + #joints.append("hip_Zposition"); minima.append(-500.0); maxima.append(-100.0); rootDofs.append(dof); dof+=1 # -450 -90 original + #------------------------------------------------------------------------------- + #joints.append("hip_Zrotation"); minima.append(-45.0); maxima.append(45.0); rootDofs.append(dof); dof+=1 + #joints.append("hip_Yrotation"); minima.append(minR); maxima.append(maxR); rootDofs.append(dof); dof+=1 + #joints.append("hip_Xrotation"); minima.append(-45.0); maxima.append(45.0); rootDofs.append(dof); dof+=1 + #------------------------------------------------------------------------------- + joints.append("abdomen_Zrotation"); minima.append(-175.82); maxima.append(maxR); dof+=1 + joints.append("abdomen_Xrotation"); minima.append(-87.84); maxima.append(92.43); dof+=1 + joints.append("abdomen_Yrotation"); minima.append(-176.7); maxima.append(173.33); dof+=1 + joints.append("chest_Zrotation"); minima.append(-63.64); maxima.append(59.23); dof+=1 + joints.append("chest_Xrotation"); minima.append(-59.62); maxima.append(42.48); dof+=1 + joints.append("chest_Yrotation"); minima.append(-30.52); maxima.append(34.27); dof+=1 + joints.append("neck1_Zrotation"); minima.append(-74.41); maxima.append(97.04); dof+=1 + joints.append("neck1_Xrotation"); minima.append(-63.63); maxima.append(81.78); dof+=1 + joints.append("neck1_Yrotation"); minima.append(-63.31); maxima.append(71.25); dof+=1 + joints.append("head_Zrotation"); minima.append(minR); maxima.append(maxR); dof+=1 + joints.append("head_Xrotation"); minima.append(-89.24); maxima.append(86.77); dof+=1 + joints.append("head_Yrotation"); minima.append(-178.54); maxima.append(maxR); dof+=1 + #------------------------------------------------------------------------------- + joints.append("rshoulder_Zrotation"); minima.append(minR); maxima.append(maxR); dof+=1 + joints.append("rshoulder_Xrotation"); minima.append(-102.07); maxima.append(103.87); dof+=1 + joints.append("rshoulder_Yrotation"); minima.append(minR); maxima.append(maxR); dof+=1 + joints.append("relbow_Zrotation"); minima.append(-44.67); maxima.append(34.52); dof+=1 + joints.append("relbow_Xrotation"); minima.append(-68.55); maxima.append(9.42); dof+=1 + joints.append("relbow_Yrotation"); minima.append(-110.29); maxima.append(164.34); dof+=1 + joints.append("rhand_Zrotation"); minima.append(-169.3); maxima.append(4.01); dof+=1 + joints.append("rhand_Xrotation"); minima.append(-88.3); maxima.append(85.24); dof+=1 + joints.append("rhand_Yrotation"); minima.append(minR); maxima.append(maxR); dof+=1 + #------------------------------------------------------------------------------- + joints.append("lshoulder_Zrotation"); minima.append(minR); maxima.append(maxR); dof+=1 + joints.append("lshoulder_Xrotation"); minima.append(-103.92); maxima.append(103.45); dof+=1 + joints.append("lshoulder_Yrotation"); minima.append(minR); maxima.append(maxR); dof+=1 + joints.append("lelbow_Zrotation"); minima.append(-14.97); maxima.append(47.34); dof+=1 + joints.append("lelbow_Xrotation"); minima.append(-68.43); maxima.append(8.68); dof+=1 + joints.append("lelbow_Yrotation"); minima.append(-163.07); maxima.append(15.34); dof+=1 + joints.append("lhand_Zrotation"); minima.append(-7.17); maxima.append(172.41); dof+=1 + joints.append("lhand_Xrotation"); minima.append(-88.13); maxima.append(85.43); dof+=1 + joints.append("lhand_Yrotation"); minima.append(minR); maxima.append(maxR); dof+=1 + #----------------------------------- + return minima,maxima,joints,constants,dof,rootDofs + + +#python3 sobolRandomDatasetGenerator.py --exponent 21 --lowerbody --copy +def getLowerbodyList(): + #----------------------------------- + minima = list() + maxima = list() + joints = list() + constants = dict() + rootDofs = list() + minR = -179.90 + maxR = 179.90 + dof = 0 + #------------------------------------------------------------------------------- + #These are no longer needed because they are randomized by the GroundTruthGenerator tool! + #joints.append("hip_Xposition"); minima.append(-340.0); maxima.append(340.0); rootDofs.append(dof); dof+=1 # -529 529 original + #joints.append("hip_Yposition"); minima.append(-80.0); maxima.append(80.0); rootDofs.append(dof); dof+=1 # -207 208 original + #joints.append("hip_Zposition"); minima.append(-500.0); maxima.append(-100.0); rootDofs.append(dof); dof+=1 # -450 -90 original + #------------------------------------------------------------------------------- + #joints.append("hip_Zrotation"); minima.append(-45.0); maxima.append(45.0); rootDofs.append(dof); dof+=1 + #joints.append("hip_Yrotation"); minima.append(minR); maxima.append(maxR); rootDofs.append(dof); dof+=1 + #joints.append("hip_Xrotation"); minima.append(-45.0); maxima.append(45.0); rootDofs.append(dof); dof+=1 + #------------------------------------------------------------------------------- + #joints.append("abdomen_Zrotation"); minima.append(-174.88); maxima.append(174.88); dof+=1 + #joints.append("abdomen_Xrotation"); minima.append(-90.0); maxima.append(90.0); dof+=1 + #joints.append("abdomen_Yrotation"); minima.append(minR); maxima.append(maxR); dof+=1 + #joints.append("chest_Zrotation"); minima.append(-57.0); maxima.append(57.0); dof+=1 + #joints.append("chest_Xrotation"); minima.append(-61.78); maxima.append(40.94); dof+=1 + #joints.append("chest_Yrotation"); minima.append(-35.27); maxima.append(27.58); dof+=1 + #joints.append("neck1_Zrotation"); minima.append(-76.28); maxima.append(97.18); dof+=1 + #joints.append("neck1_Xrotation"); minima.append(-63.08); maxima.append(81.78); dof+=1 + #joints.append("neck1_Yrotation"); minima.append(-63.31); maxima.append(71.64); dof+=1 + #------------------------------------------------------------------------------- + joints.append("rhip_Zrotation"); minima.append(minR); maxima.append(maxR); dof+=1 + joints.append("rhip_Xrotation"); minima.append(-104.0); maxima.append(94.0); dof+=1 #Default -104.11 .. 73.67 + joints.append("rhip_Yrotation"); minima.append(minR); maxima.append(maxR); dof+=1 + joints.append("rknee_Zrotation"); minima.append(minR); maxima.append(maxR); dof+=1 + joints.append("rknee_Xrotation"); minima.append(-66.0); maxima.append(94.0); dof+=1 #Default -66.73 .. 94.07 + joints.append("rknee_Yrotation"); minima.append(minR); maxima.append(maxR); dof+=1 + joints.append("rfoot_Zrotation"); minima.append(minR); maxima.append(maxR); dof+=1 + joints.append("rfoot_Xrotation"); minima.append(-89.0); maxima.append(89.0); dof+=1 #Default -90.86 .. 89.26 + joints.append("rfoot_Yrotation"); minima.append(minR); maxima.append(maxR); dof+=1 + #------------------------------------------------------------------------------- + joints.append("lhip_Zrotation"); minima.append(minR); maxima.append(maxR); dof+=1 + joints.append("lhip_Xrotation"); minima.append(-104.0); maxima.append(94.0); dof+=1 #Default -104.23 .. 93.92 + joints.append("lhip_Yrotation"); minima.append(minR); maxima.append(maxR); dof+=1 + joints.append("lknee_Zrotation"); minima.append(minR); maxima.append(maxR); dof+=1 + joints.append("lknee_Xrotation"); minima.append(-66.0); maxima.append(94.0); dof+=1 #Default -46.36 .. 94.34 + joints.append("lknee_Yrotation"); minima.append(minR); maxima.append(maxR); dof+=1 + joints.append("lfoot_Zrotation"); minima.append(minR); maxima.append(maxR); dof+=1 + joints.append("lfoot_Xrotation"); minima.append(-89.0); maxima.append(89.0); dof+=1 #Default -89.38 .. 91.81 + joints.append("lfoot_Yrotation"); minima.append(minR); maxima.append(maxR); dof+=1 + #----------------------------------- + return minima,maxima,joints,constants,dof,rootDofs + + + + +def getFullFaceList(): + #----------------------------------- + minima = list() + maxima = list() + joints = list() + constants = dict() + rootDofs = list() + dof = 0 + #/home/ammar/Programs/blender-3.4.1-linux-x64/3.4/scripts/addons/io_anim_bvh/import_bvh.py + #----------------------------------- + joints.append("hip_Xposition"); minima.append(-0.24); maxima.append(0.24); rootDofs.append(dof); dof+=1 + joints.append("hip_Yposition"); minima.append(-0.10); maxima.append(0.10); rootDofs.append(dof); dof+=1 + joints.append("hip_Zposition"); minima.append(-2.4); maxima.append(-1.0); rootDofs.append(dof); dof+=1 + joints.append("neck1_Zrotation"); minima.append(-20.0); maxima.append(20.0); rootDofs.append(dof); dof+=1 + joints.append("neck1_Xrotation"); minima.append(-20.0); maxima.append(20.0); rootDofs.append(dof); dof+=1 + joints.append("neck1_Yrotation"); minima.append(-30.0); maxima.append(30.0); rootDofs.append(dof); dof+=1 + joints.append("eye.R_Zrotation"); minima.append(-45.36); maxima.append(45.36); dof+=1 + joints.append("eye.R_Xrotation"); minima.append(-10.0); maxima.append(16.0); dof+=1 + #Let's assume eyes move together.. + #joints.append("eye.L_Zrotation"); minima.append(-20.0); maxima.append(20.0) + #joints.append("eye.L_Xrotation"); minima.append(-7.0); maxima.append(20.0) + joints.append("oculi01.R_Zrotation"); minima.append(-20.0); maxima.append(20.0); dof+=1 + joints.append("oculi01.L_Zrotation"); minima.append(-20.0); maxima.append(20.0); dof+=1 + joints.append("orbicularis03.R_Xrotation"); minima.append(-15.0); maxima.append(15.0); dof+=1 + joints.append("orbicularis03.L_Xrotation"); minima.append(-15.0); maxima.append(15.0); dof+=1 + #joints.append("orbicularis04.R_Xrotation"); minima.append(-15.0); maxima.append(15.0) #This is flipped orbicularis03.R_Xrotation + #joints.append("orbicularis04.L_Xrotation"); minima.append(-15.0); maxima.append(15.0) #This is flipped orbicularis03.L_Xrotation + joints.append("levator06.L_Xrotation"); minima.append(-9.0); maxima.append(9.0); dof+=1 + #joints.append("levator06.R_Xrotation"); minima.append(-9.0); maxima.append(9.0) #This is levator06.L_Xrotation + joints.append("levator03.L_Zrotation"); minima.append(-8.0); maxima.append(9.0); dof+=1 + #joints.append("levator03.R_Zrotation"); minima.append(-9.0); maxima.append(8.0); dof+=1 #This is flipped levator03.L_Zrotation + joints.append("oris03.L_Zrotation"); minima.append(-30.0); maxima.append(30.0); dof+=1 + joints.append("oris03.R_Zrotation"); minima.append(-30.0); maxima.append(30.0); dof+=1 + joints.append("oris07.L_Zrotation"); minima.append(-30.0); maxima.append(0.0); dof+=1 + joints.append("oris07.R_Zrotation"); minima.append(-30.0); maxima.append(0.0); dof+=1 + joints.append("jaw_Xrotation"); minima.append(-4.0); maxima.append(20.0); dof+=1 + joints.append("jaw_Yrotation"); minima.append(-15.0); maxima.append(15.0); dof+=1 + joints.append("oris04.L_Zrotation"); minima.append(-30.0); maxima.append(0.0); dof+=1 + joints.append("oris04.R_Zrotation"); minima.append(0.0); maxima.append(30.0); dof+=1 + joints.append("oris06.L_Zrotation"); minima.append(-30.0); maxima.append(30.0); dof+=1 + joints.append("oris06.R_Zrotation"); minima.append(-30.0); maxima.append(30.0); dof+=1 + #----------------------------------- + constants["orbicularis03.R_Yrotation"] = 172.0 + constants["orbicularis04.R_Yrotation"] = 172.0 + constants["orbicularis03.L_Yrotation"] = -172.0 + constants["orbicularis04.L_Yrotation"] = 172.0 + constants["levator06.L_Yrotation"] = -247.0 + constants["levator06.R_Yrotation"] = 247.0 + constants["oris03.L_Xrotation"] = -40.0 + constants["oris03.L_Yrotation"] = 172.0 + constants["oris07.L_Yrotation"] = 172.0 + constants["oris03.R_Xrotation"] = -40.0 + constants["oris03.R_Yrotation"] = 179.0 + constants["oris07.R_Yrotation"] = 172.0 + constants["oris05_Xrotation"] = -35.0 + constants["oris05_Yrotation"] = -176.0 + #----------------------------------- + return minima,maxima,joints,constants,dof,rootDofs + + +def getReyeList(): + #----------------------------------- + minima = list() + maxima = list() + joints = list() + constants = dict() + rootDofs = list() + dof = 0 + #----------------------------------- + joints.append("hip_Xposition"); minima.append(-0.24); maxima.append(0.24); rootDofs.append(dof); dof+=1 + joints.append("hip_Yposition"); minima.append(-0.10); maxima.append(0.10); rootDofs.append(dof); dof+=1 + joints.append("hip_Zposition"); minima.append(-2.3); maxima.append(-1.0); rootDofs.append(dof); dof+=1 + joints.append("neck1_Zrotation"); minima.append(-20.0); maxima.append(20.0); rootDofs.append(dof); dof+=1 + joints.append("neck1_Xrotation"); minima.append(-20.0); maxima.append(20.0); rootDofs.append(dof); dof+=1 + joints.append("neck1_Yrotation"); minima.append(-30.0); maxima.append(30.0); rootDofs.append(dof); dof+=1 + joints.append("eye.R_Zrotation"); minima.append(-45.36); maxima.append(45.36); dof+=1 + joints.append("eye.R_Xrotation"); minima.append(-10.0); maxima.append(16.0); dof+=1 + joints.append("oculi01.R_Zrotation"); minima.append(-20.0); maxima.append(20.0); dof+=1 + joints.append("orbicularis03.R_Xrotation"); minima.append(-15.0); maxima.append(15.0); dof+=1 + joints.append("jaw_Xrotation"); minima.append(-4.0); maxima.append(20.0); dof+=1 #Reason being adding some robustness + joints.append("jaw_Yrotation"); minima.append(-15.0); maxima.append(15.0); dof+=1 #Reason being adding some chin robustness + #----------------------------------- + constants["orbicularis03.R_Yrotation"] = 172.0 + constants["orbicularis04.R_Yrotation"] = 172.0 + #----------------------------------- + return minima,maxima,joints,constants,dof,rootDofs + + +def getMouthList(): + #----------------------------------- + minima = list() + maxima = list() + joints = list() + constants = dict() + rootDofs = list() + dof = 0 + #----------------------------------- + joints.append("hip_Xposition"); minima.append(-0.24); maxima.append(0.24); rootDofs.append(dof); dof+=1 + joints.append("hip_Yposition"); minima.append(-0.10); maxima.append(0.10); rootDofs.append(dof); dof+=1 + joints.append("hip_Zposition"); minima.append(-2.4); maxima.append(-1.0); rootDofs.append(dof); dof+=1 + joints.append("neck1_Zrotation"); minima.append(-20.0); maxima.append(20.0); rootDofs.append(dof); dof+=1 + joints.append("neck1_Xrotation"); minima.append(-20.0); maxima.append(20.0); rootDofs.append(dof); dof+=1 + joints.append("neck1_Yrotation"); minima.append(-30.0); maxima.append(30.0); rootDofs.append(dof); dof+=1 + joints.append("levator06.L_Xrotation"); minima.append(-9.0); maxima.append(9.0); dof+=1 + #joints.append("levator06.R_Xrotation"); minima.append(-9.0); maxima.append(9.0); dof+=1 #This is levator06.L_Xrotation + joints.append("levator03.L_Zrotation"); minima.append(-8.0); maxima.append(9.0); dof+=1 + #joints.append("levator03.R_Zrotation"); minima.append(-9.0); maxima.append(8.0); dof+=1 #This is flipped levator03.L_Zrotation + joints.append("oris03.L_Zrotation"); minima.append(-30.0); maxima.append(30.0); dof+=1 + joints.append("oris03.R_Zrotation"); minima.append(-30.0); maxima.append(30.0); dof+=1 + joints.append("oris07.L_Zrotation"); minima.append(-30.0); maxima.append(0.0); dof+=1 + joints.append("oris07.R_Zrotation"); minima.append(-30.0); maxima.append(0.0); dof+=1 + joints.append("jaw_Xrotation"); minima.append(-4.0); maxima.append(20.0); dof+=1 + joints.append("jaw_Yrotation"); minima.append(-15.0); maxima.append(15.0); dof+=1 + joints.append("oris04.L_Zrotation"); minima.append(-30.0); maxima.append(0.0); dof+=1 + joints.append("oris04.R_Zrotation"); minima.append(0.0); maxima.append(30.0); dof+=1 + joints.append("oris06.L_Zrotation"); minima.append(-30.0); maxima.append(30.0); dof+=1 + joints.append("oris06.R_Zrotation"); minima.append(-30.0); maxima.append(30.0); dof+=1 + #----------------------------------- + constants["levator06.L_Yrotation"] = -247.0 + constants["levator06.R_Yrotation"] = 247.0 + constants["oris03.L_Xrotation"] = -40.0 + constants["oris03.L_Yrotation"] = 172.0 + constants["oris07.L_Yrotation"] = 172.0 + constants["oris03.R_Xrotation"] = -40.0 + constants["oris03.R_Yrotation"] = 179.0 + constants["oris07.R_Yrotation"] = 172.0 + constants["oris05_Xrotation"] = -35.0 + constants["oris05_Yrotation"] = -176.0 + #----------------------------------- + return minima,maxima,joints,constants,dof,rootDofs + + + +def getLHandList(): + #-------------------------------------- + minima = list() + maxima = list() + joints = list() + constants = dict() + rootDofs = list() + dof = 0 + #-------------------------------------- + joints.append("lhand_Xposition"); minima.append(-120.0); maxima.append(120.0); rootDofs.append(dof); dof+=1 #230 + joints.append("lhand_Yposition"); minima.append(-60.0); maxima.append(60.0); rootDofs.append(dof); dof+=1 #92 + joints.append("lhand_Zposition"); minima.append(-250.0); maxima.append(-75.0); rootDofs.append(dof); dof+=1 + #-------------------------------------- + joints.append("lhand_Wrotation"); minima.append(-1.0); maxima.append(1.0); rootDofs.append(dof); dof+=1 + joints.append("lhand_Xrotation"); minima.append(-1.0); maxima.append(1.0); rootDofs.append(dof); dof+=1 + joints.append("lhand_Yrotation"); minima.append(-1.0); maxima.append(1.0); rootDofs.append(dof); dof+=1 + joints.append("lhand_Zrotation"); minima.append(-1.0); maxima.append(1.0); rootDofs.append(dof); dof+=1 + #-------------------------------------- + joints.append("finger2-1.l_Zrotation"); minima.append(-90.0); maxima.append(10.0); dof+=1 + joints.append("finger2-1.l_Yrotation"); minima.append(-20.0); maxima.append(20.0); dof+=1 + joints.append("finger2-2.l_Zrotation"); minima.append(-90.0); maxima.append(0.0); dof+=1 + joints.append("finger2-3.l_Zrotation"); minima.append(-45.0); maxima.append(0.0); dof+=1 + #-------------------------------------- + joints.append("finger3-1.l_Zrotation"); minima.append(-90.0); maxima.append(10.0); dof+=1 + joints.append("finger3-1.l_Yrotation"); minima.append(-10.0); maxima.append(10.0); dof+=1 + joints.append("finger3-2.l_Zrotation"); minima.append(-90.0); maxima.append(0.0); dof+=1 + joints.append("finger3-3.l_Zrotation"); minima.append(-45.0); maxima.append(0.0); dof+=1 + #-------------------------------------- + joints.append("finger4-1.l_Zrotation"); minima.append(-90.0); maxima.append(10.0); dof+=1 + joints.append("finger4-1.l_Yrotation"); minima.append(-10.0); maxima.append(10.0); dof+=1 + joints.append("finger4-2.l_Zrotation"); minima.append(-90.0); maxima.append(0.0); dof+=1 + joints.append("finger4-3.l_Zrotation"); minima.append(-45.0); maxima.append(0.0); dof+=1 + #-------------------------------------- + joints.append("finger5-1.l_Zrotation"); minima.append(-90.0); maxima.append(10.0); dof+=1 + joints.append("finger5-1.l_Yrotation"); minima.append(-8.0); maxima.append(25.0); dof+=1 + joints.append("finger5-2.l_Zrotation"); minima.append(-90.0); maxima.append(0.0); dof+=1 + joints.append("finger5-3.l_Zrotation"); minima.append(-45.0); maxima.append(0.0); dof+=1 + #-------------------------------------- + joints.append("lthumbBase_Zrotation"); minima.append(0.0); maxima.append(60.0); dof+=1 + joints.append("lthumbBase_Xrotation"); minima.append(-35.0); maxima.append(0.0); dof+=1 + joints.append("lthumbBase_Yrotation"); minima.append(0.0); maxima.append(60.0); dof+=1 + #-------------------------------------- + joints.append("lthumb_Zrotation"); minima.append(-85.0); maxima.append(85.0); dof+=1 + joints.append("lthumb_Xrotation"); minima.append(-30.0); maxima.append(48.0); dof+=1 + joints.append("lthumb_Yrotation"); minima.append(0.0); maxima.append(85.0); dof+=1 + #-------------------------------------- + joints.append("finger1-2.l_Zrotation"); minima.append(-35.0); maxima.append(0.0); dof+=1 + joints.append("finger1-2.l_Xrotation"); minima.append(-40.0); maxima.append(45.0); dof+=1 + joints.append("finger1-2.l_Yrotation"); minima.append(-70.0); maxima.append(35.0); dof+=1 + #-------------------------------------- + joints.append("finger1-3.l_Zrotation"); minima.append(-50.0); maxima.append(0.0); dof+=1 + joints.append("finger1-3.l_Xrotation"); minima.append(0.0); maxima.append(50.0); dof+=1 + #-------------------------------------- + return minima,maxima,joints,constants,dof,rootDofs + + + +#------------------------------------------ +# Generate actual samples.. +#------------------------------------------ +minima = list() +maxima = list() +joints = list() +constants = dict() +rootDofs = list() +dof = 0 + +datasetPart = "Face" +exponent = 17 +doPlots = False +doFinalCopy = False +neutralSamples = 0 + +#---------------- +# Exponent +#---------------- +# 14 = 16.384 +# 15 = 32.768 +# 16 = 65.536 +# 17 = 131.072 +# 18 = 262.144 +# 19 = 524.288 +# 20 = 1.048.576 +# 21 = 2.097.152 +# 22 = 4.194.304 +# 23 = 8.388.608 +# 24 = 16.777.216 +#---------------- + +import sys +mem = 1.0 +if (len(sys.argv)>1): + print('Argument List:', str(sys.argv)) + for i in range(0, len(sys.argv)): + if (sys.argv[i]=="--scanreye"): + scanReye("SCANREYE.csv") + if (sys.argv[i]=="--copy"): + doFinalCopy = True + if (sys.argv[i]=="--plot"): + doPlots = True + if (sys.argv[i]=="--neutral"): + neutralSamples = int(sys.argv[i+1]) + elif (sys.argv[i]=="--exponent"): + exponent = int(sys.argv[i+1]) + elif (sys.argv[i]=="--upperbody"): + datasetPart = "Upperbody" + minima,maxima,joints,constants,dof,rootDofs = getUpperbodyList() + elif (sys.argv[i]=="--lowerbody"): + datasetPart = "Lowerbody" + minima,maxima,joints,constants,dof,rootDofs = getLowerbodyList() + elif (sys.argv[i]=="--lhand"): + datasetPart = "LHand" + minima,maxima,joints,constants,dof,rootDofs = getLHandList() + elif (sys.argv[i]=="--reye"): + datasetPart = "REye" + minima,maxima,joints,constants,dof,rootDofs = getReyeList() + elif (sys.argv[i]=="--mouth"): + datasetPart = "Mouth" + minima,maxima,joints,constants,dof,rootDofs = getMouthList() + + + +if (len(minima)==0) and (len(maxima)==0) and (len(joints)==0): + datasetPart = "Face" + minima,maxima,joints,constants,dof,rootDofs = getFullFaceList() + + +numberOfDimensions = len(joints) +numberOfSamples = 2**exponent +filename = "sobol%s_%u.csv" % (datasetPart,numberOfSamples+neutralSamples) +#------------------------------------------ + + +print("Will generate ",numberOfSamples," samples ") +print("Will generate random numbers for ",numberOfDimensions," dimensions") +print("We have ",dof," degrees of freedom") + +samplesPerDimension = np.power(numberOfSamples,1/dof) +print("Samples per Dimension : %0.2f "%samplesPerDimension) + + + +print("Involved joints are : ",joints) +sampler = qmc.Sobol(d=numberOfDimensions, scramble=False) +sample = sampler.random_base2(m=exponent) +print("Numbers generated now storing them to ",filename) +#print(sample) + + +#========================================= +# Write label +#========================================= +f = open(filename, 'w') +recordsWritten=0 +#----------------------------------------- +for item in range(0,numberOfDimensions): + if (recordsWritten>0): + f.write(',') + #f.write("dim%u"%(recordsWritten)) + f.write("%s" % (joints[item])) + recordsWritten=recordsWritten+1 +#----------------------------------------- +if (constants): + for key in constants.keys(): + if (recordsWritten>0): + f.write(',') + #f.write("dim%u"%(recordsWritten)) + f.write("%s" % (joints[item])) + recordsWritten=recordsWritten+1 +#----------------------------------------- +f.write("\n") +#========================================= + + +#========================================= +# Write Sobol Samples +#========================================= +for sampleNumber in range(0,numberOfSamples): + recordsWritten=0 + for item in range(0,numberOfDimensions): + if (recordsWritten>0): + f.write(',') + thisRange = maxima[item]-minima[item] + f.write("%f"%( (thisRange * sample[sampleNumber][item]) + minima[item]) ) + recordsWritten=recordsWritten+1 + if (constants): + for key in constants.keys(): + if (recordsWritten>0): + f.write(',') + f.write("%f"%(constants[key])) + recordsWritten=recordsWritten+1 + f.write("\n") +#========================================= + + +#========================================= +# Write Neutral Samples +#========================================= +if (neutralSamples>0): + print("We will also append %u neutral samples " % neutralSamples) +#========================================= + for sampleNumber in range(0,neutralSamples): + recordsWritten=0 + for item in range(0,numberOfDimensions): + if (recordsWritten>0): + f.write(',') + + if (item in rootDofs): + #Poor quality uniform random value + value = random.uniform(minima[item],maxima[item]) + else: + #True Neutral mean value + value = float((maxima[item]+minima[item]) / 2) + f.write("%f" % value) + recordsWritten=recordsWritten+1 + if (constants): + for key in constants.keys(): + if (recordsWritten>0): + f.write(',') + f.write("%f"%(constants[key])) + recordsWritten=recordsWritten+1 + f.write("\n") +#========================================= + + +f.close() + +import os +#------------------------------ +if (doPlots): + os.system("python3 plotCSV.py --simple %s "%filename) +#------------------------------ +command1 = "" +command2 = "" +havePayload = False +minOrientation = -179.90 +maxOrientation = 179.90 +#------------------------------ +if (datasetPart == "LHand"): + command1 = "rm dataset/generated/2d_lhand_all.csv dataset/generated/bvh_lhand_all.csv" + command2 = "./GroundTruthDumper --from dataset/lhand.qbvh --importCSVPoses %s --randomize2D 200 2500 -179.0 -179.0 -179.0 179.0 179.0 179.0 --selectJoints 1 17 lHand finger5-1.l finger5-2.l finger5-3.l finger4-1.l finger4-2.l finger4-3.l finger3-1.l finger3-2.l finger3-3.l finger2-1.l finger2-2.l finger2-3.l lthumbBase lthumb finger1-2.l finger1-3.l --hide2DLocationOfJoints 0 1 lthumbBase --csv dataset/generated/ lhand_all.csv 2d+bvh " % (filename) + havePayload = True +elif (datasetPart == "Upperbody"): + command1 = "rm dataset/generated/2d_upperbody_all.csv dataset/generated/bvh_upperbody_all.csv" + command2 = "./GroundTruthDumper --from dataset/headerWithHeadAndOneMotion.bvh --importCSVPoses %s --randomize2D 1000 4500 -45 %0.2f -45 45 %0.2f 45 --selectJoints 1 13 hip eye.r eye.l abdomen chest neck head rshoulder relbow rhand lshoulder lelbow lhand --hide2DLocationOfJoints 0 4 abdomen chest eye.r eye.l --csv dataset/generated/ upperbody_all.csv 2d+bvh " % (filename,minOrientation,maxOrientation) + havePayload = True +elif (datasetPart == "Lowerbody"): + command1 = "rm dataset/generated/2d_lowerbody_all.csv dataset/generated/bvh_lowerbody_all.csv" + command2 = "./GroundTruthDumper --from dataset/headerWithHeadAndOneMotion.bvh --importCSVPoses %s --randomize2D 1000 4500 -45 %0.2f -45 45 %0.2f 45 --selectJoints 1 14 hip abdomen chest neck rhip rknee rfoot lhip lknee lfoot toe1-2.r toe5-3.r toe1-2.l toe5-3.l --hide2DLocationOfJoints 0 6 abdomen chest toe1-2.r toe5-3.r toe1-2.l toe5-3.l --csv dataset/generated/ lowerbody_all.csv 2d+bvh " % (filename,minOrientation,maxOrientation) + havePayload = True +#------------------------------ +if (havePayload): + if (doFinalCopy): + os.system(command1) + os.system(command2) + else: + print("To copy data, please execute : ") + print(command1) + print(command2) +#------------------------------ + + + diff --git a/src/python/mnet4/tools.py b/src/python/mnet4/tools.py new file mode 100755 index 0000000..3bcdad6 --- /dev/null +++ b/src/python/mnet4/tools.py @@ -0,0 +1,585 @@ +#!/usr/bin/python3 + +import os +import sys + +import numpy as np +from numba import njit #Test + +""" +Author : "Ammar Qammaz" +Copyright : "2022 Foundation of Research and Technology, Computer Science Department Greece, See license.txt" +License : "FORTH" +""" + +class bcolors: + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKGREEN = '\033[92m' + WARNING = '\033[93m' + FAIL = '\033[91m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + +""" +Convert seconds to Hertz +""" +def secondsToHz(seconds): + if (seconds==0.0): + seconds=1.0 + return float(1 / seconds) + + + +@njit +def optimizedStD(data,mean,N): + #JiT compilation of this in place loop to speed it up + M2 = np.float32(0.0) + for row in data: + for x in row: + delta = x - mean + M2 += delta**2 + return np.sqrt(M2 / N) + + +def calculateStandardDeviationInPlaceKnowingMean(data,mean): + N = data.shape[0] * data.shape[1] + if (N==0): + return np.NAN + #---------------------------------- + return optimizedStD(data,np.float32(mean),N) + #---------------------------------- + #M2 = 0.0 + #for row in data: + # for x in row: + # delta = x - mean + # M2 += delta**2 + #return np.sqrt(M2 / N) + +def convertStandardDeviationToVariance(std): + return std ** 2 + + + + + + +""" + This is a VERY commonly needed conversion, MocapNET is trained on a 1920x1080 frame to match popular cameras, + many image sources/sensors produce different kinds of image resolutions. The aspect ratio is very important + for good results, this function does the needed conversion.. +""" +def transform_coordinate_wrt_target(x, y, width, height, targetWidth = 1920, targetHeight = 1080): + aspect_ratio = width / height + + if aspect_ratio > targetWidth / targetHeight: + # The image is wider than the frame + scaled_width = targetWidth + scaled_height = int(targetWidth / aspect_ratio) + offset_x = 0 + offset_y = (targetHeight - scaled_height) // 2 + else: + # The image is taller than the frame + scaled_width = int(targetHeight * aspect_ratio) + scaled_height = targetHeight + offset_x = (targetWidth - scaled_width) // 2 + offset_y = 0 + + new_x = (x / width) * scaled_width + offset_x + new_y = (y / height) * scaled_height + offset_y + return new_x, new_y + + + + + +""" +Split joint names based on configuration +""" +def splitTextBasedOnGroupNumber(groupNumber,sourceFile,targetFile): + import sys + #This function used to be this one liner.. + #However some joints have additional '-' characters e.g. 3DZ_toe1-2.r so this whole next codeblock + #attempts to properly split them + #import os + #os.system('cat %s | sed \'s/-/\\n/g\' > %s' % (sourceFile,targetFile)) + #return + #-------------------------------------------------------------------------------------------------------- + if (groupNumber==0): + print(bcolors.FAIL," Cant split to ",groupNumber," groups ",bcolors.ENDC) + sys.exit(1) + #-------------------------------------------------------------------------------------------------------- + fileR = open(sourceFile, 'r') + fileW = open(targetFile, 'w') + #-------------------------------------------------------------------------------------------------------- + Lines = fileR.readlines() + for line in Lines: + lineNoNL = line.strip() + split = lineNoNL.split('-') + standaloneJoints = list() + if (lineNoNL!=""): + if (len(split)==groupNumber): + for joint in split: + standaloneJoints.append(joint) + elif (len(split) 3DX_tag +""" +def capitalizeCoordinateTags(inputs): + out = dict() + keysList = list(inputs.keys()) + for key in keysList: + keyOriginal = key + keyNew = "" + convert = False + tagList = key.split('_') + tag = tagList[0] + if (tag=="3dx"): + convert = True + keyNew="3DX_"+key[4:] + if (tag=="3dy"): + convert = True + keyNew="3DY_"+key[4:] + if (tag=="3dz"): + convert = True + keyNew="3DZ_"+key[4:] + + if (convert): + #print("Convert ",keyOriginal," to ",keyNew) + out[keyNew]=inputs[keyOriginal] + else: + out[keyOriginal]=inputs[keyOriginal] + + return out + + + +""" +Model names will cause errors if they contain invalid characters, so we make sure everything +is named in accordance with tensorflow rules +""" +def tensorflowFriendlyModelName(rawName): + #Avoiding "Tensorflow %s is not valid scope name error" when creating a model + #'-', '\', '/', or '_' + filteredName = rawName + filteredName = filteredName.replace("-", ".") + filteredName = filteredName.replace("_", ".") + filteredName = filteredName.replace("/", ".") + filteredName = filteredName.replace("\\", ".") + return filteredName + +""" +Send a system notification that will travel to all connected devices +""" +def notification(title,message): + #This needs a more proper sanitization! + title = title.strip("\"'") + message = message.strip("\"'") + try: + os.system('notify-send -a \"%s\" \"%s\"&' % (title,message)) + except: + print("NOTIFICATION : ",title,message) + +""" +Bytes to KB/MB/GB/TB converter +""" +def convert_bytes(num): + """ + this function will convert bytes to MB.... GB... etc + """ + step_unit = 1000.0 #1024 bad the size + + for x in ['bytes', 'KB', 'MB', 'GB', 'TB']: + if num < step_unit: + return "%3.1f %s" % (num, x) + num /= step_unit + +""" +Retreive the available system RAM +""" +def getRAMInformation(): + """ + Get node total memory and memory usage + """ + with open('/proc/meminfo', 'r') as mem: + ret = {} + tmp = 0 + for i in mem: + sline = i.split() + if str(sline[0]) == 'MemTotal:': + ret['total'] = int(sline[1]) + elif str(sline[0]) in ('MemFree:', 'Buffers:', 'Cached:'): + tmp += int(sline[1]) + ret['free'] = int(tmp) + ret['used'] = int(ret['total']) - int(ret['free']) + return ret + +""" +Count the number of lines by parsing the file inside python +""" +def getNumberOfLines(filename): + #import socket + #print("Hostname is ",socket.gethostname()) + #if (socket.gethostname()=="ammar-kriti"): + # print(bcolors.FAIL,"RETURNING FIXED NUMBER OF LINES FOR SPEED IN MY SLOW PC",bcolors.ENDC) + # return 3858095 + print("Counting number of lines in file ",filename) + with open(filename) as f: + return sum(1 for line in f) + +""" +Count the number of lines by asking the shell/OS to do it using wc -l +""" +def getNumberOfLinesOS(filename): + #import socket + #print("Hostname is ",socket.gethostname()) + #if (socket.gethostname()=="ammar-kriti"): + # print(bcolors.FAIL,"RETURNING FIXED NUMBER OF LINES FOR SPEED IN MY SLOW PC",bcolors.ENDC) + # return 3858095 + print("Counting number of lines in file ",filename) + import subprocess + #Standard but more compatible + command = ('wc -l %s' % filename) + + p = subprocess.Popen(command, universal_newlines=True,shell=True, stdout=subprocess.PIPE,stderr=subprocess.PIPE) + text = p.stdout.read() + retcode = p.wait() + out = text.split( ) + print("It was ",out[0]) + return int(out[0]) + +""" +Check the number of times a specific keyword *pattern* appears inside the file with the given filename +""" +def getNumberOfOccurancesOS(filename,keyword): + print("Counting number of occurances of ",keyword," in file ",filename) + import subprocess + #Standard but more compatible + command = ('cat %s grep -o %s | wc -l' % (filename,keyword)) + + p = subprocess.Popen(command, universal_newlines=True,shell=True, stdout=subprocess.PIPE,stderr=subprocess.PIPE) + text = p.stdout.read() + retcode = p.wait() + out = text.split() + print("It was ",out[0]) + return int(out[0]) + +""" +Get the size of a file in disk +""" +def getFileSizeInKB(filename): + return int( os.path.getsize(filename)/1024 ) + +""" +Convert list to lowercase +""" +def convertListToLowerCase(theList): + lowercaseList = [item.lower() for item in theList] + return lowercaseList + +""" +Dump a list to a file +""" +def dumpListToFile(filename,theList): + file=open(filename,'w') + for items in theList: + file.writelines(items+'\n') + file.close() + +""" +Read a list from a file +""" +def readListFromFile(filename): + file=open(filename,'r') + Lines = file.readlines() + res = [] + for x in Lines: + res.append(x.replace("\n", "")) + file.close() + print("List : ",filename," has ",len(res)," elements") + return res + + +""" +Dump a labeled mini CSV ( 2 rows ) to a file +""" +def dumpMiniCSVToFile(filename,theLabels,theValues,columns): + #Write CSV outputOffsets.csv + f = open(filename, 'w') + #------------------------------------------------------------------------ + for column in range(columns): + if (column>0): + f.write(',') + f.write("%s"%(theLabels[column])) + f.write('\n') + #------------------------------------------------------------------------ + for column in range(columns): + if (column>0): + f.write(',') + f.write("%f"%(theValues[column])) + f.write('\n') + #------------------------------------------------------------------------ + f.close() + + + +""" +Append a labeled CSV to a file +""" +def appendCSVToFile(filename,theValues,fID=0): + if (fID==0): + f = open(filename, 'w') + #------------------------------------------------------------------------ + for column in range(len(theValues)): + if (column>0): + f.write(',') + f.write("label%u"%(column)) + f.write('\n') + f.close() + #------------------------------------------------------------------------ + #------------------------------------------------------------------------ + f = open(filename, 'a') + #------------------------------------------------------------------------ + for column in range(len(theValues)): + if (column>0): + f.write(',') + f.write("%f"%(theValues[column])) + f.write('\n') + #------------------------------------------------------------------------ + f.close() + + + +def saveCSVFileFromListOfDicts(filename,inputDicts): + labels = list() + #-------------------------- + for frame in inputDicts: + for label in frame.keys(): + if not label in labels: + labels.append(label) + #-------------------------- + f = open(filename, 'w') + #Write header.. + #------------------------------------------------------------------------ + for column in range(len(labels)): + if (column>0): + f.write(',') + f.write("%s"%(labels[column])) + f.write('\n') + #------------------------------------------------------------------------ + #Write body.. + #------------------------------------------------------------------------ + for frame in inputDicts: + for column in range(len(labels)): + if (column>0): + f.write(',') + if (labels[column] in frame): + f.write("%f"%(frame[labels[column]])) + else: + f.write("0") + f.write('\n') + #------------------------------------------------------------------------ + f.close() + + + + +""" +Create a new directory if it does not exist +""" +def createDirectory(path): + if not os.path.exists(path): + os.makedirs(path) + +""" +Check if a file exists +""" +def checkIfFileExists(filename): + return os.path.isfile(filename) + +""" +Check if a path exists +""" +def checkIfPathExists(filename): + return os.path.exists(filename) + + +""" +Check if a path exists +""" +def checkIfPathIsDirectory(filename): + return os.path.isdir(filename) + + +""" +Check if an entry is part of a given list +""" +def checkIfEntryIsInList(theList,theEntry): + for listItem in theList: + if(theEntry==listItem): return 1 + return 0 + + +""" +Check if an entry is part of a given list +""" +def checkIfListsAreTheSame(theListA,theListB): + #---------------------------------------------- + if (len(theListA)!=len(theListB)): + return 0 + #---------------------------------------------- + if (len(theListA)==0) and (len(theListB)==0): + return 1 + #---------------------------------------------- + for i in range(len(theListA)): + if theListA[i]!=theListB[i]: + return 0 + #---------------------------------------------- + return 1 + + + +""" +Check if an entry is part of a given list +""" +def getEntryIndexInList(theList,theEntry): + i=0 + for listItem in theList: + if(theEntry.lower()==listItem.lower()): + return i + i=i+1 + return -1 + + +""" +Check if an entry is in a sublist of our configuration +""" +def checkIfEntryIsInConfigurationKey(configuration,theKey,theEntry): + for listItem in configuration[theKey]: + if(theEntry==listItem): return 1 + return 0 + +""" +Check if a joint is declared in the configuration hierarchy +""" +def getConfigurationJointIsDeclaredInHierarchy(configuration,theEntry): + #------------------------------------------------------------------------------------------- + try: + out = theEntry.split('_') + theEntry=out[0] + except: + print("getConfigurationJointIsDeclaredInHierarchy could not split ",theEntry) + #------------------------------------------------------------------------------------------- + if 'banned' in configuration: + for listItem in configuration['banned']: + if(theEntry.lower()==listItem['output'].lower()): + print(bcolors.WARNING," Joint ",theEntry," is declared in banlist! ",bcolors.ENDC) + return 1 + + if 'hierarchy' in configuration: + for listItem in configuration['hierarchy']: + #print("Check ",theEntry," vs ",listItem['joint']) + if(theEntry.lower()==listItem['joint'].lower()): + print("The Joint ",theEntry," is : declared in hierarchy") + return 1 + + print("Joint is not declared in hierarchy..") + #------------------------------------------------------------------------------------------- + return 0 + + +""" +Retrieve the configuration joint priority of a joint is declared in the configuration hierarchy +""" +def getConfigurationJointPriority(configuration,theEntry): + #------------------------------------------------------------------------------------------- + try: + out = theEntry.split('_') + theEntry=out[0] + except: + print("getConfigurationJointPriority could not split ",theEntry) + #------------------------------------------------------------------------------------------- + if 'outputMode' in configuration: + if (configuration['outputMode'] == "3d"): + print(bcolors.WARNING,"We treat all joints as terribly important in 3D point mode!",bcolors.ENDC) + return 1 + + if 'banned' in configuration: + for listItem in configuration['banned']: + if(theEntry==listItem['output']): + print(bcolors.WARNING," Joint ",theEntry," is in banlist! ",bcolors.ENDC) + return 0 + + if 'hierarchy' in configuration: + for listItem in configuration['hierarchy']: + if(theEntry==listItem['joint']): + print("The Importance of Joint ",theEntry," is : ",listItem['importance']) + return listItem['importance'] + + print("The Importance of Joint ",theEntry," is : 0 ") + #------------------------------------------------------------------------------------------- + return 0 + + +""" +Get the parent network from our configuration joint hierarchy +""" +def getParentNetwork(configuration,theEntry): + #------------------------------------------------------------------------------------------- + try: + out = theEntry.split('_') + theJoint=out[0] + theChannel=out[1] + except: + print("getParentNetwork could not split ",theEntry) + theJoint=theEntry + theChannel=0 + #------------------------------------------------------------------------------------------- + print("Checking the parent of Joint(",theJoint,")/Channel(",theChannel,")") + for listItem in configuration['hierarchy']: + if(theJoint==listItem['joint']): + if (listItem['inheritNetwork']=="none"): + return "none" + parentNetwork="%s_%s" % (listItem['inheritNetwork'],theChannel) + if (parentNetwork==theEntry): + return "none" + else: + return parentNetwork + #------------------------------------------------------------------------------------------- + return "none" + + +if __name__ == '__main__': + print("Tools.py is a library!") + splitTextBasedOnGroupNumber(3,"tmp.tmp","tmpF.tmp") + diff --git a/src/python/mnet4/writeCSV.py b/src/python/mnet4/writeCSV.py new file mode 100755 index 0000000..5494fd3 --- /dev/null +++ b/src/python/mnet4/writeCSV.py @@ -0,0 +1,33 @@ +#!/usr/bin/python3 +import numpy as np +import csv +import gc +import time +import array +import sys + + +def writeCSVFileHeader(filenameOutput,inputListLabels,inputStart,inputEnd): + inputNumber=0 + fileCSV = open(filenameOutput,"w") + for entry in inputListLabels[inputStart:inputEnd]: + #if (inputNumber>=inputStart) and (inputNumber=inputStart) and (inputNumber Date: Mon, 17 Jul 2023 18:20:23 +0300 Subject: [PATCH 003/154] ... --- README.md | 460 +----------------------------------------------------- 1 file changed, 6 insertions(+), 454 deletions(-) diff --git a/README.md b/README.md index a49ae12..f5e255b 100644 --- a/README.md +++ b/README.md @@ -3,460 +3,12 @@ ![MocapNET](https://raw.githubusercontent.com/FORTH-ModelBasedTracker/MocapNET/master/doc/mnet2.png) - - -## News ------------------------------------------------------------------- - -30-12-2022 - -MocapNET has a new [plugin/script](https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/src/python/blender/blender_mocapnet.py) for the [Blender](https://www.blender.org/) 3D editor that when combined with [MPFB2](http://static.makehumancommunity.org/mpfb.html) (the MakeHuman addon for Blender) can make 3D animations using custom skinned humans with the output BVH files of MocapNET with a few clicks. The code targets recent Blender Versions 3.4+ -Watch [this video](https://www.youtube.com/watch?v=9jmTdhVjAsI) to learn how to install MPFB2 and [this video](https://www.youtube.com/watch?v=ooLRUS5j4AI) to learn how to interface the provided plugin in this repository with the MocapNET output BVH file and the generated MakeHuman armature. - -![MocapNET Blender Plugin](https://raw.githubusercontent.com/FORTH-ModelBasedTracker/MocapNET/master/doc/blenderscript.jpg) - -[![YouTube Link](https://raw.githubusercontent.com/FORTH-ModelBasedTracker/MocapNET/master/doc/blenderytb.png)](https://www.youtube.com/watch?v=ooLRUS5j4AI) - - -30-9-2022 - -MocapNET was demonstrated at the [Foundation of Research and technology of Greece](https://www.forth.gr/en/home/) as part of the [European Researcher's Night 2022 event](https://cordis.europa.eu/programme/id/HORIZON_HORIZON-MSCA-2022-CITIZENS-01-01). - -![Researcher's Night 2022](https://raw.githubusercontent.com/FORTH-ModelBasedTracker/MocapNET/master/doc/ren2022.jpg) - -1-6-2022 - -An added [python/mediapipe utility](https://github.com/FORTH-ModelBasedTracker/MocapNET/tree/master/src/python/mediapipe) can now help with generating 2D data for experiments! -This can help you create datasets that include hands that can be processed using [MocapNETv3](https://github.com/FORTH-ModelBasedTracker/MocapNET/tree/mnet3) - - -7-4-2022 - -The open call of [BONSAPPS (https://bonsapps.eu/)](https://bonsapps.eu/) for AI talents received 126 proposals from 31 EU countries. -Out of these proposals, 30 were actually accepted. -Out of the 30 running BONSAPPs projects, 10 were selected yesterday to continue into phase 2. -I am very happy to report that our AUTO-MNET MocapNET based work made it to the top ten! - -![BonsAPPs Hackathon/Stage 2 selection](https://raw.githubusercontent.com/FORTH-ModelBasedTracker/MocapNET/master/doc/bonsappshackathon.jpg) - - -9-3-2022 - -MocapNET was one of the selected projects in the [BonsAPPs Open Call for AI talents](https://bonsapps-1oc-ai-talents.fundingbox.com/) -We are now preparing a version of MocapNET called AUTO-MNET tailored for [3D Body Tracking for automotive uses](https://s3.amazonaws.com/fundingbox-sites/gear%2F1635238346063-BonsAPPs_Guide+for+Applicants+%5BOC1%5D_published.pdf) - -Due to our limited resources this has currently pushed back merging of the [mnet3 branch](https://github.com/FORTH-ModelBasedTracker/MocapNET/tree/mnet3), however hopefully we will soon have a working MocapNET in the [Bonseyes platform](https://beta.bonseyes.com/). - - -8-11-2021 - -MocapNET3 with hand pose estimation support has landed in this repository! The latest version that has been accepted in BMVC2021 is now commited [in the mnet3 branch of this repository](https://github.com/FORTH-ModelBasedTracker/MocapNET/tree/mnet3). Since however there is considerable code-polish missing and currently the 2D joint estimator offered does not contain hands there needs to be a transition to a 2D joint estimator like [Mediapipe Holistic](https://google.github.io/mediapipe/solutions/holistic) for a better live webcam demo. MocapNET3 will appear in [the 32nd British Machine Vision Conference](http://www.bmvc2021.com/) that will be held virtually and is free to attend this year!! - - -An [upgraded 2020 version of MocapNET](https://github.com/FORTH-ModelBasedTracker/MocapNET/milestone/1) has landed! It contains a very big list of improvements that have been carried out during 2020 over the original work that allows higher accuracy, smoother BVH output and better occlusion robustness while maintaining realtime perfomance. MocapNET2 will appear in [the 25th International Conference on Pattern Recognition](https://www.icpr2020.it/) - -If you are interested in the older MocapNET v1 release you can find it in the [mnet1 branch](https://github.com/FORTH-ModelBasedTracker/MocapNET/tree/mnet1), - -Visualization Example: -With MocapNET2 an [RGB video feed like this](https://www.youtube.com/watch?v=Orb4pawcfFY#t=10m) can be converted to BVH motion frames in real-time. The result can be easily used in your favourite 3D engine or application. - -![Sample run](https://raw.githubusercontent.com/FORTH-ModelBasedTracker/MocapNET/master/doc/shuffle.gif) - -Example Output: -| Youtube Video | MocapNET Output | Editing on Blender | -| ------------- | ------------- | ------------- | -| [![YouTube Link](https://raw.githubusercontent.com/FORTH-ModelBasedTracker/MocapNET/master/doc/youtube.png)](https://www.youtube.com/watch?v=GtJct8nKjcc) | [![BVH File](https://raw.githubusercontent.com/FORTH-ModelBasedTracker/MocapNET/master/doc/bvh.png)](http://ammar.gr/mocapnet/mnet2/sept2020version.bvh) | [![Blender Video](https://raw.githubusercontent.com/FORTH-ModelBasedTracker/MocapNET/master/doc/blender.png)](http://ammar.gr/mocapnet/mnet2/sept2020versionBlender.ogv) | - - -## Ensemble of SNN Encoders for 3D Human Pose Estimation in RGB Images ------------------------------------------------------------------- - -We present MocapNET v2, a real-time method that estimates the 3D human pose directly in the popular [Bio Vision Hierarchy (BVH)](https://en.wikipedia.org/wiki/Biovision_Hierarchy) format, given estimations of the 2D body joints originating from monocular color images. - -Our contributions include: - - * A novel and compact 2D pose [NSRM representation](https://www.youtube.com/watch?v=Jgz1MRq-I-k#t=27s). - * A human body orientation classifier and an ensemble of orientation-tuned neural networks that regress the 3D human pose by also allowing for the decomposition of the body to an upper and lower kinematic hierarchy. This permits the recovery of the human pose even in the case of significant occlusions. - * An efficient Inverse Kinematics solver that refines the neural-network-based solution providing 3D human pose estimations that are consistent with the limb sizes of a target person (if known). - -All the above yield a 33\% accuracy improvement on the [Human 3.6 Million (H3.6M)](http://vision.imar.ro/human3.6m/description.php) dataset compared to the baseline method ([MocapNET v1](https://github.com/FORTH-ModelBasedTracker/MocapNET/tree/mnet1)) while maintaining real-time performance (70 fps in CPU-only execution). - - -![MocapNET](https://raw.githubusercontent.com/FORTH-ModelBasedTracker/MocapNET/mnet2/doc/leedsDataset.jpg) - - -## Youtube Videos ------------------------------------------------------------------- - -| BMVC 2021 Supplementary Video | ICPR 2020 Poster Session | -| ------------- | ------------- | -| [![YouTube Link](https://raw.githubusercontent.com/FORTH-ModelBasedTracker/MocapNET/master/doc/BMVC21YoutubeVideo.png) ](https://www.youtube.com/watch?v=aaLOSY_p6Zc) | [![YouTube Link](https://raw.githubusercontent.com/FORTH-ModelBasedTracker/MocapNET/master/doc/ICPR2020_posterYoutubeVideoLink.png) ](https://www.youtube.com/watch?v=mns2s4xUC7c) | - -| ICPR 2020 Supplementary Video | BMVC 2019 Supplementary Video | -| ------------- | ------------- | -| [![YouTube Link](https://raw.githubusercontent.com/FORTH-ModelBasedTracker/MocapNET/master/doc/youtubevideolink2.jpg) ](https://www.youtube.com/watch?v=Jgz1MRq-I-k) | [![YouTube Link](https://raw.githubusercontent.com/FORTH-ModelBasedTracker/MocapNET/master/doc/youtubevideolink.jpg) ](https://www.youtube.com/watch?v=fH5e-KMBvM0) | - ------------------------------------------------------------------- - - - - -## Citation ------------------------------------------------------------------- - -Please cite the following papers [1](http://users.ics.forth.gr/~argyros/mypapers/2021_11_BMVC_Qammaz.pdf), [2](http://users.ics.forth.gr/~argyros/mypapers/2021_01_ICPR_Qammaz.pdf), [3](http://users.ics.forth.gr/~argyros/mypapers/2019_09_BMVC_mocapnet.pdf) according to the part of this work that helps your research : - - - -``` -@inproceedings{Qammaz2021, - author = {Qammaz, Ammar and Argyros, Antonis A}, - title = {Towards Holistic Real-time Human 3D Pose Estimation using MocapNETs}, - booktitle = {British Machine Vision Conference (BMVC 2021)}, - publisher = {BMVA}, - year = {2021}, - month = {November}, - projects = {I.C.HUMANS}, - videolink = {https://www.youtube.com/watch?v=aaLOSY_p6Zc} -} -``` -For the BMVC21 version of MocapNET please [switch to the MNET3 branch](https://github.com/FORTH-ModelBasedTracker/MocapNET/tree/mnet3) - - -``` -@inproceedings{Qammaz2020, - author = {Ammar Qammaz and Antonis A. Argyros}, - title = {Occlusion-tolerant and personalized 3D human pose estimation in RGB images}, - booktitle = {IEEE International Conference on Pattern Recognition (ICPR 2020), (to appear)}, - year = {2021}, - month = {January}, - url = {http://users.ics.forth.gr/argyros/res_mocapnet_II.html}, - projects = {Co4Robots}, - pdflink = {http://users.ics.forth.gr/argyros/mypapers/2021_01_ICPR_Qammaz.pdf}, - videolink = {https://youtu.be/Jgz1MRq-I-k} -} -``` - - -``` -@inproceedings{Qammaz2019, - author = {Qammaz, Ammar and Argyros, Antonis A}, - title = {MocapNET: Ensemble of SNN Encoders for 3D Human Pose Estimation in RGB Images}, - booktitle = {British Machine Vision Conference (BMVC 2019)}, - publisher = {BMVA}, - year = {2019}, - month = {September}, - address = {Cardiff, UK}, - url = {http://users.ics.forth.gr/argyros/res_mocapnet.html}, - projects = {CO4ROBOTS,MINGEI}, - pdflink = {http://users.ics.forth.gr/argyros/mypapers/2019_09_BMVC_mocapnet.pdf}, - videolink = {https://youtu.be/fH5e-KMBvM0} -} -``` - - - -## Overview, System Requirements and Dependencies ------------------------------------------------------------------- -MocapNET is a high performance 2D to 3D single person pose estimator. -This code base targets recent Linux (Ubuntu 18.04 - 20.04 +) machines, and relies on the Tensorflow C-API and OpenCV. Windows 10 users can try the [linux subsystem](https://www.microsoft.com/en-us/p/ubuntu-1804-lts/9n9tngvndl3q?rtc=1&activetab=pivot:overviewtab) that has been also [reported](https://github.com/FORTH-ModelBasedTracker/MocapNET/issues/10) to work. - -Tensorflow is used as the Neural Network framework for our work and OpenCV is used to enable the acquisition of images from webcams or video files as well as to provide an easy visualization method. - -We have provided an [initialization script](https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/initialize.sh) that automatically handles most dependencies, as well as download all needed pretrained models. After running it the application should be ready for use. To examine the neural network .pb files provided you can [download](https://github.com/lutzroeder/netron/releases/) and use [Netron](https://github.com/lutzroeder/netron). - -Any issues not automatically resolved by the script can be reported on the [issues](https://github.com/FORTH-ModelBasedTracker/MocapNET/issues) section of this repository. - -This repository contains 2D joint estimators for the [MocapNET2LiveWebcamDemo](https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/src/MocapNET2/MocapNET2LiveWebcamDemo/livedemo.cpp). By giving it the [correct parameters](https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/src/MocapNET2/MocapNETLib2/applicationLogic/parseCommandlineOptions.cpp#L117) you can switch between a cut-down version of OpenPose (--openpose), VNect (--vnect) or our own MobileNet (default) based 2D joint estimator. All of these are automatically downloaded using the [initialize.sh](https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/initialize.sh) script. However in order to achieve higher accuracy estimations you are advised to set up a full [OpenPose](https://github.com/CMU-Perceptual-Computing-Lab/openpose) instance and use it to acquire JSON files with 2D detections that can be subsequently converted to CSV using [convertOpenPoseJSONToCSV](https://github.com/FORTH-ModelBasedTracker/MocapNET/tree/master/src/MocapNET2/Converters/Openpose) and then to 3D BVH files using the [MocapNET2CSV](https://github.com/FORTH-ModelBasedTracker/MocapNET/tree/master/src/MocapNET2/MocapNETFromCSV) binary. They will provide superior accuracy compared to the bundled 2D joint detectors which are provided for faster performance in the live demo, since 2D estimation is the bottleneck of the application. Our live demo will try to run the 2D Joint estimation on your GPU and MocapNET 3D estimation on the system CPU to achieve a combined framerate of over 30 fps which in most systems matches or surpasses the acquisition rate of web cameras. Unfortunately there are many GPU compatibility issues with Tensorflow C-API builds since recent versions have dropped CUDA 9.0 support as well as compute capabilities that might be required by your system, you can edit the [initialize.sh](https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/initialize.sh) script and change the variable TENSORFLOW_VERSION according to your needs. If you want CUDA 9.0 you should se it to 1.12.0. If you want CUDA 9.0 and have a card with older compute capabilities (5.2) then choose version 1.11.0. If all else fails you can always [recompile the tensorflow C-API](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/tools/lib_package/README.md) to match your specific hardware configuration. You can also [use this script that automates building tensorflow r1.15](https://github.com/AmmarkoV/MyScripts/blob/master/Tensorflow/tensorflowBuild.sh) that might help you, dealing with the Bazel build system and all of its weirdness. Release 1.15 is the final of the 1.x tensorflow tree and is compatible with MocapNET, Tensorflow 2.x is also supported, according to the [Tensorflow site, version 2.3](https://www.tensorflow.org/install/lang_c) is the first version of the 2.x tree to re-include C bindings. The [initialize.sh](https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/initialize.sh) script will ask you which version you want to use and try to download it and set it up locally for your MocapNET installation. - - -If you are interested in generating BVH training data for your research, we have also provided the code that handles randomization and pose perturbation from the CMU dataset. After a successful compilation, dataset generation is accessible using the scripts [scripts/createRandomizedDataset.sh](https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/scripts/createRandomizedDataset.sh) and [scripts/createTestDataset.sh](https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/scripts/createTestDataset.sh). All BVH manipulation code is imported from a secondary [github project](https://github.com/AmmarkoV/RGBDAcquisition/tree/master/opengl_acquisition_shared_library/opengl_depth_and_color_renderer/src/Library/MotionCaptureLoader) that is automatically downloaded, included and built using the [initialize.sh](https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/initialize.sh) script. These [scripts/createRandomizedDataset.sh](https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/scripts/createRandomizedDataset.sh) and [scripts/createTestDataset.sh](https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/scripts/createTestDataset.sh) scripts will populate the dataset/ directory with CSV files that contain valid training samples based on the CMU dataset. It is [trivial](https://pythonspot.com/reading-csv-files-in-python/) to load these files using python. After loading them using them as training samples in conjunction with a deep learning framework like [Keras](https://keras.io/) you can facilitate learning of 2D to 3D BVH. - -## Building the library ------------------------------------------------------------------- - -To download and compile the library issue : - -``` -sudo apt-get install git build-essential cmake libopencv-dev libjpeg-dev libpng-dev libglew-dev libpthread-stubs0-dev - -git clone https://github.com/FORTH-ModelBasedTracker/MocapNET - -cd MocapNET - -./initialize.sh - -``` - -After performing changes to the source code, you do not need to rerun the initialization script. You can recompile the code by using : - -``` -cd build -cmake .. -make -cd .. -``` - - - -## Updating the library ------------------------------------------------------------------- - -The MocapNET library is under active development, the same thing is true for its dependencies. - -In order to update all the relevant parts of the code you can use the [update.sh](https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/update.sh) script provided. - -``` -./update.sh -``` - -If you made changes to the source code that you want to discard and want to revert to the master you can also use the [revert.sh](https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/revert.sh) script provided - -``` -./revert.sh -``` - - - - -## Testing the library and performing benchmarks ------------------------------------------------------------------- - -To test your OpenCV installation as well as support of your webcam issue : -``` -./OpenCVTest --from /dev/video0 -``` - -To test OpenCV support of your video files issue : -``` -./OpenCVTest --from /path/to/yourfile.mp4 -``` - -These tests only use OpenCV (without Tensorflow or any other dependencies) and are intended as a quick method that can identify and debug configuration problems on your system. -In case of problems playing back video files or your webcam you might want to consider compiling OpenCV yourself. The [scripts/getOpenCV.sh](https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/scripts/getOpenCV.sh) script has been included to automatically fetch and make OpenCV for your convinience. The CMake file provided will automatically try to set the OpenCV_DIR variable to target the locally built version made using the script. If you are having trouble switching between the system version and the downloaded version consider using the cmake-gui utility or removing the build directory and making a fresh one, once again following the Building instructions. The new build directory should reset all paths and automatically see the local OpenCV version if you used the [scripts/getOpenCV.sh](https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/scripts/getOpenCV.sh) script and use this by default. - -## Live Demo ------------------------------------------------------------------- - -Assuming that the OpenCVTest executable described previously is working correctly with your input source, to do a live test of the MocapNET library using a webcam issue : - -``` -./MocapNET2LiveWebcamDemo --from /dev/video0 --live -``` - -To dump 5000 frames from the webcam to out.bvh instead of the live directive issue : - -``` -./MocapNET2LiveWebcamDemo --from /dev/video0 --frames 5000 -``` - -To control the resolution of your webcam you can use the --size width height parameter, make sure that the resolution you provide is supported by your webcam model. You can use the v4l2-ctl tool by executing it and examining your supported sensor sizes and rates. By issuing --forth you can use our FORTH developed 2D joint estimator that performs faster but offers lower accuracy - -``` - v4l2-ctl --list-formats-ext -./MocapNET2LiveWebcamDemo --from /dev/video0 --live --forth --size 800 600 -``` - - -Testing the library using a pre-recorded video file (i.e. not live input) means you can use a slower but more precise 2D Joint estimation algorithm like the included OpenPose implementation. You should keep in mind that [this OpenPose implementation](https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/src/MocapNET1/MocapNETLiveWebcamDemo/utilities.cpp#L213) does not use PAFs and so it is still not as precise as the official OpenPose implementation. To run the demo with a prerecorded file issue : - -``` -./MocapNET2LiveWebcamDemo --from /path/to/yourfile.mp4 --openpose -``` - -We have included a [video file](http://ammar.gr/mocapnet/shuffle.webm) that should be automatically downloaded by the initialize.sh script. Issuing the following command should run it and produce an out.bvh file even if you don't have any webcam or other video files available! : - -``` -./MocapNET2LiveWebcamDemo --from shuffle.webm --openpose --frames 375 -``` - - -Since high-framerate output is hard to examine, if you need some more time to elaborate on the output you can use the delay flag to add programmable delays between frames. Issuing the following will add 1 second of delay after each processed frame : - -``` -./MocapNET2LiveWebcamDemo --from shuffle.webm --openpose --frames 375 --delay 1000 -``` - -If your target is a headless environment then you might consider deactivating the visualization by passing the runtime argument --novisualization. This will prevent any windows from opening and thus not cause issues even on a headless environment. - -BVH output files are stored to the "out.bvh" file by default. If you want them to be stored in a different path use the -o option. They can be easily viewed using a variety of compatible applicatons. We suggest [Blender](https://www.blender.org/) which is a very powerful open-source 3D editing and animation suite or [BVHacker](https://www.bvhacker.com/) that is freeware and compatible with [Wine](https://wiki.winehq.org/) - - -![MocapNETLiveWebcamDemo default visualization](https://raw.githubusercontent.com/FORTH-ModelBasedTracker/MocapNET/mnet2/doc/show0.jpg) - -``` -./MocapNET2LiveWebcamDemo --from shuffle.webm --openpose --show 0 --frames 375 -``` - -![MocapNETLiveWebcamDemo all-in-one visualization](https://raw.githubusercontent.com/FORTH-ModelBasedTracker/MocapNET/mnet2/doc/show3.jpg) - -``` -./MocapNET2LiveWebcamDemo --from shuffle.webm --openpose --show 3 --frames 375 -``` - - -![MocapNETLiveWebcamDemo rotation per joint visualization](https://raw.githubusercontent.com/FORTH-ModelBasedTracker/MocapNET/mnet2/doc/show1.jpg) - - -``` -./MocapNET2LiveWebcamDemo --from shuffle.webm --openpose --show 1 --frames 375 -``` - - -By using the --show variable you can alternate between different visualizations. A particularly useful visualization is the "--show 1" one that plots the joint rotations as seen above. - -![MocapNETLiveWebcamDemo OpenGL visualization](https://raw.githubusercontent.com/FORTH-ModelBasedTracker/MocapNET/mnet2/doc/show0ogl.jpg) - -``` -./MocapNET2LiveWebcamDemo --from shuffle.webm --openpose --show 0 --opengl --frames 375 -``` -By executing "sudo apt-get install freeglut3-dev" to get the required libraries, then enabling the ENABLE_OPENGL CMake configuration flag during compilation and using the --opengl flag when running the MocapNET2LiveWebcamDemo you can also see the experimental OpenGL visualization illustrated above, rendering a skinned mesh that was generated using [makehuman](http://www.makehumancommunity.org/). The BVH file armature used corresponds to the [CMU+Face](http://www.makehumancommunity.org/content/cmu_plus_face.html) armature of makehuman. - - - -``` -./MocapNET2LiveWebcamDemo --from shuffle.webm --openpose --gestures --frames 375 -``` -By starting the live demo using the --gestures argument you can enable an experimental simple form of gesture detection as seen in the illustration above. Gestures are stored as [BVH files](https://github.com/FORTH-ModelBasedTracker/MocapNET/tree/master/dataset/gestures) and controlled through the [gestureRecognition.hpp](https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/src/MocapNET1/MocapNETLib/gestureRecognition.hpp#L18) file. A client application can register a callback as seen in the [demo](https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/src/MocapNET1/MocapNETLiveWebcamDemo/mocapNETLiveDemo.cpp#L50). The gesture detection code is experimental and has been included as a proof of concept, since due to our high-level output you can easily facilitate gesture detections by comparing subsequent BVH frames as [seen in the code](https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/src/MocapNET1/MocapNETLib/gestureRecognition.cpp#L148). That being said gestures where not a part of the original MocapNET papers. - - -## ROS (Robot Operating System) node ------------------------------------------------------------------- - -[![mocapnet_rosnode screenshot with rviz](https://raw.githubusercontent.com/FORTH-ModelBasedTracker/mocapnet_rosnode/main/doc/screenshot.jpg)](https://github.com/FORTH-ModelBasedTracker/mocapnet_rosnode) - -If you are interested in [ROS](https://www.ros.org/) development and looking for a 3D pose estimator for your robot, you are in luck, MocapNET has a ROS node! You can [get it here](https://github.com/FORTH-ModelBasedTracker/mocapnet_rosnode)! - - - -## Tuning Hierarchical Coordinate Descent for accuracy/performance gains ------------------------------------------------------------------- - -As described in the paper, the Hierarchical Coordinate Descent Inverse Kinematics algorithm has various hyper-parameters that have been set to default values after experiments. Depending on your deployment scenarios you might to sacrifice some performance for better accuracy. You can do this by altering the IK tuning parameters by using the --ik switch - -A default run without the --ik switch is equivalent to a run using a learning rate of 0.01, 5 iterations, 30 epochs. The iterations variable has the biggest impact in performance. - -A normal run without the --ik flag is equivalent to - -``` -./MocapNET2LiveWebcamDemo --from shuffle.webm --ik 0.01 5 30 -``` - -If you want a very high accuracy run and don't care about framerate as much consider -``` -./MocapNET2LiveWebcamDemo --from shuffle.webm --ik 0.01 15 40 -``` - -The IK module supports tailoring the model used for posed estimation to your liking using the "--changeJointDimensions neckLength torsoLength chestWidth shoulderToElbowLength elbowToHandLength waistWidth hipToKneeLength kneeToFootLength shoeLength as well as the focal length of your specific camera using "--focalLength fx fy" The following example will try to track the shuffle.webm sample assuming a body with feet 150% the normal size and a focal length of 600 on x and y - -``` -./MocapNET2LiveWebcamDemo --from shuffle.webm --ik 0.01 25 40 --changeJointDimensions 1.0 1.0 1.0 1.0 1.0 1.5 1.5 1.5 1.0 --focalLength 600 600 -``` - -If you don't care about fine results and just want a rough pose estimation extracted really fast you can completely switch the IK module off using -``` -./MocapNET2LiveWebcamDemo --from shuffle.webm --noik -``` - - - - -## Headless deployment ------------------------------------------------------------------- - -When deploying the code on headless environments like [Google Colab](https://github.com/FORTH-ModelBasedTracker/MocapNET/issues/33) where there is no display available you might experience errors like -``` -(3D Points Output:xxxx): Gtk-WARNING **: cannot open display: -``` - -To overcome these errors just use the --novisualization switch to disable visualization windows - - - - - -## Higher accuracy with relatively little work using Mediapipe Holistic ------------------------------------------------------------------- -To convert video files ready for use as input to MocapNET in a *relatively* easy way I have included a python converter that uses mediapipe/opencv to create the CSV files needed for MocapNET. - -![MediaPipe Video 2 CSV utility](https://raw.githubusercontent.com/FORTH-ModelBasedTracker/MocapNET/master/doc/mediapipeConverter.jpg) - -You can get mediapipe using this [src/python/mediapipe/setup.sh](https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/src/python/mediapipe/setup.sh) script or by executing - -``` -pip install --user mediapipe opencv-python -``` - -The converter utility receives an input video stream and creates an output directory with all image frames and the CSV file with 2D joint estimations. - -After going to the root directory of the project -``` -python3 src/python/mediapipe/mediapipeHolistic2CSV.py --from shuffle.webm -o tester -``` - -After the conversion finishes you can process the generated "dataset" using MocapNET2CSV - -``` -./MocapNET2CSV --from tester-mpdata/2dJoints_mediapipe.csv --show 3 -``` -Due to the higher accuracy of [mediapipe holistic](https://google.github.io/mediapipe/solutions/holistic.html) (as well as inclusion of heads and hands which makes data forward compatible with the next versions of MocapNET) this might be a very useful tool to use in conjunction with MocapNET. In particular if you use this dumper be sure to checkout [MocapNET version 3](https://github.com/FORTH-ModelBasedTracker/MocapNET/tree/mnet3) that also supports hand pose estimation! - - - - - - - -## Higher accuracy with more work deploying Caffe/OpenPose and using OpenPose JSON files ------------------------------------------------------------------- - -In order to get higher accuracy output compared to the live demo which is more performance oriented, you can use OpenPose and the 2D output JSON files produced by it. The convertOpenPoseJSONToCSV application can convert them to a BVH file. After downloading [OpenPose](https://github.com/CMU-Perceptual-Computing-Lab/openpose) and building it you can use it to acquire 2D JSON body pose data by running : - -``` -build/examples/openpose/openpose.bin -number_people_max 1 --hand --write_json /path/to/outputJSONDirectory/ -video /path/to/yourVideoFile.mp4 -``` - -This will create files in the following fashion /path/to/outputJSONDirectory/yourVideoFile_XXXXXXXXXXXX_keypoints.json Notice that the filenames generated encode the serial number by padding it up to 12 characters (marked as X). You provide this information to our executable using the --seriallength commandline option. - -The [dump_and_process_video.sh script has been included](https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/scripts/dump_and_process_video.sh) that can be used to fully process a video file using openpose and then process it through MocapNET, or act as a guide for this procedure. - -A utility has been included that can convert the JSON files to a single CSV file issuing : -``` - ./convertOpenPoseJSONToCSV --from /path/to/outputJSONDirectory/ --label yourVideoFile --seriallength 12 --size 1920 1080 -o . -``` -For more information on how to use the conversion utility please [see the documentation inside the utility](https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/src/MocapNET2/Converters/Openpose/convertOpenPoseJSONToCSV.cpp) - -A CSV file has been included that can be run by issuing : -``` - ./MocapNET2CSV --from dataset/sample.csv --visualize --delay 30 -``` -The delay is added in every frame so that there is enough time for the user to see the results, of course the visualization only contains the armature since the CSV file does not have the input images. - -Check out [this guide contributed by a project user](https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/doc/OpenPose.md) for more info. - -## Experimental utilities ------------------------------------------------------------------- - -The repository contains experimental utilities used for the development of the papers. - - -The CSV cluster plot utility if you choose to download the CMU-BVH dataset using the ./initialize.sh script will allow you to perform the clustering experiments described. - -![CSV cluster plot utility](https://raw.githubusercontent.com/FORTH-ModelBasedTracker/MocapNET/master/doc/CSVClusterPlot.jpg) - -``` -./CSVClusterPlot -``` - - -The BVHGUI2 is a very minimal utility you can use to become more familiar with the BVH armature used by the project. Using easy to use sliders you can animate the armature and it is [has a minimal source code](https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/src/MocapNET2/BVHGUI2/bvhGUI2.cpp). - -![BVH GUI utility](https://raw.githubusercontent.com/FORTH-ModelBasedTracker/MocapNET/master/doc/BVHGUI2.jpg) - -``` -./BVHGUI2 --opengl -``` - +Finishing my PhD this will probably be the *final* version of MocapNET! +MocapNET 4 will deal with upperbody / lowerbody / hands / eye tracking and / facial capture +It has a written from scratch python interface, but maintain the same compatible BVH output format. +It will also be compatible with Raspberry Pi 4 and use Tensorflow /Tf-Lite / ONNX backends + +This branch is still missing a lot of things so you can safely ignore it for now..! ## License ------------------------------------------------------------------ From 968e4bba83927388e93464d9494c42aa02c3e4bd Mon Sep 17 00:00:00 2001 From: Ammar Qammaz Date: Mon, 17 Jul 2023 18:42:16 +0300 Subject: [PATCH 004/154] ... --- initialize.sh | 301 +--- src/python/mnet4/BVH/BVHConverter.cbp | 229 --- src/python/mnet4/BVH/BVHTester.cbp | 218 --- src/python/mnet4/BVH/BVHToCSV.py | 86 -- src/python/mnet4/BVH/CMakeLists.txt | 79 - src/python/mnet4/BVH/bvhConverter.c | 814 ---------- src/python/mnet4/BVH/bvhConverter.py | 881 ----------- src/python/mnet4/BVH/bvhLibrary.h | 64 - src/python/mnet4/BVH/bvhLibrary.py | 238 --- src/python/mnet4/BVH/calibration.py | 139 -- src/python/mnet4/BVH/gatherFiles.sh | 72 - .../mnet4/BVH/headerWithHeadAndOneMotion.bvh | 1022 ------------ src/python/mnet4/BVH/libBVHConverter.so | Bin 438208 -> 0 bytes src/python/mnet4/BVH/main.c | 1373 ----------------- src/python/mnet4/BVH/makeLibrary.sh | 130 -- 15 files changed, 11 insertions(+), 5635 deletions(-) delete mode 100644 src/python/mnet4/BVH/BVHConverter.cbp delete mode 100644 src/python/mnet4/BVH/BVHTester.cbp delete mode 100644 src/python/mnet4/BVH/BVHToCSV.py delete mode 100644 src/python/mnet4/BVH/CMakeLists.txt delete mode 100644 src/python/mnet4/BVH/bvhConverter.c delete mode 100644 src/python/mnet4/BVH/bvhConverter.py delete mode 100644 src/python/mnet4/BVH/bvhLibrary.h delete mode 100644 src/python/mnet4/BVH/bvhLibrary.py delete mode 100644 src/python/mnet4/BVH/calibration.py delete mode 100755 src/python/mnet4/BVH/gatherFiles.sh delete mode 100644 src/python/mnet4/BVH/headerWithHeadAndOneMotion.bvh delete mode 100755 src/python/mnet4/BVH/libBVHConverter.so delete mode 100644 src/python/mnet4/BVH/main.c delete mode 100755 src/python/mnet4/BVH/makeLibrary.sh diff --git a/initialize.sh b/initialize.sh index 195fafc..e3c9837 100755 --- a/initialize.sh +++ b/initialize.sh @@ -42,271 +42,6 @@ fi -#We generate a Linux desktop shortcut to easily start the live demo -echo "Generating shortcut" -echo "[Desktop Entry]" > mocapnet.desktop -echo "Type=Application" >> mocapnet.desktop -echo "Name=MocapNET Demo" >> mocapnet.desktop -echo "Version=1.0" >> mocapnet.desktop -echo "GenericName=MocapNET" >> mocapnet.desktop -echo "Icon=$ORIG_DIR/doc/icon.png" >> mocapnet.desktop -echo "Exec=$ORIG_DIR/MocapNET2LiveWebcamDemo --from /dev/video0 --live --dir \"$ORIG_DIR\"" >> mocapnet.desktop -echo "Terminal=false" >> mocapnet.desktop -echo "StartupNotify=false" >> mocapnet.desktop -echo "Categories=Application;Graphics;3DGraphics;2DGraphics;" >> mocapnet.desktop -chmod +x mocapnet.desktop - - -#unfortunately 2022 has not been kind on the internet and my server so I wont use my server -CMUDATASET_WEBSERVER="http://ammar.gr/datasets/" -DATASET_WEBSERVER="http://ammar.gr/datasets/" -OTHERFILE_WEBSERVER="http://ammar.gr/mocapnet/" - -#Instead files are now located on the CVRL FORTH server -CMUDATASET_WEBSERVER="http://cvrlcode.ics.forth.gr/web_share/mocapnet/" -DATASET_WEBSERVER="http://cvrlcode.ics.forth.gr/web_share/mocapnet/" -OTHERFILE_WEBSERVER="http://cvrlcode.ics.forth.gr/web_share/mocapnet/" - - -clear - -cd "$DIR" -if [ -f dataset/MotionCapture/READMEFIRST.txt ] -then -echo "CMU BVH datasets appear to have been downloaded.." -else - echo " Do you want to download the CMU BVH datasets ? " - echo "The download is approximately 1GB and uncompressed will take 4GB of disk space " - echo "(You probably don't need this if you dont want to use the GenerateGroundTruth/CSVClusterPlot utility)" - echo - echo -n " (Y/N)?" - - #Only ask if we can answer - #_____________________________ - if [ "$ASK_QUESTIONS" -eq "0" ]; then - answer="Y" - else - read answer - fi - #_____________________________ - - if test "$answer" != "N" -a "$answer" != "n"; - then - cd "$DIR/dataset" - echo "Could not find MotionCapture" - - #This is a richer armature that also contains provisons for head and feet animation - wget "$CMUDATASET_WEBSERVER/CMUPlusHeadMotionCapture.zip" - unzip CMUPlusHeadMotionCapture.zip - mv CMUPlusHeadMotionCapture.zip MotionCapture - - cd "$DIR" - fi -fi - - -#SWITCH DOWNLOAD BEHAVIOR -USE_GOOGLE_HOSTING="yes" - -if [ "$USE_GOOGLE_HOSTING" == "yes" ]; then - #Since June 8 2023, FORTH NOC has firewalled cvrldemo.ics.forth.gr and ammar.gr, - #as a result the old way to access files is not available.. - #this is a workaround until they fix this.. - #https://github.com/FORTH-ModelBasedTracker/MocapNET/issues/96 - cd "$DIR" - echo "Using Google Drive Hosting to retrieve required files.." - mkdir -p dataset/combinedModel/mocapnet2/mode5/1.0/ - mkdir -p dataset/combinedModel/mocapnet2/mode1/1.0/ - if [ ! -f allInOneMNET2RedistMirrorICPR2020.zip ]; then - wget -O allInOneMNET2RedistMirrorICPR2020.zip "drive.google.com/u/3/uc?id=1GtmPWOpf3MzhqhqegaC8cS3_m3Drp6y3&export=download&confirm=yes" - fi - unzip allInOneMNET2RedistMirrorICPR2020.zip -else - -echo "Using FORTH Hosting to retrieve required files.." -cd "$DIR" -#Force download of a Video sample -if [ ! -f shuffle.webm ]; then - wget "$OTHERFILE_WEBSERVER/shuffle.webm" -fi -#-------------------------------------------- - -if [ ! -f dataset/makehuman.tri ]; then - cd "$DIR/dataset" - #TRI is the internal 3D format used by my 3D renderer to handle 3D meshes - #https://github.com/AmmarkoV/RGBDAcquisition/blob/master/opengl_acquisition_shared_library/opengl_depth_and_color_renderer/src/Library/ModelLoader/model_loader_tri.h - wget "$OTHERFILE_WEBSERVER/makehuman.tri" - - #Also provide the OpenCollada file in case someone wants to create their own .tri by `sudo apt-get install libassimp-dev` and then compiling and using the project - # https://github.com/AmmarkoV/RGBDAcquisition/tree/master/opengl_acquisition_shared_library/opengl_depth_and_color_renderer/submodules/Assimp - #that you will find in $ROOT_DIR/dependencies/RGBDAcquisition/opengl_acquisition_shared_library/opengl_depth_and_color_renderer/submodules/Assimp/ - #./assimpTester --convert $ROOT_DIR/dataset/makehuman.dae $ROOT_DIR/dataset/makehuman.tri --paint 123 123 123 - #This dae file has been created usign makehuman(http://www.makehumancommunity.org/) and the CMU+Face Rig (http://www.makehumancommunity.org/content/cmu_plus_face.html) - wget "$OTHERFILE_WEBSERVER/makehuman.dae" -fi - -cd "$DIR" - - - - -cd "$DIR/dataset" -mkdir -p combinedModel/mocapnet2/mode5/1.0/ -cd "$DIR/dataset/combinedModel/mocapnet2/mode5/1.0/" - - -#New ICPR pretrained networks - -LIST_OF_NETWORKS="categorize_lowerbody_all.pb lowerbody_left.pb upperbody_left.pb categorize_upperbody_all.pb lowerbody_right.pb upperbody_right.pb lowerbody_back.pb upperbody_back.pb lowerbody_front.pb upperbody_front.pb" - -for NETWORK in $LIST_OF_NETWORKS; do -if [ ! -f $NETWORK ]; then - wget "$DATASET_WEBSERVER/icpr2020/$NETWORK" -fi -done - - - - -#-------------------------------------------------------------------- -cd "$DIR/combinedModel" -#-------------------------------------------------------------------- - -#We also downloar pre-trained models for the 2D joint estimation -#We have 3D flavours available, openpose, vnect and our own 2D detector -echo "Downloading 2D Joint Estimator models" -cd "$DIR/dataset/combinedModel" - -if [ ! -f openpose_model.pb ]; then - wget "$DATASET_WEBSERVER/combinedModel/openpose_model.pb" -fi - -if [ ! -f vnect_sm_pafs_8.1k.pb ]; then - wget "$DATASET_WEBSERVER/combinedModel/vnect_sm_pafs_8.1k.pb" -fi - -if [ ! -f mobnet2_tiny_vnect_sm_1.9k.pb ]; then - wget "$DATASET_WEBSERVER/combinedModel/mobnet2_tiny_vnect_sm_1.9k.pb" -fi - -cd "$DIR" - -#END OF FORTH HOSTING FILE RETRIEVAL -fi - - - - - -#Default Tensorflow to be downloaded is 2.x with CPU only stuff to improve compatibility -TENSORFLOW_VERSION="2.3.1" -ARCHITECTURE="cpu" #can be gpu or cpu -#https://www.tensorflow.org/install/lang_c -#https://github.com/tensorflow/tensorflow/tree/master/tensorflow/c - - -#Tensorflow 2.3.1 works well with CUDA 10 and cudnn-10.0-linux-x64-v7.6.5.32.tgz -#wget https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-gpu-linux-x86_64-2.3.1.tar.gz - -#if you want the latest version -#you can download it from https://storage.googleapis.com/tensorflow-nightly/github/tensorflow/lib_package/libtensorflow-gpu-linux-x86_64.tar.gz - -#========================================================================== -#========================================================================== -#========================================================================== -clear - echo " Do you want to use your GPU in Tensorflow ? " - echo "If you select Y a GPU-enabled version will be downloaded " - echo "If you don't have a CUDA-enabled GPU its best to select N" - echo "GPU execution is mainly imporant for the RGB->2D neural networks" - echo - echo -n " (Y/N)?" - - #Only ask if we can answer - #_____________________________ - if [ "$ASK_QUESTIONS" -eq "0" ]; then - answer="Y" - else - read answer - fi - #_____________________________ - - if test "$answer" != "N" -a "$answer" != "n"; - then - ARCHITECTURE="gpu" - fi -#========================================================================== -#========================================================================== -#========================================================================== -clear - echo " Do you want to use Tensorflow 1.x instead of 2.x ? " - echo "The project is compatible with both but if you have an older GPU it might be better for you " - echo "to stick with Tensorflow 1.x " - echo - echo -n " (Y/N)?" - - #Only ask if we can answer - #_____________________________ - if [ "$ASK_QUESTIONS" -eq "0" ]; then - answer="N" - else - read answer - fi - #_____________________________ - - - if test "$answer" != "N" -a "$answer" != "n"; - then - TENSORFLOW_VERSION="1.14.0" # 1.12.0 for CUDA 9.0 / 1.11.0 for CUDA9 with older compute capabilities (5.2) .. / 1.8.0 for CUDA9 and a device with compute capability 3.0 / 1.4.1 for CUDA 8 - fi -#========================================================================== -#========================================================================== -#========================================================================== -echo "Selected Tensorflow version $ARCHITECTURE/$TENSORFLOW_VERSION" - - -#I have a special version of tensorflow 1.11.0 tailored for Intel Core 2 and NVIDIA 7XX cards ( compute capabilities ) that you can find here -#wget http://ammar.gr/mocapnet/libtensorflow-oldgpu-linux-x86_64-1.11.0.tar.gz - -#I have a special version of tensorflow 1.15.2 built for i7 950 CPUs without later AVX instrucitons but CUDA 10.0 compute capability 3.5 + GPUs -#wget http://ammar.gr/mocapnet/libtensorflow-1-15.2_CPUi7_970_CUDA10.tar.gz - -cd "$DIR" -if [ -f /usr/local/lib/libtensorflow.so ]; then - echo "Found a system wide tensorflow installation, not altering anything" -elif [ -f dependencies/libtensorflow/lib/libtensorflow.so ]; then - echo "Found a local tensorflow installation, not altering anything" -else - echo "Did not find tensorflow already installed..!" - if [ ! -f dependencies/libtensorflow-$ARCHITECTURE-linux-x86_64-$TENSORFLOW_VERSION.tar.gz ]; then - echo "Did not find tensorflow tarball so will have to download it..!" - cd "$DIR/dependencies" - wget https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-$ARCHITECTURE-linux-x86_64-$TENSORFLOW_VERSION.tar.gz - #Is the Google link down ? we have a mirror :) - #wget http://ammar.gr/mocapnet/libtensorflow-gpu-linux-x86_64-$TENSORFLOW_VERSION.tar.gz - else - echo "The tensorflow tarball was already downloaded.." - fi - - -cd "$DIR" -if [ -f dependencies/libtensorflow-$ARCHITECTURE-linux-x86_64-$TENSORFLOW_VERSION.tar.gz ]; then - #Doing a local installation that requires no SUDO - cd "$DIR/dependencies" - mkdir libtensorflow - tar -C libtensorflow -xzf libtensorflow-$ARCHITECTURE-linux-x86_64-$TENSORFLOW_VERSION.tar.gz - #echo "Please give me sudo permissions to install Tensorflow $TENSORFLOW_VERSION C Bindings.." - #sudo tar -C /usr/local -xzf libtensorflow-gpu-linux-x86_64-$TENSORFLOW_VERSION.tar.gz - else - echo "Failed to download/extract tensorflow.." -fi - -fi -#--------------------------------------------------------------------------------------------------------------------------- - - - - cd "$DIR" if [ -f dependencies/RGBDAcquisition/README.md ]; then echo "RGBDAcquisition appears to already exist .." @@ -337,33 +72,19 @@ fi +cd "$DIR" +cd src/python/mnet4 +rm -rf BVH/ +ln -s ../../../dependencies/RGBDAcquisition/opengl_acquisition_shared_library/opengl_depth_and_color_re +nderer/src/Applications/BVHTester/ BVH +cd BVH +./makeLibrary.sh + +cd .. +#Install rest of python stuff.. +./setup.sh -#This webserver stuff is really not needed, and just adds complexity to everything so it is disabled -#cd "$DIR" -#if [ -f dependencies/AmmarServer/README.md ] -#then -#echo "AmmarServer appears to already exist .." -#else -# echo "Do you want to download AmmarServer and enable MocapNETServer build ? " -# echo "(You probably don't need this)" -# echo -# echo -n " (Y/N)?" -# read answer -# if test "$answer" != "N" -a "$answer" != "n"; -# then -# cd "$DIR/dependencies" -# git clone https://github.com/AmmarkoV/AmmarServer -# AmmarServer/scripts/get_dependencies.sh -# cd AmmarServer -# mkdir build -# cd build -# cmake .. -# make -# cd "$DIR" -# fi -#fi - #Now that we have everything lets build.. diff --git a/src/python/mnet4/BVH/BVHConverter.cbp b/src/python/mnet4/BVH/BVHConverter.cbp deleted file mode 100644 index e69cce6..0000000 --- a/src/python/mnet4/BVH/BVHConverter.cbp +++ /dev/null @@ -1,229 +0,0 @@ - - - - - - diff --git a/src/python/mnet4/BVH/BVHTester.cbp b/src/python/mnet4/BVH/BVHTester.cbp deleted file mode 100644 index 78fda98..0000000 --- a/src/python/mnet4/BVH/BVHTester.cbp +++ /dev/null @@ -1,218 +0,0 @@ - - - - - - diff --git a/src/python/mnet4/BVH/BVHToCSV.py b/src/python/mnet4/BVH/BVHToCSV.py deleted file mode 100644 index eca45fa..0000000 --- a/src/python/mnet4/BVH/BVHToCSV.py +++ /dev/null @@ -1,86 +0,0 @@ -def bvh_to_csv(bvh_file_path_in,csv_file_path_out): - with open(bvh_file_path_in, 'r') as file: - lines = file.readlines() - - motion_start_index = None - motionIDAssignments = list() - - # Parse Hierarchy - for i, line in enumerate(lines): - if 'MOTION' in line: - motion_start_index = i + 2 # Skip the "Frames:" line - break - - line = line.strip() - - if 'ROOT' in line: - root_name = line.split(" ")[1].strip() - current_joint = root_name - - elif 'JOINT' in line: - joint_name = line.split(" ")[1].strip() - current_joint = joint_name - - #elif 'End Site' in line: - # joint_name = 'End Site' - # current_joint = "EndSite_%s" % current_joint - - elif 'CHANNELS' in line: - splitLine = line.split(" ") - numberOfchannels = int(splitLine[1]) - #print(current_joint," has ",numberOfchannels," channels ") - for mID in range(0,numberOfchannels): - motionIDAssignments.append("%s_%s" % (current_joint,splitLine[2+mID])) - - - #Debug - #print("Hierarchy To Motion IDS Mapping :") - #for i in range(0,len(motionIDAssignments)): - # print(i, " - ",motionIDAssignments[i]) - numberOfMotionIDs = len(motionIDAssignments) - - f = open(csv_file_path_out,'w') - #------------------------------------------------------------------------ - for column in range(numberOfMotionIDs): - if (column>0): - f.write(',') - f.write("%s"%(motionIDAssignments[column])) - f.write('\n') - #------------------------------------------------------------------------ - - # Parse Motion - if motion_start_index: - for i in range(motion_start_index, len(lines)): - if ("Frame" not in lines[i]): #skip Frames: / Frame Time: lines - - #Got a fresh motion line - motionData = lines[i].split(" ") - #Make sure it is consistent with our hierarchy length - if (len(motionData)!=numberOfMotionIDs): - raise ValueError('Line %u: Mismatched number of motion values (%u) compared to our hierarchy (%u)' % (i,len(motionData),len(motionIDAssignments)) ) - #Dump it to CSV - #---------------------------------------------- - for column in range(numberOfMotionIDs): - if (column>0): - f.write(',') - f.write("%f"%(float(motionData[column]))) - f.write('\n') - #---------------------------------------------- - f.close() - - -if __name__ == '__main__': - import sys - if (len(sys.argv)>1): - for i in range(1,len(sys.argv)): - baseFilename = sys.argv[i] - if (".bvh" in baseFilename): - print("Will convert ",baseFilename) - targetFilename = "%s.csv" % (baseFilename.rsplit(".")[0]) - print("To ",targetFilename) - bvh_to_csv(baseFilename,targetFilename) - else: - print("Will NOT convert ",baseFilename," it does not have a .bvh extension") - else: - raise ValueError('Please call the utility with a single path of BVH file\n') - diff --git a/src/python/mnet4/BVH/CMakeLists.txt b/src/python/mnet4/BVH/CMakeLists.txt deleted file mode 100644 index 07ccb99..0000000 --- a/src/python/mnet4/BVH/CMakeLists.txt +++ /dev/null @@ -1,79 +0,0 @@ -project( BVHTester ) -cmake_minimum_required( VERSION 2.8.7 ) -set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/modules ${CMAKE_MODULE_PATH}) - - - -IF( ENABLE_JPG ) - MESSAGE("JPGs will be included in this codec build") - set(JPG_Libs jpeg ) - set(JPG_Parts jpgInput.c jpgInput.h jpgExifexternal.c jpgExifexternal.h jpgExiforient_embed.c jpgExiforient_embed.h ) - set(JPG_Includes ${CMAKE_SOURCE_DIR}/3dparty/OpenNI2/Include/ ) - add_definitions(-DUSE_JPG_FILES) - add_definitions(-DENABLE_JPG) -ENDIF( ENABLE_JPG ) - - -IF( ENABLE_PNG ) - MESSAGE("PNGs will be included in this codec build") - set(PNG_Libs png ) - set(PNG_Parts pngInput.c pngInput.h) - set(PNG_Includes ${CMAKE_SOURCE_DIR}/3dparty/OpenNI2/Include/ ) - add_definitions(-DUSE_PNG_FILES) - add_definitions(-DENABLE_PNG) -ENDIF( ENABLE_PNG ) - -IF( ENABLE_SHADERS ) - MESSAGE("Shaders will be included in this codec build") - set(GLEW_Libs GLEW ) #sudo apt-get install libglew-dev - set(GLEW_Parts ) - set(GLEW_Includes ) - add_definitions(-DUSE_GLEW) -ENDIF( ENABLE_SHADERS ) - -add_executable( - BVHTester - main.c - ../../Library/MotionCaptureLoader/bvh_loader.c - ../../Library/MotionCaptureLoader/calculate/bvh_transform.c - ../../Library/MotionCaptureLoader/calculate/bvh_project.c - ../../Library/MotionCaptureLoader/calculate/bvh_to_tri_pose.c - ../../Library/MotionCaptureLoader/calculate/smoothing.h - ../../Library/MotionCaptureLoader/calculate/smoothing.c - ../../Library/MotionCaptureLoader/import/fromBVH.c - ../../Library/MotionCaptureLoader/export/bvh_export.c - ../../Library/MotionCaptureLoader/export/bvh_to_c.c - ../../Library/MotionCaptureLoader/export/bvh_to_bvh.c - ../../Library/MotionCaptureLoader/export/bvh_to_svg.c - ../../Library/MotionCaptureLoader/export/bvh_to_csv.c - ../../Library/MotionCaptureLoader/export/bvh_to_json.c - ../../Library/MotionCaptureLoader/export/bvh_to_trajectoryParserTRI.c - ../../Library/MotionCaptureLoader/export/bvh_to_trajectoryParserPrimitives.c - ../../Library/MotionCaptureLoader/edit/bvh_randomize.c - ../../Library/MotionCaptureLoader/edit/bvh_rename.c - ../../Library/MotionCaptureLoader/edit/bvh_remapangles.c - ../../Library/MotionCaptureLoader/edit/bvh_interpolate.c - ../../Library/MotionCaptureLoader/edit/bvh_merge.c - ../../Library/MotionCaptureLoader/edit/bvh_filter.c - ../../Library/MotionCaptureLoader/edit/bvh_cut_paste.c - ../../Library/MotionCaptureLoader/ik/bvh_inverseKinematics.c - ../../Library/MotionCaptureLoader/ik/hardcodedProblems_inverseKinematics.c - #../../Library/MotionCaptureLoader/ik/levmar.c - ../../Library/MotionCaptureLoader/metrics/bvh_measure.c - ../../Library/MotionCaptureLoader/tests/test.c - ../../Library/TrajectoryParser/InputParser_C.c - ../../../../../tools/AmMatrix/matrix4x4Tools.c - ../../../../../tools/AmMatrix/matrixMultiplicationOptimization.c - ../../../../../tools/AmMatrix/matrixOpenGL.c - ../../../../../tools/AmMatrix/quaternions.c - ../../../../../tools/AmMatrix/simpleRenderer.c - ) - -target_link_libraries(BVHTester rt m pthread ) - - -set_target_properties(BVHTester PROPERTIES - ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}" - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}" - ) diff --git a/src/python/mnet4/BVH/bvhConverter.c b/src/python/mnet4/BVH/bvhConverter.c deleted file mode 100644 index d431d17..0000000 --- a/src/python/mnet4/BVH/bvhConverter.c +++ /dev/null @@ -1,814 +0,0 @@ -/** @file main.c - * @brief A library that can parse BVH files and perform various processing options as a commandline tool - * X86 compilation: gcc -o -L/usr/X11/lib main main.c - * X64 compilation: gcc -o -L/usr/X11/lib64 main main.c - * @author Ammar Qammaz (AmmarkoV) - */ - -#include -#include -#include -#include -#include - -#include "../../Library/TrajectoryParser/TrajectoryParserDataStructures.h" -#include "../../Library/MotionCaptureLoader/bvh_loader.h" -#include "../../Library/MotionCaptureLoader/calculate/bvh_to_tri_pose.h" -#include "../../Library/MotionCaptureLoader/calculate/smoothing.h" - -#include "../../Library/MotionCaptureLoader/export/bvh_to_trajectoryParserTRI.h" -#include "../../Library/MotionCaptureLoader/export/bvh_to_trajectoryParserPrimitives.h" -#include "../../Library/MotionCaptureLoader/export/bvh_export.h" -#include "../../Library/MotionCaptureLoader/export/bvh_to_bvh.h" -#include "../../Library/MotionCaptureLoader/export/bvh_to_csv.h" -#include "../../Library/MotionCaptureLoader/export/bvh_to_c.h" - -#include "../../Library/MotionCaptureLoader/edit/bvh_cut_paste.h" -#include "../../Library/MotionCaptureLoader/edit/bvh_randomize.h" -#include "../../Library/MotionCaptureLoader/edit/bvh_filter.h" -#include "../../Library/MotionCaptureLoader/edit/bvh_rename.h" -#include "../../Library/MotionCaptureLoader/edit/bvh_merge.h" -#include "../../Library/MotionCaptureLoader/edit/bvh_remapangles.h" -#include "../../Library/MotionCaptureLoader/edit/bvh_interpolate.h" - -#include "../../Library/MotionCaptureLoader/ik/bvh_inverseKinematics.h" -#include "../../Library/MotionCaptureLoader/ik/hardcodedProblems_inverseKinematics.h" - -#include "../../Library/MotionCaptureLoader/metrics/bvh_measure.h" -#include "../../Library/MotionCaptureLoader/tests/test.h" - -#include "../../../../../tools/AmMatrix/matrix4x4Tools.h" -#include "../../../../../tools/AmMatrix/matrixOpenGL.h" - - -#define NORMAL "\033[0m" -#define BLACK "\033[30m" /* Black */ -#define RED "\033[31m" /* Red */ -#define GREEN "\033[32m" /* Green */ -#define YELLOW "\033[33m" /* Yellow */ -#define BLUE "\033[34m" /* Blue */ -#define MAGENTA "\033[35m" /* Magenta */ -#define CYAN "\033[36m" /* Cyan */ -#define WHITE "\033[37m" /* White */ - -void haltOnError(unsigned int haltingSwitch,const char * message) -{ - fprintf(stderr,RED "=======================================\n"); - fprintf(stderr,"=======================================\n"); - fprintf(stderr,"Encountered error during procedure %s \n",message); - fprintf(stderr,"=======================================\n"); - fprintf(stderr,"=======================================\n" NORMAL); - - if (haltingSwitch) - { - fprintf(stderr,RED "Halting because of --haltonerror switch\n" NORMAL); - exit(1); - } -} - -void incorrectArguments() -{ - fprintf(stderr,RED "Incorrect number of arguments.. \n" NORMAL); - exit(1); -} - -//------------------------------------------------------------------ -//------------------------------------------------------------------ -//------------------------------------------------------------------ -//------------------------------------------------------------------ -struct BVH_MotionCapture bvhAtomicMotion = {0}; -struct BVH_Transform bvhTransformAtomic = {0}; -struct simpleRenderer rendererAtomic = {0}; -struct BVH_RendererConfiguration renderingAtomicConfiguration = {0}; -struct ikProblem * atomicFaceProblem = 0; -struct ikProblem * atomicBodyProblem = 0; -struct ikProblem * atomicLHandProblem = 0; -struct ikProblem * atomicRHandProblem = 0; -struct MotionBuffer * atomicPenultimateSolution=0; -struct MotionBuffer * atomicPreviousSolution=0; -struct MotionBuffer * atomicSolution=0; -struct ButterWorthArray * atomicSmoothingFilter = 0; -//------------------------------------------------------------------ -//------------------------------------------------------------------ -//------------------------------------------------------------------ -//------------------------------------------------------------------ -int bvhConverter_loadAtomic(const char *path) -{ - float scaleWorld=1.0; - int immediatelyHaltOnError = 1; - fprintf(stderr,"Attempting to load %s\n",path); - if (!bvh_loadBVH(path, &bvhAtomicMotion, scaleWorld)) - { - haltOnError(immediatelyHaltOnError,"Error loading bvh file.."); - } - //Change joint names.. - bvh_renameJointsForCompatibility(&bvhAtomicMotion); - - - // Emulate GoPro Hero4 @ FullHD mode by default.. - // https://gopro.com/help/articles/Question_Answer/HERO4-Field-of-View-FOV-Information - renderingAtomicConfiguration.near = 1.0; - renderingAtomicConfiguration.far = 10000.0; - renderingAtomicConfiguration.width = 1920; - renderingAtomicConfiguration.height = 1080; - renderingAtomicConfiguration.cX = (float)renderingAtomicConfiguration.width/2; - renderingAtomicConfiguration.cY = (float)renderingAtomicConfiguration.height/2; - renderingAtomicConfiguration.fX = 582.18394; - renderingAtomicConfiguration.fY = 582.52915; - //---------------------------------------------- - simpleRendererDefaults( - &rendererAtomic, - renderingAtomicConfiguration.width, - renderingAtomicConfiguration.height, - renderingAtomicConfiguration.fX, - renderingAtomicConfiguration.fY - ); - //---------------------------------------------- - simpleRendererInitialize(&rendererAtomic); - //---------------------------------------------- - return bvhAtomicMotion.jointHierarchySize; -} - - -int bvhConverter_unloadAtomic() -{ - /* TODO: unload all this..! - struct BVH_MotionCapture bvhAtomicMotion={0}; -struct BVH_Transform bvhTransformAtomic={0}; -struct simpleRenderer rendererAtomic={0}; -struct BVH_RendererConfiguration renderingAtomicConfiguration={0}; -struct ikProblem * atomicFaceProblem = 0; -struct ikProblem * atomicBodyProblem = 0; -struct ikProblem * atomicLHandProblem = 0; -struct ikProblem * atomicRHandProblem = 0; -struct MotionBuffer * atomicPreviousSolution=0; -struct MotionBuffer * atomicSolution=0; -*/ - fprintf(stderr,"bvhConverter_unloadAtomic not implemented yet..\n"); - return 0; -} - -int bvhConverter_rendererConfigurationAtomic(const char ** labels,const float * values,int numberOfElements) -{ - // Emulate GoPro Hero4 @ FullHD mode by default.. - // https://gopro.com/help/articles/Question_Answer/HERO4-Field-of-View-FOV-Information - renderingAtomicConfiguration.near = 1.0; - renderingAtomicConfiguration.far = 10000.0; - renderingAtomicConfiguration.width = 1920; - renderingAtomicConfiguration.height = 1080; - renderingAtomicConfiguration.cX = (float)renderingAtomicConfiguration.width/2; - renderingAtomicConfiguration.cY = (float)renderingAtomicConfiguration.height/2; - renderingAtomicConfiguration.fX = 582.18394; - renderingAtomicConfiguration.fY = 582.52915; - - fprintf(stderr,"bvhConverter_rendererConfigurationAtomic received %u elements\n",numberOfElements); - for (int i=0; i%0.2f\n",i,labels[i],values[i]); - if (strcmp(labels[i],"near")==0) { renderingAtomicConfiguration.near = values[i]; } else - if (strcmp(labels[i],"far")==0) { renderingAtomicConfiguration.far = values[i]; } else - if (strcmp(labels[i],"width")==0) { renderingAtomicConfiguration.width = (unsigned int) values[i]; } else - if (strcmp(labels[i],"height")==0) { renderingAtomicConfiguration.height = (unsigned int) values[i]; } else - if (strcmp(labels[i],"cX")==0) { renderingAtomicConfiguration.cX = values[i]; } else - if (strcmp(labels[i],"cY")==0) { renderingAtomicConfiguration.cY = values[i]; } else - if (strcmp(labels[i],"fX")==0) { renderingAtomicConfiguration.fX = values[i]; } else - if (strcmp(labels[i],"fY")==0) { renderingAtomicConfiguration.fY = values[i]; } else - { - fprintf(stderr,RED"bvhConverter_rendererConfigurationAtomic: Unknown command %u - %s->%0.2f\n" NORMAL,i,labels[i],values[i]); - } - } - - simpleRendererDefaults( - &rendererAtomic, - renderingAtomicConfiguration.width, - renderingAtomicConfiguration.height, - renderingAtomicConfiguration.fX, - renderingAtomicConfiguration.fY - ); - simpleRendererInitialize(&rendererAtomic); - return 1; -} - -int bvhConverter_processFrame(int frameID) -{ - int occlusions=1; - return performPointProjectionsForFrame( - &bvhAtomicMotion, - &bvhTransformAtomic, - frameID, - &rendererAtomic, - occlusions, - renderingAtomicConfiguration.isDefined - ); -} - - -int bvhConverter_scale(float scaleRatio) -{ - fprintf(stderr,"Offset scaling ratio = %0.2f \n",scaleRatio); - return bvh_scaleAllOffsets( - &bvhAtomicMotion, - scaleRatio - ); -} - -int bvhConverter_getNumberOfMotionValuesPerFrame() -{ - return bvhAtomicMotion.numberOfValuesPerFrame; -} - -int bvhConverter_getNumberOfJoints() -{ - return bvhAtomicMotion.jointHierarchySize; -} - -int bvhConverter_writeBVH(char * filename,int writeHierarchy,int writeMotion) -{ - return dumpBVHToBVH( - filename, - &bvhAtomicMotion, - writeHierarchy, - writeMotion - ); -} - -int bvhConverter_getMotionValueOfFrame(int fID,int mID) -{ - return bvh_getMotionValueOfFrame(&bvhAtomicMotion,fID,mID); -} - -int bvhConverter_setMotionValueOfFrame(int fID,int mID,float value) -{ - float localValue = value; - return bvh_setMotionValueOfFrame(&bvhAtomicMotion,fID,mID,&localValue); -} - -int bvhConverter_getJointNameJointID(const char * jointName) -{ - //fprintf(stderr,"Asked to resolve %s\n",jointName); - BVHJointID jID=0; - if ( - bvh_getJointIDFromJointNameNocase( - &bvhAtomicMotion, - jointName, - &jID - ) - ) - { - return jID; - } - fprintf(stderr,RED "BVH library could not resolve joint \"%s\" \n" NORMAL,jointName); - return -1; -} - -const char * bvhConverter_getJointNameFromJointID(int jointID) -{ - if (jointID=bvhAtomicMotion.numberOfFrames) { return 0; } - //------------------------------------------------------------ - //fprintf(stderr,"bvhConverter_modifyAtomic received element %s with value %0.2f for frame %u\n",label,value,frameID); - //------------------------------------------------------------ - int everythingOk = 1; - char jointName[513]={0}; - snprintf(jointName,512,"%s",label); - char * delimeter = strchr(jointName,'_'); - if (delimeter==0) - { - fprintf(stderr,"bvhConverter_modifyAtomic received element %s with value %0.2f for frame %u ",label,value,frameID); - fprintf(stderr,"it doesn't have a degree of freedom associated so ignoring it.."); - return 0; - } - *delimeter = 0; - char * dof = delimeter+1; - //======================================================= - lowercase(jointName); - lowercase(dof); - //======================================================= - if (strstr(jointName,"endsite_")!=0) - { - fprintf(stderr,RED "Endsites can't be modified..!\n" NORMAL); - return 0; - } - - if (strcmp(jointName,"neck01")==0) - { - snprintf(jointName,512,"neck1"); //Fix ? - } - if (strcmp(jointName,"lthumbbase")==0) - { - snprintf(jointName,512,"__lthumb"); //Fix ? - } - if (strcmp(jointName,"rthumbbase")==0) - { - snprintf(jointName,512,"__rthumb"); //Fix ? - } - - //fprintf(stderr," %u - %s->%0.2f ",i,label,value); - //fprintf(stderr," Joint:%s Control:%s\n",jointName,dof); - //======================================================= - //int jointID = bvhConverter_getJointNameJointID(jointName); - BVHJointID jointID=0; - if ( - bvh_getJointIDFromJointNameNocase( - &bvhAtomicMotion, - jointName, - &jointID - ) - ) - { - // The next line is a debug message that spams a *lot*! - //fprintf(stderr,"Joint ID %u / %s|%s => %0.2f \n",jointID,bvhAtomicMotion.jointHierarchy[jointID].jointName,dof,value); - //============================================================================================================== - if (strcmp(dof,"xposition")==0) { bvh_setJointPositionXAtFrame(&bvhAtomicMotion,jointID,frameID,value); } else - if (strcmp(dof,"yposition")==0) { bvh_setJointPositionYAtFrame(&bvhAtomicMotion,jointID,frameID,value); } else - if (strcmp(dof,"zposition")==0) { bvh_setJointPositionZAtFrame(&bvhAtomicMotion,jointID,frameID,value); } else - if (strcmp(dof,"xrotation")==0) { bvh_setJointRotationXAtFrame(&bvhAtomicMotion,jointID,frameID,value); } else - if (strcmp(dof,"yrotation")==0) { bvh_setJointRotationYAtFrame(&bvhAtomicMotion,jointID,frameID,value); } else - if (strcmp(dof,"zrotation")==0) { bvh_setJointRotationZAtFrame(&bvhAtomicMotion,jointID,frameID,value); } else - if (strcmp(dof,"wrotation")==0) { bvh_setJointRotationWAtFrame(&bvhAtomicMotion,jointID,frameID,value); } else - { - fprintf(stderr,RED "\n\n\nBVH library could not perform modification \"%s\" for joint \"%s\" \n\n\n" NORMAL,dof,jointName); - everythingOk=0; - } - //============================================================================================================== - } else - { - fprintf(stderr,RED "\nBVH library modification could not resolve joint \"%s\" \n" NORMAL,jointName); - everythingOk=0; - } - return everythingOk; -} - - - -int bvhConverter_modifyAtomic(const char ** labels,const float * values,int numberOfElements,int frameID) -{ - //fprintf(stderr,"bvhConverter_modifyAtomic received %u elements\n",numberOfElements); - int everythingOk = 1; - for (int i=0; i 300 so 10 is a good limit - ikConfig.spring = 20; - ikConfig.dumpScreenshots = 0; // Dont thrash disk - ikConfig.verbose = 0; //Dont spam console - ikConfig.tryMaintainingLocalOptima=1; //Less Jittery but can be stuck at local optima - ikConfig.dontUseSolutionHistory=0; - ikConfig.useLangevinDynamics = langevinDynamics; - ikConfig.ikVersion = IK_VERSION; - //------------------------------------ - - int multiThreading = 0; - - //====================================================================================================== - //====================================================================================================== - //====================================================================================================== - if ( (strcmp(bodyPart,"body")==0) || (strcmp(bodyPart,"lhand")==0) || (strcmp(bodyPart,"rhand")==0) ) - { - //Keep history..! - copyMotionBuffer(atomicPenultimateSolution,atomicPreviousSolution); - copyMotionBuffer(atomicPreviousSolution,atomicSolution); - bvh_copyMotionFrameToMotionBuffer( - &bvhAtomicMotion, - atomicSolution, - frameID - ); - - char jointName[512]={0}; - struct BVH_Transform bvhTargetTransform={0}; - int occlusions=1; - performPointProjectionsForFrame( - &bvhAtomicMotion, - &bvhTargetTransform, - frameID, - &rendererAtomic, - occlusions, - renderingAtomicConfiguration.isDefined - ); - - for (int i=0; i %s with %0.2f \n",i,labels[i],values[i] ); - snprintf(jointName,512,"%s",labels[i]); - char * delimeter = strchr(jointName,'_'); - - if (delimeter!=0) - { - *delimeter = 0; - char * coord = jointName; - char * dof = delimeter+1; - //======================================================= - lowercase(coord); - lowercase(dof); - - if ( (coord[0]=='2') && (coord[1]=='d') && ( (coord[2]=='x') || (coord[2]=='y') ) ) - { - BVHJointID jID=0; - if ( bvh_getJointIDFromJointNameNocase(&bvhAtomicMotion,dof,&jID) ) - { - if (coord[2]=='x') - { - //fprintf(stderr,GREEN "%s/%s \n" NORMAL,coord,dof); - bvhTargetTransform.joint[jID].pos2D[0] = (float) values[i]*renderingAtomicConfiguration.width; - } else - if (coord[2]=='y') - { - //fprintf(stderr,GREEN "%s/%s \n" NORMAL,coord,dof); - bvhTargetTransform.joint[jID].pos2D[1] = (float) values[i]*renderingAtomicConfiguration.height; - } - } else - { - //fprintf(stderr,RED "IK: Could not resolve Joint %s for Number %u => %s with %0.2f \n" NORMAL,dof,i,labels[i],values[i] ); - } - }//2DX/Y - - } //Tag has an _ and we process it - - }//Loop over received elements - - if ( approximateBodyFromMotionBufferUsingInverseKinematics( - &bvhAtomicMotion, - &rendererAtomic, - selectedProblem, - &ikConfig, - //---------------- - atomicPenultimateSolution, - atomicPreviousSolution, - atomicSolution, - 0, //No ground truth.. - //---------------- - &bvhTargetTransform, - //---------------- - multiThreading,// 0=single thread, 1=multi thread - //---------------- - &initialMAEInPixels, - &finalMAEInPixels, - &initialMAEInMM, - &finalMAEInMM - ) - ) - { - - /* No longer automatically do this to avoid multiple smoothings per frame - if ( (fSampling>0.0) && (fCutoff>0.0) ) - { //Only perform smoothing if sampling/cutoff is set.. - for (int mID=0; mIDbufferSize; mID++) - { - atomicSolution->motion[mID] = butterWorth_filterArrayElement(atomicSmoothingFilter,mID,atomicSolution->motion[mID]); - } - }*/ - - - - if(!bvh_copyMotionBufferToMotionFrame( - &bvhAtomicMotion, - frameID, - atomicSolution - ) - ) - { - fprintf(stderr,RED "Failed bvh_copyMotionBufferToMotionFrame\n" NORMAL); - } - - //Perform and update projections for new results..! - bvhConverter_processFrame(frameID); - } else - { - fprintf(stderr,RED "Failed approximateBodyFromMotionBufferUsingInverseKinematics\n" NORMAL); - } - } - - return finalMAEInPixels; -} - - -int bvhConverter_smooth(int frameID,float fSampling,float fCutoff) -{ - if (atomicSolution==0) { fprintf(stderr,RED "bvhConverter_smooth has no solution to work with..\n" NORMAL); return 0; } - if (atomicSmoothingFilter==0) { fprintf(stderr,RED "bvhConverter_smooth has no initialized filter to work with..\n" NORMAL); return 0; } - - if ( (fSampling>0.0) && (fCutoff>0.0) ) - { //Only perform smoothing if sampling/cutoff is set.. - fprintf(stderr,GREEN "bvhConverter_smooth going through motions\n" NORMAL); - for (int mID=0; mIDbufferSize; mID++) - { - atomicSolution->motion[mID] = butterWorth_filterArrayElement(atomicSmoothingFilter,mID,atomicSolution->motion[mID]); - } - - - - fprintf(stderr,GREEN "copyback..\n" NORMAL); - if(!bvh_copyMotionBufferToMotionFrame( - &bvhAtomicMotion, - frameID, - atomicSolution - ) - ) - { - fprintf(stderr,RED "Failed bvh_copyMotionBufferToMotionFrame\n" NORMAL); - } - - //Perform and update projections for new results..! - bvhConverter_processFrame(frameID); - return 1; - } - return 0; -} - - - -int bvhConverter(int argc,const char **argv) -{ - fprintf(stderr,RED "BVHConverter.c main is a stub please use the python code\n" NORMAL); - return 0; -} - -int main(int argc,const char **argv) -{ - fprintf(stderr,RED "BVHConverter.c main is a stub please use the python code\n" NORMAL); - return 0; -} - diff --git a/src/python/mnet4/BVH/bvhConverter.py b/src/python/mnet4/BVH/bvhConverter.py deleted file mode 100644 index 0cda3c6..0000000 --- a/src/python/mnet4/BVH/bvhConverter.py +++ /dev/null @@ -1,881 +0,0 @@ -#!/usr/bin/python3 - -import ctypes -import os -import sys -from ctypes import * -from os.path import exists - -class bcolors: - HEADER = '\033[95m' - OKBLUE = '\033[94m' - OKGREEN = '\033[92m' - WARNING = '\033[93m' - FAIL = '\033[91m' - ENDC = '\033[0m' - BOLD = '\033[1m' - UNDERLINE = '\033[4m' - - -#This mimics the calibration files like ; -# https://github.com/AmmarkoV/RGBDAcquisition/blob/master/tools/Calibration/calibration.c -def readCalibrationFromFile(filename): - calib = dict() - if filename is None: - return calib - - fp = None - try: - fp = open(filename, "r") - except IOError: - return calib - - # Our state - # ---------------------------- - i = 0 - category = 0 - line_length = 0 - lines_at_current_category = 0 - # ---------------------------- - - - for line in fp: - #-------------------------------------- - line = line.rstrip("\r\n") - line_length = len(line) - #-------------------------------------- - if line_length > 0: - if line[line_length - 1] == '\n': - line = line[:-1] - if line[line_length - 1] == '\r': - line = line[:-1] - #-------------------------------------- - if line_length > 1: - if line[line_length - 2] == '\n': - line = line[:-2] - if line[line_length - 2] == '\r': - line = line[:-2] - #-------------------------------------- - if line[0] == '%': - lines_at_current_category = 0 - #-------------------------------------- - # ---------------------------- ---------------------------- ---------------------------- - if line == "%I": - category = 1 - calib["intrinsic"] = list() - elif line == "%D": - category = 2 - elif line == "%T": - category = 3 - calib["extrinsicTranslation"] = list() - elif line == "%R": - category = 4 - calib["extrinsicRotationRodriguez"] = list() - elif line == "%NF": - category = 5 - elif line == "%UNIT": - category = 6 - elif line == "%RT4*4": - category = 7 - calib["extrinsic"] = list() - elif line == "%Width": - category = 8 - elif line == "%Height": - category = 9 - else: - # ---------------------------- ---------------------------- ---------------------------- - if category == 1: - calib["intrinsicParametersSet"] = 1 - lines_at_current_category = min(lines_at_current_category, 9) - calib["intrinsic"].append(float(line)) - lines_at_current_category += 1 - if (lines_at_current_category==9): - category = 0 - elif category == 2: - if lines_at_current_category == 0: - calib["k1"] = float(line) - elif lines_at_current_category == 1: - calib["k2"] = float(line) - elif lines_at_current_category == 2: - calib["p1"] = float(line) - elif lines_at_current_category == 3: - calib["p2"] = float(line) - elif lines_at_current_category == 4: - calib["k3"] = float(line) - lines_at_current_category += 1 - if (lines_at_current_category==4): - category = 0 - elif category == 3: - calib["extrinsicParametersSet"] = 1 - lines_at_current_category = min(lines_at_current_category, 3) - calib["extrinsicTranslation"].append(float(line)) - lines_at_current_category += 1 - if (lines_at_current_category==3): - category = 0 - elif category == 4: - lines_at_current_category = min(lines_at_current_category, 3) - calib["extrinsicRotationRodriguez"].append(float(line)) - lines_at_current_category += 1 - if (lines_at_current_category==3): - category = 0 - elif category == 5: - calib["nearPlane"] = float(line) - category = 0 - elif category == 6: - calib["farPlane"] = float(line) - category = 0 - elif category == 7: - lines_at_current_category = min(lines_at_current_category, 16) - calib["extrinsic"].append(float(line)) - lines_at_current_category += 1 - category = 0 - elif category == 8: - calib["width"] = int(line) - category = 0 - elif category == 9: - calib["height"] = int(line) - category = 0 - # ---------------------------- ---------------------------- ---------------------------- - - fp.close() - - try: - calib["fX"] = calib["intrinsic"][0] - calib["fY"] = calib["intrinsic"][4] - calib["cX"] = calib["intrinsic"][2] - calib["cY"] = calib["intrinsic"][5] - except: - print("No intrinsic matrix declared in ", filename) - print("Cannot populate fX, fY, cX, cY") - - - print("New calibration loaded : ",calib) - - return calib - - - -def loadLibrary(filename,relativePath="",forceUpdate=False): -#-------------------------------------------------------- - if (relativePath!=""): - filename=relativePath+"/"+filename - - if (forceUpdate) or (not exists(filename)): - print("Could not find BVH Library (",filename,"), compiling a fresh one..!") - print("Current directory was (",os.getcwd(),") ") - directory=os.path.dirname(os.path.abspath(filename)) - creationScript = directory+"/makeLibrary.sh" - os.system(creationScript) - #Magic JIT Just in time compilation, java has nothing on this :P - if not exists(filename): - directory=os.path.dirname(os.path.abspath(filename)) - print("Could not make BVH Library, terminating") - print("Directory we tried was : ",directory) - sys.exit(0) - libBVH = CDLL(filename) - #call C function to check connection - libBVH.connect() - libBVH.bvhConverter.restype = c_int - libBVH.bvhConverter.argtypes = c_int,POINTER(c_char_p) - return libBVH -#-------------------------------------------------------- - - -def splitDictionaryInLabelsAndFloats(arguments): - #First prepare the labels of the joints we want to transmit - #-------------------------------------------------- - labels = list(arguments.keys()) - labelsBytes = [] - for i in range(len(labels)): - #Potential renaming.. - #--------------------------------------------- - #if ("endsite_" in labels[i]): - # if ("eye" in labels[i]): - # datasplit = labels[i].split("endsite_",1) - # newLabel="%s%s" % (datasplit[0],datasplit[1]) - # print(labels[i]," renamed to -> ",newLabel) - #--------------------------------------------- - labelsBytes.append(bytes(labels[i], 'utf-8')) - labelsCStr = (ctypes.c_char_p * len(labelsBytes))() - labelsCStr[:] = labelsBytes - #-------------------------------------------------- - - #Then prepare the array of floats we want to transmit - #-------------------------------------------------- - values = list(arguments.values()) - valuesF = list() - for v in values: - try: - valuesF.append(float(v)) - except: - print("Argument ",v,"cannot be casted to float..") - valuesF.append(0.0) - valuesArray = (ctypes.c_float * len(valuesF))() - valuesArray[:] = valuesF - #-------------------------------------------------- - - argc=len(labelsBytes) - - return labelsCStr,valuesArray,argc -#-------------------------------------------------------- - - -class BVH(): - def __init__( - self, - bvhPath:str, - libraryPath:str = "./libBVHConverter.so", - cameraCalibrationFile = "", - forceLibUpdate=False - ): - print("Initializing BVH file ",bvhPath," from ",libraryPath) - self.libBVH = loadLibrary(libraryPath,forceUpdate = forceLibUpdate) - self.numberOfJoints = 0 - self.lastMAEErrorInPixels = 0.0 - self.traceStages = False #If set to true each call will be emitted in stdout to speed-up debugging - self.calib = dict() - #----------------------------------- - if (cameraCalibrationFile!=""): - if not exists(cameraCalibrationFile): - print("Could not find renderer configuration file ",cameraCalibrationFile) - raise FileNotFoundError - self.configureRendererFromFile(cameraCalibrationFile) - #----------------------------------- - if not exists(bvhPath): - print("Could not find BVH file ",bvhPath) - raise FileNotFoundError - self.loadBVHFile(bvhPath) - #-------------------------------------------------------- - def stage(self,message): - if (self.traceStages): - print(bcolors.WARNING,message,bcolors.ENDC) - #-------------------------------------------------------- - def loadBVHFile(self,bvhPath): - self.stage("loadBVHFile") - # create byte objects from the strings - arg1 = bvhPath.encode('utf-8') - # send strings to c function - self.libBVH.bvhConverter_loadAtomic.argtypes = [ctypes.c_char_p] - self.libBVH.bvhConverter_loadAtomic.restype = ctypes.c_int - self.numberOfJoints = self.libBVH.bvhConverter_loadAtomic(arg1) - if (self.numberOfJoints==0): - print("Failed to load BVH file ",bvhPath) - return self.numberOfJoints - #-------------------------------------------------------- - def scale(self, scaleRatio:float): - self.stage("scale") - self.libBVH.bvhConverter_scale.argtypes = [ctypes.c_float] - self.libBVH.bvhConverter_scale.restype = ctypes.c_int - return str(self.libBVH.bvhConverter_scale(scaleRatio)); - #-------------------------------------------------------- - def getJointName(self, jointID:int): - self.stage("getJointName") - self.libBVH.bvhConverter_getJointNameFromJointID.argtypes = [ctypes.c_int] - self.libBVH.bvhConverter_getJointNameFromJointID.restype = ctypes.c_char_p - return str(self.libBVH.bvhConverter_getJointNameFromJointID(jointID).decode('UTF-8')); - #-------------------------------------------------------- - def isJointEndSite(self, jointID:int): - self.stage("isJointEndSite") - self.libBVH.bvhConverter_isJointEndSite.argtypes = [ctypes.c_int] - self.libBVH.bvhConverter_isJointEndSite.restype = ctypes.c_int - retval = self.libBVH.bvhConverter_isJointEndSite(jointID) - return retval - #-------------------------------------------------------- - def getJointParent(self, jointID:int): - self.stage("getJointParent") - self.libBVH.bvhConverter_getJointParent.argtypes = [ctypes.c_int] - self.libBVH.bvhConverter_getJointParent.restype = ctypes.c_int - jointID = self.libBVH.bvhConverter_getJointParent(jointID) - return jointID - #-------------------------------------------------------- - def getJointParentList(self): - self.stage("getJointParentList") - jointList = list() - for jointID in range(0,self.numberOfJoints): - jointList.append(int(self.getJointParent(jointID))) - return jointList - #-------------------------------------------------------- - def getMotionValueOfFrame(self, frameID:int, jointID:int): - self.stage("getMotionValueOfFrame") - self.libBVH.bvhConverter_getMotionValueOfFrame.argtypes = [ctypes.c_int,ctypes.c_int] - self.libBVH.bvhConverter_getMotionValueOfFrame.restype = ctypes.c_float - value = self.libBVH.bvhConverter_getMotionValueOfFrame(frameID,jointID) - return value - #-------------------------------------------------------- - def getAllMotionValuesOfFrame(self, frameID:int): - allMIDs=list() - for mID in range(0,self.getNumberOfMotionValuesPerFrame()): - allMIDs.append(self.getMotionValueOfFrame(frameID,mID)) - return allMIDs - #-------------------------------------------------------- - def saveBVHFileFromList(self, filename:str, allMotionData:list): - self.stage("saveBVHFileFromList") - arg1 = filename.encode('utf-8') - #int bvhConverter_writeBVH(char * filename,int writeHierarchy,int writeMotion) - self.libBVH.bvhConverter_writeBVH.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.c_int] - self.libBVH.bvhConverter_writeBVH.restype = ctypes.c_int - success = self.libBVH.bvhConverter_writeBVH(arg1,1,0) #Just write the Hierarchy part of the BVH file - - if (success): - numberOfFrames = len(allMotionData) - f = open(filename, 'a') - f.write("MOTION\n"); - f.write("Frames: %u\n"%numberOfFrames); - f.write("Frame Time: %0.8f\n"%(float(1/24)) ); - for fID in range(0,numberOfFrames): - i=0 - for mID in allMotionData[fID]: - if (i>0): - f.write(' ') - if (mID==0.0): - f.write("0") - else: - f.write("%0.4f" % mID) - i=i+1 - f.write('\n') - f.close() - - #-------------------------------------------- - os.system("sed -i 's/rcollar/rCollar/g' out.bvh") - os.system("sed -i 's/rshoulder/rShldr/g' out.bvh") - os.system("sed -i 's/relbow/rForeArm/g' out.bvh") - os.system("sed -i 's/rhand/rHand/g' out.bvh") - #-------------------------------------------- - os.system("sed -i 's/lcollar/lCollar/g' out.bvh") - os.system("sed -i 's/lshoulder/lShldr/g' out.bvh") - os.system("sed -i 's/lelbow/lForeArm/g' out.bvh") - os.system("sed -i 's/lhand/lHand/g' out.bvh") - #-------------------------------------------- - os.system("sed -i 's/rhip/rThigh/g' out.bvh") - os.system("sed -i 's/rknee/rShin/g' out.bvh") - os.system("sed -i 's/rfoot/rFoot/g' out.bvh") - #------------------------------------------------------ - os.system("sed -i 's/lhip/lThigh/g' out.bvh") - os.system("sed -i 's/lknee/lShin/g' out.bvh") - os.system("sed -i 's/lfoot/lFoot/g' out.bvh") - - - return success - #-------------------------------------------------------- - def setMotionValueOfFrame(self, frameID:int, jointID:int, value:float): - self.stage("setMotionValueOfFrame") - self.libBVH.bvhConverter_setMotionValueOfFrame.argtypes = [ctypes.c_int,ctypes.c_int,ctypes.c_float] - self.libBVH.bvhConverter_setMotionValueOfFrame.restype = ctypes.c_int - success = self.libBVH.bvhConverter_setMotionValueOfFrame(frameID,jointID,value) - return success - #-------------------------------------------------------- - def getNumberOfMotionValuesPerFrame(self): - self.stage("getNumberOfMotionValuesPerFrame") - self.libBVH.bvhConverter_getNumberOfMotionValuesPerFrame.argtypes = [] - self.libBVH.bvhConverter_getNumberOfMotionValuesPerFrame.restype = ctypes.c_int - jointID = self.libBVH.bvhConverter_getNumberOfMotionValuesPerFrame() - return jointID - #-------------------------------------------------------- - def getNumberOfJoints(self): - self.stage("getNumberOfJoints") - self.libBVH.bvhConverter_getNumberOfJoints.argtypes = [] - self.libBVH.bvhConverter_getNumberOfJoints.restype = ctypes.c_int - jointID = self.libBVH.bvhConverter_getNumberOfJoints() - return jointID - #-------------------------------------------------------- - def getJointID(self, jointName:str): - self.stage("getJointID") - arg1 = jointName.encode('utf-8') - self.libBVH.bvhConverter_getJointNameJointID.argtypes = [ctypes.c_char_p] - self.libBVH.bvhConverter_getJointNameJointID.restype = ctypes.c_int - jointID = self.libBVH.bvhConverter_getJointNameJointID(arg1) - return jointID - #-------------------------------------------------------- - def getJointList(self): - self.stage("getJointList") - jointList = list() - for jointID in range(0,self.numberOfJoints): - jointList.append(self.getJointName(jointID)) - return jointList - #-------------------------------------------------------- - def getJointRotationsForFrame(self, jointID:int, frameID:int): - self.stage("getJointRotationsForFrame") - if (self.isJointEndSite(jointID)==1): - xRot=0.0 - yRot=0.0 - zRot=0.0 - else: - #-------------------------------------------------------- - self.libBVH.bvhConverter_getBVHJointRotationXForFrame.argtypes = [ctypes.c_int, ctypes.c_int] - self.libBVH.bvhConverter_getBVHJointRotationXForFrame.restype = ctypes.c_float - xRot = self.libBVH.bvhConverter_getBVHJointRotationXForFrame(frameID,jointID) - #-------------------------------------------------------- - self.libBVH.bvhConverter_getBVHJointRotationYForFrame.argtypes = [ctypes.c_int, ctypes.c_int] - self.libBVH.bvhConverter_getBVHJointRotationYForFrame.restype = ctypes.c_float - yRot = self.libBVH.bvhConverter_getBVHJointRotationYForFrame(frameID,jointID) - #-------------------------------------------------------- - self.libBVH.bvhConverter_getBVHJointRotationZForFrame.argtypes = [ctypes.c_int, ctypes.c_int] - self.libBVH.bvhConverter_getBVHJointRotationZForFrame.restype = ctypes.c_float - zRot = self.libBVH.bvhConverter_getBVHJointRotationZForFrame(frameID,jointID) - #-------------------------------------------------------- - return xRot,yRot,zRot - #-------------------------------------------------------- - def getJoint3D(self, jointID:int): - self.stage("getJoint3D") - self.libBVH.bvhConverter_get3DX.argtypes = [ctypes.c_int] - self.libBVH.bvhConverter_get3DX.restype = ctypes.c_float - x3D = self.libBVH.bvhConverter_get3DX(jointID) - - self.libBVH.bvhConverter_get3DY.argtypes = [ctypes.c_int] - self.libBVH.bvhConverter_get3DY.restype = ctypes.c_float - y3D = self.libBVH.bvhConverter_get3DY(jointID) - - self.libBVH.bvhConverter_get3DZ.argtypes = [ctypes.c_int] - self.libBVH.bvhConverter_get3DZ.restype = ctypes.c_float - z3D = self.libBVH.bvhConverter_get3DZ(jointID) - - return x3D,y3D,z3D - #-------------------------------------------------------- - def getJoint2D(self, jointID:int): - self.stage("getJoint2D") - self.libBVH.bvhConverter_get2DX.argtypes = [ctypes.c_int] - self.libBVH.bvhConverter_get2DX.restype = ctypes.c_float - x2D = self.libBVH.bvhConverter_get2DX(jointID) - - self.libBVH.bvhConverter_get2DY.argtypes = [ctypes.c_int] - self.libBVH.bvhConverter_get2DY.restype = ctypes.c_float - y2D = self.libBVH.bvhConverter_get2DY(jointID) - - #Flip X - if (x2D!=0.0) or (y2D!=0.0): - x2D = 1.0 - x2D - - return x2D,y2D - #-------------------------------------------------------- - def getJoint3DUsingJointName(self, jointName:str): - return self.getJoint3D(self.getJointID(jointName)) - #-------------------------------------------------------- - def getJoint2DUsingJointName(self, jointName:str): - return self.getJoint2D(self.getJointID(jointName)) - #-------------------------------------------------------- - def processFrame(self, frameID:int): - self.stage("processFrame") - self.libBVH.bvhConverter_processFrame.argtypes = [ctypes.c_int] - self.libBVH.bvhConverter_processFrame.restype = ctypes.c_int - success = self.libBVH.bvhConverter_processFrame(frameID) - return success - #-------------------------------------------------------- - def modify(self,arguments:dict,frameID=0): - self.stage("modify") - #print("BVH modify called with : ",arguments) - if (not arguments): - print("BVH modify called without arguments") - return 0 - #Arguments is a dict with a lot of key/value pairs we want to transmit to the C code - labelsCStr,valuesArray,argc = splitDictionaryInLabelsAndFloats(arguments) - self.libBVH.bvhConverter_modifyAtomic.argtypes = [ctypes.POINTER(ctypes.c_char_p), ctypes.POINTER(ctypes.c_float), ctypes.c_int, ctypes.c_int] - success = self.libBVH.bvhConverter_modifyAtomic(labelsCStr,valuesArray,argc,frameID) - return success - #-------------------------------------------------------- - def configureRenderer(self,arguments:dict): - #Arguments is a dict with a lot of key/value pairs we want to transmit to the C code - labelsCStr,valuesArray,argc = splitDictionaryInLabelsAndFloats(arguments) - self.libBVH.bvhConverter_rendererConfigurationAtomic.argtypes = [ctypes.POINTER(ctypes.c_char_p), ctypes.POINTER(ctypes.c_float), ctypes.c_int] - self.libBVH.bvhConverter_rendererConfigurationAtomic(labelsCStr,valuesArray,argc) - #-------------------------------------------------------- - def configureRendererFromFile(self,cameraCalibrationFile:str): - #from calibration import readCalibrationFromFile - self.calib = readCalibrationFromFile(cameraCalibrationFile) - if (self.calib): - print("We found a calibration in file ",cameraCalibrationFile) - print("calib : ",self.calib) - self.configureRenderer(self.calib) - #-------------------------------------------------------- - def get2DAnd3DAndBVHDictsForFrame(self,frameID=0): - self.stage("get2DAnd3DAndBVHDictsForFrame ") - #Arguments is a dict with a lot of key/value pairs we want to transmit to the C code - self.processFrame(frameID=frameID) - - #Our output - #--------------- - data2D = dict() - data3D = dict() - dataBVH = dict() - #--------------- - - for jointID in range(0,self.numberOfJoints): - #------------------------------------------------------- - #print("joint ID = ",jointID) - #------------------------------------------- - jointName = self.getJointName(jointID).lower() - #------------------------------------------- - #print("Getting 3D") - x3D,y3D,z3D = self.getJoint3D(jointID) - data3D["3DX_"+jointName]=float(x3D) - data3D["3DY_"+jointName]=float(y3D) - data3D["3DZ_"+jointName]=float(z3D) - #------------------------------------------- - #print("Getting 2D") - x2D,y2D = self.getJoint2D(jointID) - data2D["2DX_"+jointName]=float(x2D) - data2D["2DY_"+jointName]=float(y2D) - #------------------------------------------- - #print("Getting Joint Rotations") - if (self.isJointEndSite(jointID)==0): #Do not try to recover rotations for EndSites (they dont have rotations) - xRot,yRot,zRot = self.getJointRotationsForFrame(jointID,frameID) - if (jointID==0): - dataBVH[jointName+"_Xposition"]=float(x3D) - dataBVH[jointName+"_Yposition"]=float(y3D) - dataBVH[jointName+"_Zposition"]=float(z3D) - dataBVH[jointName+"_Xrotation"]=float(xRot) - dataBVH[jointName+"_Yrotation"]=float(yRot) - dataBVH[jointName+"_Zrotation"]=float(zRot) - #------------------------------------------------------- - return data2D,data3D,dataBVH - #-------------------------------------------------------- - def fineTuneToMatch(self,bodyPart:str,target:dict,frameID=0,iterations=20,epochs=30,lr=0.01,fSampling=30.0,fCutoff=5.0,langevinDynamics=0.0): - self.stage("fineTuneToMatch ") - bodyPartCStr = bytes(bodyPart, 'utf-8') - - #Arguments is a dict with a lot of key/value pairs we want to transmit to the C code - labelsCStr,valuesArray,argc = splitDictionaryInLabelsAndFloats(target) - self.libBVH.bvhConverter_IKFineTune.argtypes = [ctypes.c_char_p, ctypes.POINTER(ctypes.c_char_p), ctypes.POINTER(ctypes.c_float), ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_float, ctypes.c_float, ctypes.c_float,ctypes.c_float] - self.libBVH.bvhConverter_IKFineTune.restype = ctypes.c_float - accuracy2D = self.libBVH.bvhConverter_IKFineTune(bodyPartCStr,labelsCStr,valuesArray,argc,frameID,iterations,epochs,lr,fSampling,fCutoff,langevinDynamics) - print("HCD results for ",iterations," iterations ~> %0.2f pixels!" % accuracy2D) - self.lastMAEErrorInPixels = accuracy2D - return self.get2DAnd3DAndBVHDictsForFrame(frameID=frameID) - - #return dict() - #-------------------------------------------------------- - - def smooth(self,frameID=0,fSampling=30.0,fCutoff=5.0): - self.stage("smooth ") - #This call assumes that is called after subsequent(?) calls to fineTuneToMatch that have transmitted the BVH state..! - self.libBVH.bvhConverter_smooth.argtypes = [ctypes.c_int, ctypes.c_float, ctypes.c_float] - self.libBVH.bvhConverter_smooth.restype = ctypes.c_int - result = self.libBVH.bvhConverter_smooth(frameID,fSampling,fCutoff) - return result==1 - - #return dict() - #-------------------------------------------------------- - - - -if __name__== "__main__": - bvhFile = BVH(bvhPath="./headerWithHeadAndOneMotion.bvh",forceLibUpdate=True) - - print("File has ",bvhFile.numberOfJoints," joints") - - print(" Joint List : ",bvhFile.getJointList()) - print(" Joint Parent List : ",bvhFile.getJointParentList()) - - modifications = dict() - modifications["hip_Xposition"]=100.0 - modifications["hip_Yposition"]=100.0 - modifications["hip_Zposition"]=-400.0 - modifications["hip_Xrotation"]=1.0 - modifications["hip_Yrotation"]=2.0 - modifications["hip_Zrotation"]=4.0 - bvhFile.modify(modifications) - jointName = "neck" - print("Joint ID for ",jointName," is ",bvhFile.getJointID(jointName)) - - frameID=0 - - for i in range(0,10): - modifications["hip_Xposition"]=100.0 + i * 10.0 - bvhFile.modify(modifications) - bvhFile.processFrame(frameID) - x3D,y3D,z3D = bvhFile.getJoint3DUsingJointName(jointName) - print(" I=",i," Joint=",jointName," 3D values for frame ",frameID," are ",x3D,",",y3D,",",z3D," ") - - x2D,y2D = bvhFile.getJoint2DUsingJointName(jointName) - print(" Joint ",jointName," 2D values for frame ",frameID," are ",x2D,",",y2D) - - target2D = dict() - target2D["2dx_head"]=0.4722689390182495 - target2D["2dy_head"]=0.1971915066242218 - target2D["visible_head"]=0.9999899864196777 - target2D["2dx_head_leye_0"]=0.46880775690078735 - target2D["2dy_head_leye_0"]=0.1777663230895996 - target2D["visible_head_leye_0"]=0.0 - target2D["2dx_endsite_eye.l"]=0.4609638452529907 - target2D["2dy_endsite_eye.l"]=0.18265053629875183 - target2D["visible_endsite_eye.l"]=0.9999715089797974 - target2D["2dx_head_leye_3"]=0.4599621295928955 - target2D["2dy_head_leye_3"]=0.17712406814098358 - target2D["visible_head_leye_3"]=0.0 - target2D["2dx_head_reye_3"]=0.4780046343803406 - target2D["2dy_head_reye_3"]=0.17705687880516052 - target2D["visible_head_reye_3"]=0.0 - target2D["2dx_endsite_eye.r"]=0.47662675380706787 - target2D["2dy_endsite_eye.r"]=0.17989128828048706 - target2D["visible_endsite_eye.r"]=0.9999359846115112 - target2D["2dx_head_reye_0"]=0.4844837188720703 - target2D["2dy_head_reye_0"]=0.17520418763160706 - target2D["visible_head_reye_0"]=0.0 - target2D["2dx_lear"]=0.4520418643951416 - target2D["2dy_lear"]=0.1892796754837036 - target2D["visible_lear"]=0.9999462366104126 - target2D["2dx_rear"]=0.47904515266418457 - target2D["2dy_rear"]=0.18252810835838318 - target2D["visible_rear"]=0.999884843826294 - target2D["2dx_head_outmouth_0"]=0.4792625308036804 - target2D["2dy_head_outmouth_0"]=0.20948739349842072 - target2D["visible_head_outmouth_0"]=0.0 - target2D["2dx_head_outmouth_6"]=0.4670618176460266 - target2D["2dy_head_outmouth_6"]=0.2107415348291397 - target2D["visible_head_outmouth_6"]=0.0 - target2D["2dx_lshoulder"]=0.43696290254592896 - target2D["2dy_lshoulder"]=0.2820419669151306 - target2D["visible_lshoulder"]=0.9999864101409912 - target2D["2dx_rshoulder"]=0.5070507228374481 - target2D["2dy_rshoulder"]=0.2563856244087219 - target2D["visible_rshoulder"]=0.9998794794082642 - target2D["2dx_lelbow"]=0.4353405833244324 - target2D["2dy_lelbow"]=0.3725816607475281 - target2D["visible_lelbow"]=0.9570146799087524 - target2D["2dx_relbow"]=0.5561909377574921 - target2D["2dy_relbow"]=0.323696106672287 - target2D["visible_relbow"]=0.974626362323761 - target2D["2dx_lhand"]=0.41237425804138184 - target2D["2dy_lhand"]=0.4415815770626068 - target2D["visible_lhand"]=0.9751281142234802 - target2D["2dx_rhand"]=0.6100260317325592 - target2D["2dy_rhand"]=0.36322692036628723 - target2D["visible_rhand"]=0.9656738638877869 - target2D["2dx_left_hand_pinky_4"]=0.40250515937805176 - target2D["2dy_left_hand_pinky_4"]=0.468822181224823 - target2D["visible_left_hand_pinky_4"]=0.9458165764808655 - target2D["2dx_right_hand_pinky_4"]=0.6301354765892029 - target2D["2dy_right_hand_pinky_4"]=0.36382848024368286 - target2D["visible_right_hand_pinky_4"]=0.9336408376693726 - target2D["2dx_left_hand_index_4"]=0.4018949866294861 - target2D["2dy_left_hand_index_4"]=0.46844327449798584 - target2D["visible_left_hand_index_4"]=0.9523322582244873 - target2D["2dx_right_hand_index_4"]=0.6305072009563446 - target2D["2dy_right_hand_index_4"]=0.361262708902359 - target2D["visible_right_hand_index_4"]=0.9438664317131042 - target2D["2dx_left_hand_thumb_4"]=0.40644168853759766 - target2D["2dy_left_hand_thumb_4"]=0.4600275158882141 - target2D["visible_left_hand_thumb_4"]=0.9461172819137573 - target2D["2dx_right_hand_thumb_4"]=0.6229645609855652 - target2D["2dy_right_hand_thumb_4"]=0.3644818663597107 - target2D["visible_right_hand_thumb_4"]=0.9423035979270935 - target2D["2dx_lhip"]=0.4714365005493164 - target2D["2dy_lhip"]=0.49608662724494934 - target2D["visible_lhip"]=0.9997338652610779 - target2D["2dx_rhip"]=0.5158871114253998 - target2D["2dy_rhip"]=0.48727133870124817 - target2D["visible_rhip"]=0.9996464252471924 - target2D["2dx_lknee"]=0.46627652645111084 - target2D["2dy_lknee"]=0.6712287068367004 - target2D["visible_lknee"]=0.9964113831520081 - target2D["2dx_rknee"]=0.5255001187324524 - target2D["2dy_rknee"]=0.6690568923950195 - target2D["visible_rknee"]=0.997963547706604 - target2D["2dx_lfoot"]=0.45652568340301514 - target2D["2dy_lfoot"]=0.799410879611969 - target2D["visible_lfoot"]=0.9943563342094421 - target2D["2dx_rfoot"]=0.5572476387023926 - target2D["2dy_rfoot"]=0.8126811981201172 - target2D["visible_rfoot"]=0.9981330037117004 - target2D["2dx_lheel"]=0.46518951654434204 - target2D["2dy_lheel"]=0.8143126964569092 - target2D["visible_lheel"]=0.9416220784187317 - target2D["2dx_rheel"]=0.5635095536708832 - target2D["2dy_rheel"]=0.8311944007873535 - target2D["visible_rheel"]=0.9331190586090088 - target2D["2dx_endsite_toe1-2.l"]=0.44505226612091064 - target2D["2dy_endsite_toe1-2.l"]=0.8585301637649536 - target2D["visible_endsite_toe1-2.l"]=0.9915184378623962 - target2D["2dx_endsite_toe1-2.r"]=0.5560439825057983 - target2D["2dy_endsite_toe1-2.r"]=0.8789670467376709 - target2D["visible_endsite_toe1-2.r"]=0.9957401752471924 - target2D["2dx_head_outmouth_3"]=0.4757194519042969 - target2D["2dy_head_outmouth_3"]=0.2063373625278473 - target2D["visible_head_outmouth_3"]=0.0 - target2D["2dx_head_nosebone_3"]=0.47762298583984375 - target2D["2dy_head_nosebone_3"]=0.19625969231128693 - target2D["visible_head_nosebone_3"]=0.0 - target2D["2dx_head_nostrills_2"]=0.4758298993110657 - target2D["2dy_head_nostrills_2"]=0.19925406575202942 - target2D["visible_head_nostrills_2"]=0.0 - target2D["2dx_head_nosebone_2"]=0.47625207901000977 - target2D["2dy_head_nosebone_2"]=0.1822354793548584 - target2D["visible_head_nosebone_2"]=0.0 - target2D["2dx_head_nosebone_1"]=0.47568726539611816 - target2D["2dy_head_nosebone_1"]=0.17861950397491455 - target2D["visible_head_nosebone_1"]=0.0 - target2D["2dx_head_inmouth_2"]=0.47532206773757935 - target2D["2dy_head_inmouth_2"]=0.20903927087783813 - target2D["visible_head_inmouth_2"]=0.0 - target2D["2dx_head_inmouth_6"]=0.4751202464103699 - target2D["2dy_head_inmouth_6"]=0.21265165507793427 - target2D["visible_head_inmouth_6"]=0.0 - target2D["2dx_head_outmouth_9"]=0.47498154640197754 - target2D["2dy_head_outmouth_9"]=0.21609455347061157 - target2D["visible_head_outmouth_9"]=0.0 - target2D["2dx_head_outmouth_2"]=0.48239976167678833 - target2D["2dy_head_outmouth_2"]=0.17039550840854645 - target2D["visible_head_outmouth_2"]=0.0 - target2D["2dx_head_outmouth_1"]=0.47901231050491333 - target2D["2dy_head_outmouth_1"]=0.2075507491827011 - target2D["visible_head_outmouth_1"]=0.0 - target2D["2dx_head_inmouth_1"]=0.47740042209625244 - target2D["2dy_head_inmouth_1"]=0.20893418788909912 - target2D["visible_head_inmouth_1"]=0.0 - target2D["2dx_head_reyebrow_4"]=0.4785383939743042 - target2D["2dy_head_reyebrow_4"]=0.1684335619211197 - target2D["visible_head_reyebrow_4"]=0.0 - target2D["2dx_head_reyebrow_1"]=0.4863806962966919 - target2D["2dy_head_reyebrow_1"]=0.16322830319404602 - target2D["visible_head_reyebrow_1"]=0.0 - target2D["2dx_head_reyebrow_3"]=0.48255395889282227 - target2D["2dy_head_reyebrow_3"]=0.16279345750808716 - target2D["visible_head_reyebrow_3"]=0.0 - target2D["2dx_head_reyebrow_0"]=0.4868544340133667 - target2D["2dy_head_reyebrow_0"]=0.16618424654006958 - target2D["visible_head_reyebrow_0"]=0.0 - target2D["2dx_head_inmouth_0"]=0.4783351421356201 - target2D["2dy_head_inmouth_0"]=0.20919831097126007 - target2D["visible_head_inmouth_0"]=0.0 - target2D["2dx_head_outmouth_10"]=0.47662580013275146 - target2D["2dy_head_outmouth_10"]=0.2154039442539215 - target2D["visible_head_outmouth_10"]=0.0 - target2D["2dx_head_outmouth_11"]=0.47859442234039307 - target2D["2dy_head_outmouth_11"]=0.2121586948633194 - target2D["visible_head_outmouth_11"]=0.0 - target2D["2dx_head_nostrills_1"]=0.4774726629257202 - target2D["2dy_head_nostrills_1"]=0.19906578958034515 - target2D["visible_head_nostrills_1"]=0.0 - target2D["2dx_head_nostrills_0"]=0.47901052236557007 - target2D["2dy_head_nostrills_0"]=0.19807150959968567 - target2D["visible_head_nostrills_0"]=0.0 - target2D["2dx_head_reyebrow_2"]=0.4849526286125183 - target2D["2dy_head_reyebrow_2"]=0.1620578020811081 - target2D["visible_head_reyebrow_2"]=0.0 - target2D["2dx_head_rchin_1"]=0.4867928624153137 - target2D["2dy_head_rchin_1"]=0.1828579306602478 - target2D["visible_head_rchin_1"]=0.0 - target2D["2dx_head_rchin_0"]=0.4863375425338745 - target2D["2dy_head_rchin_0"]=0.17663004994392395 - target2D["visible_head_rchin_0"]=0.0 - target2D["2dx_head_reye_4"]=0.4802663326263428 - target2D["2dy_head_reye_4"]=0.1782480776309967 - target2D["visible_head_reye_4"]=0.0 - target2D["2dx_head_rchin_2"]=0.4862361550331116 - target2D["2dy_head_rchin_2"]=0.195520281791687 - target2D["visible_head_rchin_2"]=0.0 - target2D["2dx_head_rchin_7"]=0.4762313961982727 - target2D["2dy_head_rchin_7"]=0.22934123873710632 - target2D["visible_head_rchin_7"]=0.0 - target2D["2dx_head_chin"]=0.473285973072052 - target2D["2dy_head_chin"]=0.2309272140264511 - target2D["visible_head_chin"]=0.0 - target2D["2dx_head_reye_2"]=0.4805048704147339 - target2D["2dy_head_reye_2"]=0.1739354431629181 - target2D["visible_head_reye_2"]=0.0 - target2D["2dx_head_reye_1"]=0.483254075050354 - target2D["2dy_head_reye_1"]=0.17367364466190338 - target2D["visible_head_reye_1"]=0.0 - target2D["2dx_head_nosebone_0"]=0.47514963150024414 - target2D["2dy_head_nosebone_0"]=0.1740616112947464 - target2D["visible_head_nosebone_0"]=0.0 - target2D["2dx_head_rchin_5"]=0.47995781898498535 - target2D["2dy_head_rchin_5"]=0.2206612378358841 - target2D["visible_head_rchin_5"]=0.0 - target2D["2dx_head_rchin_6"]=0.478015661239624 - target2D["2dy_head_rchin_6"]=0.22674590349197388 - target2D["visible_head_rchin_6"]=0.0 - target2D["2dx_head_inmouth_7"]=0.4774059057235718 - target2D["2dy_head_inmouth_7"]=0.21126437187194824 - target2D["visible_head_inmouth_7"]=0.0 - target2D["2dx_head_rchin_3"]=0.48438382148742676 - target2D["2dy_head_rchin_3"]=0.20477566123008728 - target2D["visible_head_rchin_3"]=0.0 - target2D["2dx_head_rchin_4"]=0.4815486669540405 - target2D["2dy_head_rchin_4"]=0.21430769562721252 - target2D["visible_head_rchin_4"]=0.0 - target2D["2dx_head_leye_4"]=0.4646076560020447 - target2D["2dy_head_leye_4"]=0.18126901984214783 - target2D["visible_head_leye_4"]=0.0 - target2D["2dx_head_leye_5"]=0.4679994583129883 - target2D["2dy_head_leye_5"]=0.17986340820789337 - target2D["visible_head_leye_5"]=0.0 - target2D["2dx_head_lchin_0"]=0.45561397075653076 - target2D["2dy_head_lchin_0"]=0.17862515151500702 - target2D["visible_head_lchin_0"]=0.0 - target2D["2dx_head_outmouth_4"]=0.47371816635131836 - target2D["2dy_head_outmouth_4"]=0.2061852067708969 - target2D["visible_head_outmouth_4"]=0.0 - target2D["2dx_head_inmouth_3"]=0.4718068242073059 - target2D["2dy_head_inmouth_3"]=0.20968028903007507 - target2D["visible_head_inmouth_3"]=0.0 - target2D["2dx_head_leyebrow_0"]=0.45764458179473877 - target2D["2dy_head_leyebrow_0"]=0.16966667771339417 - target2D["visible_head_leyebrow_0"]=0.0 - target2D["2dx_head_leyebrow_4"]=0.47118234634399414 - target2D["2dy_head_leyebrow_4"]=0.16888980567455292 - target2D["visible_head_leyebrow_4"]=0.0 - target2D["2dx_head_leyebrow_1"]=0.4586902856826782 - target2D["2dy_head_leyebrow_1"]=0.16491857171058655 - target2D["visible_head_leyebrow_1"]=0.0 - target2D["2dx_head_leyebrow_3"]=0.466333270072937 - target2D["2dy_head_leyebrow_3"]=0.16365274786949158 - target2D["visible_head_leyebrow_3"]=0.0 - target2D["2dx_head_inmouth_4"]=0.46794217824935913 - target2D["2dy_head_inmouth_4"]=0.21027947962284088 - target2D["visible_head_inmouth_4"]=0.0 - target2D["2dx_head_outmouth_8"]=0.4729023575782776 - target2D["2dy_head_outmouth_8"]=0.21605923771858215 - target2D["visible_head_outmouth_8"]=0.0 - target2D["2dx_head_nostrills_3"]=0.4738311171531677 - target2D["2dy_head_nostrills_3"]=0.19950126111507416 - target2D["visible_head_nostrills_3"]=0.0 - target2D["2dx_head_nostrills_4"]=0.47070103883743286 - target2D["2dy_head_nostrills_4"]=0.19887711107730865 - target2D["visible_head_nostrills_4"]=0.0 - target2D["2dx_head_leyebrow_2"]=0.46217411756515503 - target2D["2dy_head_leyebrow_2"]=0.16334131360054016 - target2D["visible_head_leyebrow_2"]=0.0 - target2D["2dx_head_lchin_1"]=0.4578735828399658 - target2D["2dy_head_lchin_1"]=0.18603739142417908 - target2D["visible_head_lchin_1"]=0.0 - target2D["2dx_head_lchin_2"]=0.45422083139419556 - target2D["2dy_head_lchin_2"]=0.19913220405578613 - target2D["visible_head_lchin_2"]=0.0 - target2D["2dx_head_lchin_7"]=0.4697527289390564 - target2D["2dy_head_lchin_7"]=0.2310400754213333 - target2D["visible_head_lchin_7"]=0.0 - target2D["2dx_head_leye_1"]=0.46723294258117676 - target2D["2dy_head_leye_1"]=0.1757526397705078 - target2D["visible_head_leye_1"]=0.0 - target2D["2dx_head_leye_2"]=0.4639958143234253 - target2D["2dy_head_leye_2"]=0.1749143898487091 - target2D["visible_head_leye_2"]=0.0 - target2D["2dx_head_lchin_5"]=0.4636954665184021 - target2D["2dy_head_lchin_5"]=0.22440548241138458 - target2D["visible_head_lchin_5"]=0.0 - target2D["2dx_head_lchin_6"]=0.46653610467910767 - target2D["2dy_head_lchin_6"]=0.22966337203979492 - target2D["visible_head_lchin_6"]=0.0 - target2D["2dx_head_inmouth_5"]=0.4714820384979248 - target2D["2dy_head_inmouth_5"]=0.21212585270404816 - target2D["visible_head_inmouth_5"]=0.0 - target2D["2dx_head_outmouth_7"]=0.4708884358406067 - target2D["2dy_head_outmouth_7"]=0.21508848667144775 - target2D["visible_head_outmouth_7"]=0.0 - target2D["2dx_head_lchin_3"]=0.4568250775337219 - target2D["2dy_head_lchin_3"]=0.20863905549049377 - target2D["visible_head_lchin_3"]=0.0 - target2D["2dx_head_lchin_4"]=0.4621039032936096 - target2D["2dy_head_lchin_4"]=0.217911496758461 - target2D["visible_head_lchin_4"]=0.0 - target2D["2dx_neck"]=0.47200681269168854 - target2D["2dy_neck"]=0.26921379566192627 - target2D["visible_neck"]=0.9999329447746277 - target2D["2dx_hip"]=0.4936618059873581 - target2D["2dy_hip"]=0.49167898297309875 - target2D["visible_hip"]=0.9996901452541351 - - print("fineTuneToMatch") - result = bvhFile.fineTuneToMatch("body",target2D,frameID=0,iterations=10,epochs=30) - #print("Result ",result) - diff --git a/src/python/mnet4/BVH/bvhLibrary.h b/src/python/mnet4/BVH/bvhLibrary.h deleted file mode 100644 index ccf3dcf..0000000 --- a/src/python/mnet4/BVH/bvhLibrary.h +++ /dev/null @@ -1,64 +0,0 @@ -/** @file bvhLibrary.h - * @brief BVH file parser part of https://github.com/AmmarkoV/RGBDAcquisition/tree/master/opengl_acquisition_shared_library/opengl_depth_and_color_renderer - This is the central header for the BVH library in order to compile it not as an executable file but as a real library! - To enable building as a library please compile the code with -DBVH_USE_AS_A_LIBRARY so that there is no main function included! - Don't forget, to check generated symbols : nm -gD libBVHConverter.so - * @author Ammar Qammaz (AmmarkoV) - */ -#ifndef BVH_STANDALONE_LIBRARY_H_INCLUDED -#define BVH_STANDALONE_LIBRARY_H_INCLUDED - -#ifdef __cplusplus -extern "C" -{ -#endif - - -int bvhConverter_writeBVH(char * filename,int writeHierarchy,int writeMotion); -int bvhConverter_getMotionValueOfFrame(int fID,int mID); -int bvhConverter_setMotionValueOfFrame(int fID,int mID,float value); - -int bvhConverter_loadAtomic(const char *path); -int bvhConverter_unloadAtomic(); - -int bvhConverter_scale(float scaleRatio); - -int bvhConverter_rendererConfigurationAtomic(const char ** labels,const float * values,int numberOfElements); -int bvhConverter_processFrame(int frameID); -int bvhConverter_getJointNameJointID(const char * jointName); - -int bvhConverter_getNumberOfMotionValuesPerFrame(); -int bvhConverter_getNumberOfJoints(); -const char * bvhConverter_getJointNameFromJointID(int jointID); - -int bvhConverter_getJointParent(int jointID); - -float bvhConverter_get3DX(int jointID); -float bvhConverter_get3DY(int jointID); -float bvhConverter_get3DZ(int jointID); - -float bvhConverter_get2DX(int jointID); -float bvhConverter_get2DY(int jointID); - -int bvhConverter_isJointEndSite(int jointID); - -float bvhConverter_getBVHJointRotationXForFrame(int frameID,int jointID); -float bvhConverter_getBVHJointRotationYForFrame(int frameID,int jointID); -float bvhConverter_getBVHJointRotationZForFrame(int frameID,int jointID); - -int bvhConverter_modifySingleAtomic(const char * label,const float value,int frameID); -int bvhConverter_modifyAtomic(const char ** labels,const float * values,int numberOfElements,int frameID); - -int bvhConverter_IKSetup(const char * bodyPart,const char ** labels,const float * values,int numberOfElements,int frameID); -float bvhConverter_IKFineTune(const char * bodyPart,const char ** labels,const float * values,int numberOfElements,int frameID,int iterations,int epochs,float lr,float fSampling,float fCutoff,float langevinDynamics); - - -int bvhConverter_smooth(int frameID,float fSampling,float fCutoff); - -int bvhConverter(int argc,const char **argv); - -#ifdef __cplusplus -} -#endif - -#endif // BVH_STANDALONE_LIBRARY_H_INCLUDED diff --git a/src/python/mnet4/BVH/bvhLibrary.py b/src/python/mnet4/BVH/bvhLibrary.py deleted file mode 100644 index f79a048..0000000 --- a/src/python/mnet4/BVH/bvhLibrary.py +++ /dev/null @@ -1,238 +0,0 @@ -#!/usr/bin/python3 - -import ctypes -import os -import sys -from ctypes import * -from os.path import exists - - -#-------------------------------------------------------- -def readCSV(filename): - result=dict() - import csv - with open(filename,newline='') as csvfile: - readerIn = csv.reader(csvfile,delimiter=',',quotechar='"') - for rowIn in readerIn: - numberOfColumns=len(rowIn) - labels = list(rowIn[i] for i in range(0,numberOfColumns) ) - if (labels[0]!=''): - newList = list() - newList.append(labels[1]) - newList.append(labels[2]) - result[labels[0]]=newList - return result -#-------------------------------------------------------- -def gatherAllBVHFiles(directoryPath): - results = list() - for f in os.scandir(directoryPath): - if f.is_file(): - #print("f.path=",f.path) - if (f.path.find(".bvh")!=-1): - #print("Adding BVH file ",f.path) - results.append(f.path) - return results; -#-------------------------------------------------------- -def gatherAllBVHDirectories(directoryPath): - results = list() - for f in os.scandir(directoryPath): - if f.is_dir(): - #print("Adding files in ",f.path) - results = results + gatherAllBVHFiles(f.path) - return results -#-------------------------------------------------------- -def writeListToFile(theList,theFilename): - file = open(theFilename, "w") - for theList in allBVHFiles: - file.write(theList + "\n") - file.close() -#-------------------------------------------------------- -def appendJSONEnding(theFilename): - file = open(theFilename, "a") # append mode - file.write(" ]\n } \n") - file.close() -#-------------------------------------------------------- - - - -def loadLibrary(filename): - if not exists(filename): - print("Could not find BVH Library (",filename,"), compiling a fresh one..!") - print("Current directory was (",os.getcwd(),") ") - directory=os.path.dirname(os.path.abspath(filename)) - os.system(directory+"/makeLibrary.sh") - if not exists(filename): - print("Could not make BVH Library, terminating") - sys.exit(0) - libBVH = CDLL(filename) - #call C function to check connection - libBVH.connect() - libBVH.bvhConverter.restype = c_int - libBVH.bvhConverter.argtypes = c_int,POINTER(c_char_p) - return libBVH -#-------------------------------------------------------- -def bvhConvert(libBVH,arguments): - argumentBytes = [] - for i in range(len(arguments)): - argumentBytes.append(bytes(arguments[i], 'utf-8')) - argv = (ctypes.c_char_p * len(argumentBytes))() - argv[:] = argumentBytes - argc=len(argumentBytes) - libBVH.bvhConverter(argc,argv) -#-------------------------------------------------------- - - - - - - - - - - - -if __name__== "__main__": - #python main : - pythonFlags=list() - #Add any arguments given in the python script directly! - if (len(sys.argv)>1): - print('Supplied argument List:', str(sys.argv)) - for i in range(0, len(sys.argv)): - pythonFlags.append(sys.argv[i]) - if (sys.argv[i]=="--update"): - print('Deleting previous libBVHConverter.so to force update!\n') - os.system("rm libBVHConverter.so") - - - - - libBVH = loadLibrary("./libBVHConverter.so") - - - - datasetDirectory = "/home/ammar/Documents/Programming/DNNTracker/DNNTracker/dataset/MotionCapture" - outputDirectory = os.getcwd() - allBVHFiles = gatherAllBVHDirectories(datasetDirectory) - #Keep list for debug.. - writeListToFile(allBVHFiles,"listOfBVHFiles.txt") - print("Output Directory = ",outputDirectory) - print("Found ",len(allBVHFiles)," BVH files") - - - #Package Annotations as JSON - print("Packaging annotations in JSON format") - annotations=readCSV(datasetDirectory+"/cmu-mocap-annotations.csv") - import json - with open("annotations.json", "w") as outfile: - json.dump(annotations, outfile, indent=4) - - - mode="json" # json or csv - extension="."+mode - bodyPart="upperbody" - os.system("rm 2d_"+bodyPart+extension) - os.system("rm 3d_"+bodyPart+extension) - os.system("rm bvh_"+bodyPart+extension) - - - minRotationLimit="-45" - maxRotationLimit="45" - minORIENTATION="-55" #-45 default , -55 with 10 deg safety - maxORIENTATION="55" # 45 default , 55 with 10 deg safety - minDepth="900" #1000 original - maxDepth="4500" #3000 is too small - - #HIDEBODY="--hide2DLocationOfJoints 0 8 abdomen chest eye.r eye.l toe1-2.r toe5-3.r toe1-2.l toe5-3.l" #We want to contain these joints in the BVH file and solve them, but they do not - #SELECTBODY="--selectJoints 1 23 hip eye.r eye.l abdomen chest neck head rshoulder relbow rhand lshoulder lelbow lhand rhip rknee rfoot lhip lknee lfoot toe1-2.r toe5-3.r toe1-2.l toe5-3.l $HIDEBODY" - #--randomize2D $MIN_DEPTH $MAX_DEPTH $MIN_LIM $FRONT_MIN_ORIENTATION $MIN_LIM $MAX_LIM $FRONT_MAX_ORIENTATION $MAX_LIM" "$SELECTLOWERBODY $RAND_LOWER_BODY - - #$MIN_DEPTH $MAX_DEPTH $MIN_LIM -179.999999 $MIN_LIM $MAX_LIM 180 $MAX_LIM - - numberOfFilesProcessed = 0 - for bvhFile in allBVHFiles: - args=list() - args.append("--printparams") - args.append("--haltonerror") - args.append("--from") - args.append(bvhFile) - #RAND_UPPER_BODY="--perturbJointAngles 2 30.0 rshoulder lshoulder --perturbJointAngles 2 16.0 relbow lelbow --perturbJointAngles 2 10.0 abdomen chest" - #RAND_LOWER_BODY="--perturbJointAngles 2 30.0 rhip lhip --perturbJointAngles 4 10.0 lknee rknee lfoot rfoot --perturbJointAngles 2 10.0 abdomen chest" - args.append("--filtergimballocks"); args.append("4") - #startingFlags.append("--repeat") ; startingFlags.append("1") - - #Hip Position/Rotation Randomization - args.append("--randomize2D") - args.append(minDepth) #Minimum distance from the camera - args.append(maxDepth) #Maximum distance from the camera - args.append(minRotationLimit) # - args.append("-179.99") - args.append(minRotationLimit) - args.append(maxRotationLimit) - args.append("179.99") - args.append(maxRotationLimit) - #------------------------------------- - - - #Upper body joints - #------------------------------------- - args.append("--selectJoints") - args.append("1") - args.append("23") #Number of joints to select - args.append("hip") - args.append("eye.r") - args.append("eye.l") - args.append("abdomen") - args.append("chest") - args.append("neck") - args.append("head") - args.append("rshoulder") - args.append("relbow") - args.append("rhand") - args.append("lshoulder") - args.append("lelbow") - args.append("lhand") - args.append("rhip") - args.append("rknee") - args.append("rfoot") - args.append("lhip") - args.append("lknee") - args.append("lfoot") - args.append("toe1-2.r") - args.append("toe5-3.r") - args.append("toe1-2.l") - args.append("toe5-3.l") - #------------------------------------- - - #Deactivate spare joints - #------------------------------------- - args.append("--hide2DLocationOfJoints") - args.append("0") - args.append("8") #Number of joints to hide - args.append("abdomen") - args.append("chest") - args.append("eye.r") - args.append("eye.l") - args.append("toe1-2.r") - args.append("toe5-3.r") - args.append("toe1-2.l") - args.append("toe5-3.l") - #------------------------------------- - - - args.append("--occlusions") - args.append("--"+mode) - args.append(outputDirectory) - args.append(bodyPart+extension) - args.append("2d+bvh") - args = args + pythonFlags - bvhConvert(libBVH,args) - numberOfFilesProcessed = numberOfFilesProcessed + 1 - if (numberOfFilesProcessed==2): - print("Stopping now at ",numberOfFilesProcessed," limit") - break - - appendJSONEnding("2d_"+bodyPart+extension) - appendJSONEnding("3d_"+bodyPart+extension) - appendJSONEnding("bvh_"+bodyPart+extension) - - #./GroundTruthDumper $VIEW_COMMANDS --haltonerror --from $BVHFILE --filtergimballocks 4 $3 --repeat $ITERATIONS $2 --occlusions --csv $outputDir $1 2d+bvh # --bvh $outputDir/$f-random.bvh diff --git a/src/python/mnet4/BVH/calibration.py b/src/python/mnet4/BVH/calibration.py deleted file mode 100644 index 9abe449..0000000 --- a/src/python/mnet4/BVH/calibration.py +++ /dev/null @@ -1,139 +0,0 @@ -#This mimics the calibration files like ; -# https://github.com/AmmarkoV/RGBDAcquisition/blob/master/tools/Calibration/calibration.c - -#This mimics the calibration files like ; -# https://github.com/AmmarkoV/RGBDAcquisition/blob/master/tools/Calibration/calibration.c -def readCalibrationFromFile(filename): - calib = dict() - if filename is None: - return calib - - fp = None - try: - fp = open(filename, "r") - except IOError: - return calib - - # Our state - # ---------------------------- - i = 0 - category = 0 - line_length = 0 - lines_at_current_category = 0 - # ---------------------------- - - - for line in fp: - #-------------------------------------- - line = line.rstrip("\r\n") - line_length = len(line) - #-------------------------------------- - if line_length > 0: - if line[line_length - 1] == '\n': - line = line[:-1] - if line[line_length - 1] == '\r': - line = line[:-1] - #-------------------------------------- - if line_length > 1: - if line[line_length - 2] == '\n': - line = line[:-2] - if line[line_length - 2] == '\r': - line = line[:-2] - #-------------------------------------- - if line[0] == '%': - lines_at_current_category = 0 - #-------------------------------------- - # ---------------------------- ---------------------------- ---------------------------- - if line == "%I": - category = 1 - calib["intrinsic"] = list() - elif line == "%D": - category = 2 - elif line == "%T": - category = 3 - calib["extrinsicTranslation"] = list() - elif line == "%R": - category = 4 - calib["extrinsicRotationRodriguez"] = list() - elif line == "%NF": - category = 5 - elif line == "%UNIT": - category = 6 - elif line == "%RT4*4": - category = 7 - calib["extrinsic"] = list() - elif line == "%Width": - category = 8 - elif line == "%Height": - category = 9 - else: - # ---------------------------- ---------------------------- ---------------------------- - if category == 1: - calib["intrinsicParametersSet"] = 1 - lines_at_current_category = min(lines_at_current_category, 9) - calib["intrinsic"].append(float(line)) - lines_at_current_category += 1 - if (lines_at_current_category==9): - category = 0 - elif category == 2: - if lines_at_current_category == 0: - calib["k1"] = float(line) - elif lines_at_current_category == 1: - calib["k2"] = float(line) - elif lines_at_current_category == 2: - calib["p1"] = float(line) - elif lines_at_current_category == 3: - calib["p2"] = float(line) - elif lines_at_current_category == 4: - calib["k3"] = float(line) - lines_at_current_category += 1 - if (lines_at_current_category==4): - category = 0 - elif category == 3: - calib["extrinsicParametersSet"] = 1 - lines_at_current_category = min(lines_at_current_category, 3) - calib["extrinsicTranslation"].append(float(line)) - lines_at_current_category += 1 - if (lines_at_current_category==3): - category = 0 - elif category == 4: - lines_at_current_category = min(lines_at_current_category, 3) - calib["extrinsicRotationRodriguez"].append(float(line)) - lines_at_current_category += 1 - if (lines_at_current_category==3): - category = 0 - elif category == 5: - calib["nearPlane"] = float(line) - category = 0 - elif category == 6: - calib["farPlane"] = float(line) - category = 0 - elif category == 7: - lines_at_current_category = min(lines_at_current_category, 16) - calib["extrinsic"].append(float(line)) - lines_at_current_category += 1 - category = 0 - elif category == 8: - calib["width"] = int(line) - category = 0 - elif category == 9: - calib["height"] = int(line) - category = 0 - # ---------------------------- ---------------------------- ---------------------------- - - fp.close() - - try: - calib["fX"] = calib["intrinsic"][0] - calib["fY"] = calib["intrinsic"][4] - calib["cX"] = calib["intrinsic"][2] - calib["cY"] = calib["intrinsic"][5] - except: - print("No intrinsic matrix declared in ", filename) - print("Cannot populate fX, fY, cX, cY") - - - print("New calibration loaded : ",calib) - - return calib - diff --git a/src/python/mnet4/BVH/gatherFiles.sh b/src/python/mnet4/BVH/gatherFiles.sh deleted file mode 100755 index 1c10583..0000000 --- a/src/python/mnet4/BVH/gatherFiles.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/bin/bash - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -cd "$DIR" - -REPO="/home/ammar/Documents/Programming/RGBDAcquisition" - - - -ITEM="tools/AmMatrix" -cd "$DIR" -mkdir -p $ITEM -cd $ITEM -cp $REPO/$ITEM/matrixTools* ./ -cp $REPO/$ITEM/matrix3x3Tools* ./ -cp $REPO/$ITEM/matrix4x4Tools* ./ -cp $REPO/$ITEM/matrixCalculations* ./ -cp $REPO/$ITEM/matrixOpenGL* ./ -cp $REPO/$ITEM/quaternions* ./ -cp $REPO/$ITEM/simpleRenderer* ./ -cp $REPO/$ITEM/solveLinearSystemGJ* ./ -cp $REPO/$ITEM/solveHomography* ./ - -ITEM="tools/PThreadWorkerPool" -cd "$DIR" -mkdir -p $ITEM -cd $ITEM -cd .. -cp -R $REPO/$ITEM/ ./ - - -ITEM="opengl_acquisition_shared_library/opengl_depth_and_color_renderer/src/Applications/BVHTester" -cd "$DIR" -mkdir -p $ITEM -cd $ITEM -cp $REPO/$ITEM/bvhConverter.py ./ -cp $REPO/$ITEM/bvhLibrary.py ./ -cp $REPO/$ITEM/bvhLibrary.h ./ -cp $REPO/$ITEM/main.c ./ -cd "$DIR" -cp $REPO/$ITEM/bvhLibrary.py ./ - -#Also copy the two most important files in root -cd "$DIR" -cp $REPO/$ITEM/bvhConverter.py ./ -cp $REPO/$ITEM/bvhLibrary.py ./ - -ITEM="opengl_acquisition_shared_library/opengl_depth_and_color_renderer/src/Library/MotionCaptureLoader" -cd "$DIR" -mkdir -p $ITEM -cd $ITEM -cd .. -cp -R $REPO/$ITEM/ ./ - -ITEM="opengl_acquisition_shared_library/opengl_depth_and_color_renderer/src/Library/ModelLoader" -cd "$DIR" -mkdir -p $ITEM -cd $ITEM -cd .. -cp -R $REPO/$ITEM/ ./ - -ITEM="opengl_acquisition_shared_library/opengl_depth_and_color_renderer/src/Library/TrajectoryParser" -cd "$DIR" -mkdir -p $ITEM -cd $ITEM -cp $REPO/$ITEM/InputParser_C.* ./ -cp $REPO/$ITEM/TrajectoryParserDataStructures.* ./ -cp $REPO/$ITEM/TrajectoryParser* ./ -cp $REPO/$ITEM/hashmap* ./ - - -exit 0 diff --git a/src/python/mnet4/BVH/headerWithHeadAndOneMotion.bvh b/src/python/mnet4/BVH/headerWithHeadAndOneMotion.bvh deleted file mode 100644 index 5009ccf..0000000 --- a/src/python/mnet4/BVH/headerWithHeadAndOneMotion.bvh +++ /dev/null @@ -1,1022 +0,0 @@ -HIERARCHY -ROOT hip -{ - OFFSET 0 0 0 - CHANNELS 6 Xposition Yposition Zposition Zrotation Yrotation Xrotation - JOINT abdomen - { - OFFSET 0 20.6881 -0.73152 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT chest - { - OFFSET 0 11.7043 -0.48768 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT neck - { - OFFSET 0 22.1894 -2.19456 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT neck1 - { - OFFSET 0.000000 5.364170 1.574630 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT head - { - OFFSET 0.000000 5.364141 1.574630 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT __jaw - { - OFFSET 0.000000 13.604700 -0.502080 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT jaw - { - OFFSET 0.000000 -13.499860 2.500710 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT special04 - { - OFFSET -0.000000 -6.835370 4.375500 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT oris02 - { - OFFSET 0.000000 1.711150 2.820850 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT oris01 - { - OFFSET -0.000000 0.972390 0.845650 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET 0.000000 1.162291 0.607091 - } - } - } - JOINT oris06.l - { - OFFSET 0.000000 1.711150 2.820850 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT oris07.l - { - OFFSET 1.168850 0.445180 0.506110 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET 0.450611 1.195178 0.204519 - } - } - } - JOINT oris06.r - { - OFFSET 0.000000 1.711150 2.820850 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT oris07.r - { - OFFSET -1.168850 0.445180 0.506110 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET -0.450611 1.195173 0.204519 - } - } - } - } - JOINT tongue00 - { - OFFSET -0.000000 -6.835370 4.375500 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT tongue01 - { - OFFSET 0.000000 3.973650 -3.762340 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT tongue02 - { - OFFSET 0.000000 0.429760 2.924710 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT tongue03 - { - OFFSET 0.000000 0.018530 2.059010 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT __tongue04 - { - OFFSET 0.000000 -0.440240 0.838860 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT tongue04 - { - OFFSET 0.000000 0.000000 0.000000 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET 0.000000 -0.440230 0.838860 - } - } - } - JOINT tongue07.l - { - OFFSET 0.000000 -0.440240 0.838860 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET 1.160923 -0.331531 0.018227 - } - } - JOINT tongue07.r - { - OFFSET 0.000000 -0.440240 0.838860 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET -1.160922 -0.331531 0.018227 - } - } - } - JOINT tongue06.l - { - OFFSET 0.000000 0.018530 2.059010 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET 1.644752 -0.526075 -0.203281 - } - } - JOINT tongue06.r - { - OFFSET 0.000000 0.018530 2.059010 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET -1.644752 -0.526075 -0.203282 - } - } - } - JOINT tongue05.l - { - OFFSET 0.000000 0.429760 2.924710 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET 1.971028 -0.388618 0.239206 - } - } - JOINT tongue05.r - { - OFFSET 0.000000 0.429760 2.924710 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET -1.971028 -0.388618 0.239205 - } - } - } - } - } - } - JOINT __levator02.l - { - OFFSET 0.000000 13.604700 -0.502080 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT levator02.l - { - OFFSET 0.313580 -11.321120 11.599360 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT levator03.l - { - OFFSET 1.681690 -1.563730 -1.357570 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT levator04.l - { - OFFSET 0.504730 -1.676760 -0.058160 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT levator05.l - { - OFFSET 0.145440 -1.643170 -0.225470 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET -0.310116 -0.760198 -0.121474 - } - } - } - } - } - } - JOINT __levator02.r - { - OFFSET 0.000000 13.604700 -0.502080 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT levator02.r - { - OFFSET -0.313580 -11.321120 11.599360 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT levator03.r - { - OFFSET -1.681690 -1.563740 -1.357570 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT levator04.r - { - OFFSET -0.504730 -1.676750 -0.058160 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT levator05.r - { - OFFSET -0.145440 -1.643170 -0.225470 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET 0.310116 -0.760193 -0.121474 - } - } - } - } - } - } - JOINT __special01 - { - OFFSET 0.000000 13.604700 -0.502080 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT special01 - { - OFFSET -0.000000 -14.026930 -5.716970 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT oris04.l - { - OFFSET -0.000000 -0.492640 17.312620 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT oris03.l - { - OFFSET 1.215520 -0.627430 -0.393050 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET 0.288776 -0.560899 -0.149645 - } - } - } - JOINT oris04.r - { - OFFSET -0.000000 -0.492640 17.312620 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT oris03.r - { - OFFSET -1.215520 -0.627440 -0.393050 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET -0.288776 -0.560894 -0.149645 - } - } - } - JOINT oris06 - { - OFFSET -0.000000 -0.492640 17.312620 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT oris05 - { - OFFSET -0.000000 -0.486000 0.000950 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET 0.000000 -0.630493 0.197635 - } - } - } - } - } - JOINT __special03 - { - OFFSET 0.000000 13.604700 -0.502080 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT special03 - { - OFFSET 0.000000 -13.499860 2.500710 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT __levator06.l - { - OFFSET -0.000000 1.035800 10.090229 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT levator06.l - { - OFFSET 0.522240 -0.615720 0.045900 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET 1.107505 -0.131898 -2.142227 - } - } - } - JOINT __levator06.r - { - OFFSET -0.000000 1.035800 10.090229 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT levator06.r - { - OFFSET -0.522240 -0.615730 0.045900 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET -1.107506 -0.131893 -2.142227 - } - } - } - } - } - JOINT special06.l - { - OFFSET 0.000000 13.604700 -0.502080 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT special05.l - { - OFFSET 2.108890 0.153870 5.595070 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT eye.l - { - OFFSET 0.857170 -10.254801 2.414670 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET 0.111609 0.006490 4.051424 - } - } - JOINT orbicularis03.l - { - OFFSET 0.857170 -10.254801 2.414670 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET 0.097789 1.146431 3.788029 - } - } - JOINT orbicularis04.l - { - OFFSET 0.857170 -10.254801 2.414670 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET 0.113609 -1.130505 3.863064 - } - } - } - } - JOINT special06.r - { - OFFSET 0.000000 13.604700 -0.502080 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT special05.r - { - OFFSET -2.108890 0.153870 5.595070 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT eye.r - { - OFFSET -0.857170 -10.254801 2.414670 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET -0.111609 0.006490 4.051424 - } - } - JOINT orbicularis03.r - { - OFFSET -0.857170 -10.254801 2.414670 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET -0.097789 1.146431 3.788029 - } - } - JOINT orbicularis04.r - { - OFFSET -0.857170 -10.254801 2.414670 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET -0.113609 -1.130505 3.863064 - } - } - } - } - JOINT __temporalis01.l - { - OFFSET 0.000000 13.604700 -0.502080 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT temporalis01.l - { - OFFSET 6.332510 -9.444281 6.595120 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT oculi02.l - { - OFFSET -0.804920 0.053010 1.621140 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT oculi01.l - { - OFFSET -2.161570 1.690970 2.142660 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET -1.740622 0.281157 0.647323 - } - } - } - } - } - JOINT __temporalis01.r - { - OFFSET 0.000000 13.604700 -0.502080 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT temporalis01.r - { - OFFSET -6.332510 -9.444281 6.595120 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT oculi02.r - { - OFFSET 0.804920 0.053010 1.621140 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT oculi01.r - { - OFFSET 2.161570 1.690970 2.142660 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET 1.740622 0.281157 0.647323 - } - } - } - } - } - JOINT __temporalis02.l - { - OFFSET 0.000000 13.604700 -0.502080 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT temporalis02.l - { - OFFSET 6.377600 -11.680510 6.235180 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT risorius02.l - { - OFFSET -0.814250 0.451130 1.721730 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT risorius03.l - { - OFFSET -0.649710 -2.514660 0.612550 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET 0.479556 -1.760402 -1.642659 - } - } - } - } - } - JOINT __temporalis02.r - { - OFFSET 0.000000 13.604700 -0.502080 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT temporalis02.r - { - OFFSET -6.377600 -11.680510 6.235180 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT risorius02.r - { - OFFSET 0.814250 0.451130 1.721730 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT risorius03.r - { - OFFSET 0.649710 -2.514660 0.612550 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET -0.479556 -1.760402 -1.642659 - } - } - } - } - } - } - } - } - JOINT rCollar - { - OFFSET -2.68224 19.2634 -4.8768 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT rShldr - { - OFFSET -8.77824 -1.95073 1.46304 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT rForeArm - { - OFFSET -28.1742 -1.7115 0.48768 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT rHand - { - - OFFSET -21.049400 0.002190 -0.634230 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT metacarpal1.r - { - OFFSET -2.815680 -0.279180 0.531660 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger2-1.r - { - OFFSET -6.292930 0.272380 2.520090 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger2-2.r - { - OFFSET -2.310530 -0.320530 -0.060510 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger2-3.r - { - OFFSET -2.051030 -0.295400 -0.164890 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET -2.376838 -0.681367 -0.183877 - } - } - } - } - } - JOINT metacarpal2.r - { - OFFSET -2.815680 -0.279180 0.531660 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger3-1.r - { - OFFSET -6.313640 0.626130 0.318530 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger3-2.r - { - OFFSET -3.015730 -0.589480 -0.088540 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger3-3.r - { - OFFSET -2.482120 -0.426280 0.076670 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET -2.344174 -0.731969 0.003260 - } - } - } - } - } - JOINT __metacarpal3.r - { - OFFSET -2.815680 -0.279180 0.531660 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT metacarpal3.r - { - OFFSET -0.606080 -0.162120 -1.874870 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger4-1.r - { - OFFSET -5.355730 0.702040 0.402510 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger4-2.r - { - OFFSET -2.643900 -0.485530 -0.117520 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger4-3.r - { - OFFSET -2.215850 -0.353160 0.066220 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET -2.350273 -0.621223 -0.046375 - } - } - } - } - } - } - JOINT __metacarpal4.r - { - OFFSET -2.815680 -0.279180 0.531660 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT metacarpal4.r - { - OFFSET -0.606080 -0.162120 -1.874870 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger5-1.r - { - OFFSET -4.761700 0.175470 -1.109590 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger5-2.r - { - OFFSET -1.916360 -0.173360 -0.146170 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger5-3.r - { - OFFSET -1.411290 -0.108670 -0.020110 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET -1.799226 -0.102363 -0.078601 - } - } - } - } - } - } - JOINT __rthumb - { - OFFSET -2.815680 -0.279180 0.531660 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT rthumb - { - OFFSET -0.283040 -0.142720 1.950690 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger1-2.r - { - OFFSET -0.915590 -2.152150 1.546760 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger1-3.r - { - OFFSET -3.213140 -0.470060 0.247480 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET -2.521224 -0.161543 -0.511272 - } - } - } - } - } - } - } - } - } - JOINT lCollar - { - OFFSET 2.68224 19.2634 -4.8768 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT lShldr - { - OFFSET 8.77824 -1.95073 1.46304 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT lForeArm - { - OFFSET 28.1742 -1.7115 0.48768 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT lHand - { - - OFFSET 21.049408 0.002200 -0.634230 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT metacarpal1.l - { - OFFSET 2.815670 -0.279180 0.531660 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger2-1.l - { - OFFSET 6.292930 0.272390 2.520090 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger2-2.l - { - OFFSET 2.310530 -0.320520 -0.060510 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger2-3.l - { - OFFSET 2.051030 -0.295400 -0.164880 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET 2.376823 -0.681367 -0.183876 - } - } - } - } - } - JOINT metacarpal2.l - { - OFFSET 2.815670 -0.279180 0.531660 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger3-1.l - { - OFFSET 6.313640 0.626120 0.318530 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger3-2.l - { - OFFSET 3.015730 -0.589470 -0.088540 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger3-3.l - { - OFFSET 2.482120 -0.426270 0.076670 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET 2.344170 -0.731978 0.003260 - } - } - } - } - } - JOINT __metacarpal3.l - { - OFFSET 2.815670 -0.279180 0.531660 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT metacarpal3.l - { - OFFSET 0.606080 -0.162120 -1.874870 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger4-1.l - { - OFFSET 5.355730 0.702050 0.402510 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger4-2.l - { - OFFSET 2.643900 -0.485530 -0.117510 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger4-3.l - { - OFFSET 2.215840 -0.353150 0.066210 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET 2.350273 -0.621228 -0.046377 - } - } - } - } - } - } - JOINT __metacarpal4.l - { - OFFSET 2.815670 -0.279180 0.531660 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT metacarpal4.l - { - OFFSET 0.606080 -0.162120 -1.874870 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger5-1.l - { - OFFSET 4.761700 0.175480 -1.109600 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger5-2.l - { - OFFSET 1.916350 -0.173360 -0.146170 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger5-3.l - { - OFFSET 1.411290 -0.108670 -0.020110 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET 1.799216 -0.102372 -0.078600 - } - } - } - } - } - } - JOINT __lthumb - { - OFFSET 2.815670 -0.279180 0.531660 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT lthumb - { - OFFSET 0.283040 -0.142710 1.950690 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger1-2.l - { - OFFSET 0.915930 -2.151960 1.546820 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT finger1-3.l - { - OFFSET 3.213210 -0.469680 0.247300 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET 2.521210 -0.161290 -0.511422 - } - } - } - } - } - } - } - } - } - } - } - JOINT rButtock - { - OFFSET -8.77824 4.35084 1.2192 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT rThigh - { - OFFSET 0 -1.70687 -2.19456 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT rShin - { - OFFSET 0 -36.8199 0.73152 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT rFoot - { - - OFFSET 0.73152 -45.1104 -5.12064 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT toe1-1.R - { - OFFSET 2.454000 -4.050002 13.194999 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT toe1-2.R - { - OFFSET -0.214000 -0.646000 2.427000 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET -0.401900 -0.827789 2.725930 - } - } - } - JOINT toe2-1.R - { - OFFSET 0.177000 -4.299998 13.329000 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT toe2-2.R - { - OFFSET -0.177000 -0.323000 2.039000 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT toe2-3.R - { - OFFSET -0.067000 -0.440998 1.248000 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET -0.042990 -0.647306 1.660872 - } - } - } - } - JOINT toe3-1.R - { - OFFSET -1.396000 -4.461999 13.078999 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT toe3-2.R - { - OFFSET -0.161000 -0.247002 1.809000 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT toe3-3.R - { - OFFSET -0.033000 -0.441999 1.202000 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET 0.032040 -0.433550 1.271800 - } - } - } - } - JOINT toe4-1.R - { - OFFSET -2.888001 -4.480000 12.376999 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT toe4-2.R - { - OFFSET -0.160000 -0.331998 1.491001 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT toe4-3.R - { - OFFSET 0.035999 -0.251002 1.138999 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET -0.088911 -0.568814 0.969530 - } - } - } - } - JOINT toe5-1.R - { - OFFSET -4.257999 -4.467001 11.711999 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT toe5-2.R - { - OFFSET -0.046000 -0.265999 0.982000 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT toe5-3.R - { - OFFSET 0.086999 -0.372000 0.791000 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET -0.044329 -0.555482 1.085780 - } - } - } - } - } - } - } - } - JOINT lButtock - { - OFFSET 8.77824 4.35084 1.2192 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT lThigh - { - OFFSET 0 -1.70687 -2.19456 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT lShin - { - OFFSET 0 -36.8199 0.73152 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT lFoot - { - - OFFSET -0.73152 -45.1104 -5.12064 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT toe1-1.L - { - OFFSET -2.454000 -4.050002 13.194999 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT toe1-2.L - { - OFFSET 0.214000 -0.646000 2.427000 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET 0.401900 -0.827789 2.725930 - } - } - } - JOINT toe2-1.L - { - OFFSET -0.177000 -4.299998 13.329000 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT toe2-2.L - { - OFFSET 0.177000 -0.323000 2.039000 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT toe2-3.L - { - OFFSET 0.067000 -0.440998 1.248000 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET 0.042990 -0.647306 1.660872 - } - } - } - } - JOINT toe3-1.L - { - OFFSET 1.396000 -4.461999 13.078999 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT toe3-2.L - { - OFFSET 0.161000 -0.247002 1.809000 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT toe3-3.L - { - OFFSET 0.033000 -0.441999 1.202000 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET -0.032040 -0.433550 1.271800 - } - } - } - } - JOINT toe4-1.L - { - OFFSET 2.888001 -4.480000 12.376999 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT toe4-2.L - { - OFFSET 0.160000 -0.331998 1.491001 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT toe4-3.L - { - OFFSET -0.035999 -0.251002 1.138999 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET 0.088911 -0.568814 0.969530 - } - } - } - } - JOINT toe5-1.L - { - OFFSET 4.257999 -4.467001 11.711999 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT toe5-2.L - { - OFFSET 0.046000 -0.265999 0.982000 - CHANNELS 3 Zrotation Xrotation Yrotation - JOINT toe5-3.L - { - OFFSET -0.086999 -0.372000 0.791000 - CHANNELS 3 Zrotation Xrotation Yrotation - End Site - { - OFFSET 0.044329 -0.555482 1.085780 - } - } - } - } - } - } - } - } -} -MOTION -Frames: 1 -Frame Time: 0.04 -0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 diff --git a/src/python/mnet4/BVH/libBVHConverter.so b/src/python/mnet4/BVH/libBVHConverter.so deleted file mode 100755 index 737442edba7fa8bcf7ba4fa5f731daa67b08fe06..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 438208 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�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�P-b3J0(#_h?QA4CCD>LxZ|-ed&WHX(Yxe& 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

gVzhVy56nG6!D_YVWI+&T~S_QmCMi5N; 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�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

bq&*Q!2UmsxNYF@Das$*{%81svC|`N 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 diff --git a/src/python/mnet4/BVH/main.c b/src/python/mnet4/BVH/main.c deleted file mode 100644 index 1b794cc..0000000 --- a/src/python/mnet4/BVH/main.c +++ /dev/null @@ -1,1373 +0,0 @@ -/** @file main.c - * @brief A library that can parse BVH files and perform various processing options as a commandline tool - * X86 compilation: gcc -o -L/usr/X11/lib main main.c - * X64 compilation: gcc -o -L/usr/X11/lib64 main main.c - * @author Ammar Qammaz (AmmarkoV) - */ - -#include -#include -#include -#include -#include - -#include "../../Library/TrajectoryParser/TrajectoryParserDataStructures.h" -#include "../../Library/MotionCaptureLoader/bvh_loader.h" -#include "../../Library/MotionCaptureLoader/calculate/bvh_to_tri_pose.h" -#include "../../Library/MotionCaptureLoader/calculate/smoothing.h" - -#include "../../Library/MotionCaptureLoader/export/bvh_to_trajectoryParserTRI.h" -#include "../../Library/MotionCaptureLoader/export/bvh_to_trajectoryParserPrimitives.h" -#include "../../Library/MotionCaptureLoader/export/bvh_export.h" -#include "../../Library/MotionCaptureLoader/export/bvh_to_bvh.h" -#include "../../Library/MotionCaptureLoader/export/bvh_to_csv.h" -#include "../../Library/MotionCaptureLoader/export/bvh_to_c.h" - -#include "../../Library/MotionCaptureLoader/edit/bvh_cut_paste.h" -#include "../../Library/MotionCaptureLoader/edit/bvh_randomize.h" -#include "../../Library/MotionCaptureLoader/edit/bvh_filter.h" -#include "../../Library/MotionCaptureLoader/edit/bvh_rename.h" -#include "../../Library/MotionCaptureLoader/edit/bvh_merge.h" -#include "../../Library/MotionCaptureLoader/edit/bvh_remapangles.h" -#include "../../Library/MotionCaptureLoader/edit/bvh_interpolate.h" - -#include "../../Library/MotionCaptureLoader/ik/bvh_inverseKinematics.h" -#include "../../Library/MotionCaptureLoader/ik/hardcodedProblems_inverseKinematics.h" - -#include "../../Library/MotionCaptureLoader/metrics/bvh_measure.h" -#include "../../Library/MotionCaptureLoader/tests/test.h" - -#include "../../../../../tools/AmMatrix/matrix4x4Tools.h" -#include "../../../../../tools/AmMatrix/matrixOpenGL.h" - - -#define NORMAL "\033[0m" -#define BLACK "\033[30m" /* Black */ -#define RED "\033[31m" /* Red */ -#define GREEN "\033[32m" /* Green */ -#define YELLOW "\033[33m" /* Yellow */ -#define BLUE "\033[34m" /* Blue */ -#define MAGENTA "\033[35m" /* Magenta */ -#define CYAN "\033[36m" /* Cyan */ -#define WHITE "\033[37m" /* White */ - -void haltOnError(unsigned int haltingSwitch,const char * message) -{ - fprintf(stderr,RED "=======================================\n"); - fprintf(stderr,"=======================================\n"); - fprintf(stderr,"Encountered error during procedure %s \n",message); - fprintf(stderr,"=======================================\n"); - fprintf(stderr,"=======================================\n" NORMAL); - - if (haltingSwitch) - { - fprintf(stderr,RED "Halting because of --haltonerror switch\n" NORMAL); - exit(1); - } -} - -void incorrectArguments() -{ - fprintf(stderr,RED "Incorrect number of arguments.. \n" NORMAL); - exit(1); -} - -//----------------------------------------------------------------- -//----------------------------------------------------------------- -//----------------------------------------------------------------- - -void prepare4x4Human36MRotationMatrix(struct Matrix4x4OfFloats * rotationMatrix,float rX,float rY,float rZ) -{ - if ( (rX==0.0) && (rY==0.0) && (rZ==0.0) ) - { - create4x4FIdentityMatrix(rotationMatrix); - return ; - } - - struct Matrix4x4OfFloats rXM={0}; - struct Matrix4x4OfFloats rYM={0}; - struct Matrix4x4OfFloats rZM={0}; - - //R1x=np.matrix([[1,0,0], [0,np.cos(Rx),-np.sin(Rx)], [0,np.sin(Rx),np.cos(Rx)] ]) #[1 0 0; 0 cos(obj.Params(1)) -sin(obj.Params(1)); 0 sin(obj.Params(1)) cos(obj.Params(1))] - rXM.m[0]=1.0; rXM.m[1]=0.0; rXM.m[2]=0.0; rXM.m[3]=0.0; - rXM.m[4]=0.0; rXM.m[5]=cos(rX); rXM.m[6]=-sin(rX); rXM.m[7]=0.0; - rXM.m[8]=0.0; rXM.m[9]=sin(rX); rXM.m[10]=cos(rX); rXM.m[11]=0.0; - rXM.m[12]=0.0; rXM.m[13]=0.0; rXM.m[14]=0.0; rXM.m[15]=1.0; - - //R1y=np.matrix([[np.cos(Ry),0,np.sin(Ry)], [0,1,0], [-np.sin(Ry),0,np.cos(Ry)]]) #[cos(obj.Params(2)) 0 sin(obj.Params(2)); 0 1 0; -sin(obj.Params(2)) 0 cos(obj.Params(2))] - rYM.m[0]=cos(rY); rYM.m[1]=0.0; rYM.m[2]=sin(rY); rYM.m[3]=0.0; - rYM.m[4]=0.0; rYM.m[5]=1.0; rYM.m[6]=0.0; rYM.m[7]=0.0; - rYM.m[8]=-sin(rY); rYM.m[9]=0.0; rYM.m[10]=cos(rY); rYM.m[11]=0.0; - rYM.m[12]=0.0; rYM.m[13]=0.0; rYM.m[14]=0.0; rYM.m[15]=1.0; - - //R1z=np.matrix([[np.cos(Rz),-np.sin(Rz),0], [np.sin(Rz),np.cos(Rz),0], [0,0,1]]) #[cos(obj.Params(3)) -sin(obj.Params(3)) 0; sin(obj.Params(3)) cos(obj.Params(3)) 0; 0 0 1] - rZM.m[0]=cos(rZ); rZM.m[1]=-sin(rZ); rZM.m[2]=0.0; rZM.m[3]=0.0; - rZM.m[4]=sin(rZ); rZM.m[5]=cos(rZ); rZM.m[6]=0.0; rZM.m[7]=0.0; - rZM.m[8]=0.0; rZM.m[9]=0.0; rZM.m[10]=1.0; rZM.m[11]=0.0; - rYM.m[12]=0.0; rZM.m[13]=0.0; rZM.m[14]=0.0; rZM.m[15]=1.0; - - multiplyThree4x4FMatrices( - rotationMatrix , - &rXM , - &rYM, - &rZM - ); - -} - - -int testMultipleLoad(const char * filename) -{ - struct BVH_MotionCapture bvhMotion={0}; - - FILE * fp = fopen(filename,"r"); - if (fp!=0) - { - char * line = NULL; - size_t len = 0; - ssize_t read; - - unsigned int fileNumber=0; - //unsigned int done=0; - // while ( (!done) && ( (read = getline(&line, &len, fp)) != -1) ) - while ( (read = getline(&line, &len, fp)) != -1) - { - if (line!=0) - { - int lineLength = strlen(line); - if (lineLength>=1) - { - if (line[lineLength-1]==10) { line[lineLength-1]=0; } - if (line[lineLength-1]==13) { line[lineLength-1]=0; } - } - if (lineLength>=2) - { - if (line[lineLength-2]==10) { line[lineLength-2]=0; } - if (line[lineLength-2]==13) { line[lineLength-2]=0; } - } - - fprintf(stderr,"Next file is `%s`\n",line); - if ( bvh_loadBVH(line, &bvhMotion, 1.0) ) - { - fprintf(stderr,"Loaded file `%s`\n",line); - //Change joint names.. - bvh_renameJointsForCompatibility(&bvhMotion); - fprintf(stderr,"Did rename `%s`\n",line); - bvh_free(&bvhMotion); - fprintf(stderr,"Freed file `%s`\n",line); - } - } - - ++fileNumber; - //if (fileNumber==10) { done=1; } - } - - if (line!=0) { free(line); line=0; } - fclose(fp); - return 1; - } - return 0; -} - - -void printCallingParameters(int argc,const char **argv) -{ - fprintf(stderr,"Utility was called using following parameters :\n"); - unsigned int z=0; - for (z=0; z=argc) { incorrectArguments(); } - sampleSkip=atoi(argv[i+1]); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--printparams")==0) - { - printCallingParameters(argc,argv); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--debug")==0) - { - bvhMotion.debug=1; - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--filtergimballocks")==0) - { - if (i+1>=argc) { incorrectArguments(); } - filterOutPosesThatAreGimbalLocked(&bvhMotion,atof(argv[i+1])); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--haltonerror")==0) - { - immediatelyHaltOnError=1; - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--occlusions")==0) - { - occlusions=1; - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--print")==0) - { - bvh_printBVH(&bvhMotion); - } else - if (strcmp(argv[i],"--printc")==0) - { - bvh_print_C_Header(&bvhMotion); - } else - if (strcmp(argv[i],"--printprofile")==0) - { - bvh_print_profile(&bvhMotion); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--extractmotionrangeforlistoffiles")==0) - { - if (i+1>=argc) { incorrectArguments(); } - extractMinimaMaximaFromBVHList(argv[i+1]); - - exit(0); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--testmultiple")==0) - { - if (i+1>=argc) { incorrectArguments(); } - testMultipleLoad(argv[i+1]); - exit(0); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--tuneIterations")==0) - { - // ./BVHTester --from Motions/05_01.bvh --selectJoints 0 23 hip eye.r eye.l abdomen chest neck head rshoulder relbow rhand lshoulder lelbow lhand rhip rknee rfoot lhip lknee lfoot toe1-2.r toe5-3.r toe1-2.l toe5-3.l --testIK 80 4 130 0.001 5 100 - - if (i+7>=argc) { incorrectArguments(); } - - unsigned int previousFrame=atoi(argv[i+1]); - unsigned int sourceFrame=atoi(argv[i+2]); - unsigned int targetFrame=atoi(argv[i+3]); - float learningRate = atof(argv[i+4]); - unsigned int iterations=atoi(argv[i+5]); - unsigned int epochs=atoi(argv[i+6]); - float spring = atof(argv[i+7]); - - bvhMeasureIterationInfluence( - &bvhMotion, - learningRate, - spring, - iterations, - epochs, - previousFrame, - sourceFrame, - targetFrame, - multiThreaded - ); - - exit(0); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--testIK")==0) - { - // ./BVHTester --from Motions/05_01.bvh --selectJoints 0 23 hip eye.r eye.l abdomen chest neck head rshoulder relbow rhand lshoulder lelbow lhand rhip rknee rfoot lhip lknee lfoot toe1-2.r toe5-3.r toe1-2.l toe5-3.l --testIK 4 80 130 1 0.1 15 100 0 0 1 1 1 - if (i+12>=argc) { - fprintf(stderr,"--testIK requires 12 arguments, previousFrame sourceFrame targetFrame stepFrame learningRate iterations epochs spring langevin verbosity.."); - fprintf(stderr,"got %u ",argc-i); - incorrectArguments(); - } - - unsigned int previousFrame=atoi(argv[i+1]); - unsigned int sourceFrame=atoi(argv[i+2]); - unsigned int targetFrame=atoi(argv[i+3]); - unsigned int stepFrame=atoi(argv[i+4]); - float learningRate = atof(argv[i+5]); - unsigned int iterations=atoi(argv[i+6]); - unsigned int epochs=atoi(argv[i+7]); - float spring = atof(argv[i+8]); - float langevin=atof(argv[i+9]); - char verbose = atoi(argv[i+10]); - float learningRateDecayRate = atof(argv[i+11]); - float momentum = atof(argv[i+12]); - - float maeSum = 0.0; - unsigned int maeSamples = 0; - unsigned long elapsedTime = 0; - - FILE * fp = fopen("report.html","w"); - if (fp!=0) - { - fprintf(fp,""); - - // - fprintf(fp,"File : %s
\n",bvhMotion.fileName); - fprintf(fp," %u frames / %u joints / %u motion values per frame
\n",bvhMotion.numberOfFrames,bvhMotion.jointHierarchySize,bvhMotion.numberOfValuesPerFrame); - fprintf(fp,"Previous Frame : %u
\n",previousFrame); - fprintf(fp,"Source Frame : %u
\n",sourceFrame); - fprintf(fp,"Target Frame : %u
\n",targetFrame); - fprintf(fp,"Step Frame : %u
\n",stepFrame); - fprintf(fp,"Learning Rate : %f
\n",learningRate); - fprintf(fp,"Langevin Dynamics : %f
\n",langevin); - fprintf(fp,"Iterations : %u
\n",iterations); - fprintf(fp,"Epochs : %u
\n",epochs); - fprintf(fp,"
\n"); - - - fprintf(fp,"\n\n\n"); - unsigned int step = 0; - while( - (sourceFrame+step\n", - sourceFrame+step,targetFrame+step, - mae, - sourceFrame+step,targetFrame+step - ); - //------------------------------------------------------------------------------------------------ - maeSum+=mae; - step+=stepFrame; - maeSamples+=1; - //------------------------------------------------------------------------------------------------ - } - - float maeAverage = 0.0; - unsigned long elapsedTimeAverage = 0; - if (maeSamples>0) - { - maeAverage = (float) maeSum/maeSamples; - elapsedTimeAverage = (unsigned long) elapsedTime/maeSamples; - } - - fprintf(fp,"
Source
Frame
Target
Frame
Mean Average
Error
Link
%u%u%0.2f mmOpen
"); - fprintf(fp,"

Total M.A.E. for %u samples : %0.2f mm
\n",maeSamples,maeAverage); - fprintf(fp,"Elapsed Time : %lu microseconds (%0.2f fps)
\n",(unsigned long) elapsedTime,convertStartEndTimeFromMicrosecondsToFPSIK(0,elapsedTimeAverage)); - fprintf(fp,""); - fclose(fp); - } - - //---------------------------------------------------- - //---------------------------------------------------- - fprintf(stdout,"MAE:%0.2f",(float) maeSum/maeSamples); - if (!fileExistsIK("report.csv")) - { - fp = fopen("report.csv","w"); - if (fp!=0) - { - fprintf(fp,"dataset,learningRate,lrdecay,previous,start,target,step,iterations,epochs,langevin,samples,mae,fps,momentum\n"); - fclose(fp); - } - } - //---------------------------------------------------- - //---------------------------------------------------- - if (fileExistsIK("report.csv")) - { - fp = fopen("report.csv","a"); - if (fp!=0) - { - fprintf( - fp,"%s,%f,%f,%u,%u,%u,%u,%u,%u,%f,%u,%f,%f,%f\n", - bvhMotion.fileName, - learningRate, - learningRateDecayRate, - previousFrame, - sourceFrame, - targetFrame, - stepFrame, - iterations, - epochs, - langevin, - maeSamples, - (float) maeSum/maeSamples, - convertStartEndTimeFromMicrosecondsToFPSIK(0,(unsigned long) elapsedTime/maeSamples), - momentum - ); - fclose(fp); - } - } - //---------------------------------------------------- - //---------------------------------------------------- - int r=0; //int r=system("xdg-open report.html"); - exit(r); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--probefilter")==0) - { - //Filter using 2D rules - //./BVHTester --from Motions/05_01.bvh --probefilter 0 0 -130.0 0 0 0 1920 1080 570.7 570.3 3 rhand lhip 10 12 rhand rhip 5 8 rhand lhand 20 25 - probeForFilterRules(&bvhMotion,argc-i-1,&argv[i+1]); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--filterout")==0) - { - //Filter using 2D rules - //./BVHTester --from Motions/05_01.bvh --filterout 0 0 -130.0 0 0 0 1920 1080 570.7 570.3 3 rhand lhip 140.0 145.0 rhand rhip 65 70 rhand lhand 190 205 - //./BVHTester --printparams --haltonerror --from Motions/05_01.bvh --selectJoints 1 13 hip eye.r eye.l abdomen chest neck head rshoulder relbow rhand lshoulder lelbow lhand --hide2DLocationOfJoints 0 4 abdomen chest eye.r eye.l --perturbJointAngles 4 38.0 rshoulder lshoulder rhip lhip --perturbJointAngles 8 10.0 rhand relbow lelbow lhand lknee rknee lfoot rfoot --repeat 0 --filterout 0 0 -130.0 0 90 0 1920 1080 570.7 570.3 6 rhand lhip 0 120 rhand rhip 0 120 rhand lhand 0 150 lhand rhip 0 120 lhand lhip 0 120 lhand rhand 0 150 --randomize2D 1000 4000 -35 -179.999999 -35 35 180 35 --occlusions --csv ./ upperbody_all.csv 2d+bvh - - if (!filterOutPosesThatAreCloseToRules(&bvhMotion,argc-i-1,&argv[i+1])) - { - haltOnError(immediatelyHaltOnError,"Error while filtering out joints.."); - } - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--nofilter")==0) - { - filterBehindCamera=0; - filterIfAnyJointOutsideof2DFrame=0; - filterTopWeirdRandomSkeletons=0; - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--renderingConfiguration")==0) - { - if (i+17>=argc) { incorrectArguments(); } - //----------------------------------------- - float rX=atof(argv[i+1]); - float rY=atof(argv[i+2]); - float rZ=atof(argv[i+3]); - renderingConfiguration.T[0]=atof(argv[i+4]); - renderingConfiguration.T[1]=atof(argv[i+5]); - renderingConfiguration.T[2]=atof(argv[i+6]); - renderingConfiguration.fX=atof(argv[i+7]); - renderingConfiguration.fY=atof(argv[i+8]); - renderingConfiguration.cX=atof(argv[i+9]); - renderingConfiguration.cY=atof(argv[i+10]); - renderingConfiguration.k1=atof(argv[i+11]); - renderingConfiguration.k2=atof(argv[i+12]); - renderingConfiguration.k3=atof(argv[i+13]); - renderingConfiguration.p1=atof(argv[i+14]); - renderingConfiguration.p2=atof(argv[i+15]); - unsigned int width=atoi(argv[i+16]); - unsigned int height=atoi(argv[i+17]); - //------------------------------------------------------------------------------- - prepare4x4Human36MRotationMatrix(&renderingConfiguration.viewMatrix,rX,rY,rZ); - //copy3x3FMatrixTo4x4F(renderingConfiguration.viewMatrix,renderingConfiguration.R); - - // 0 1 2 3 - // 4 5 6 7 - // 8 9 10 11 - // 12 13 14 15 - //---------------------------------------------------------------- - renderingConfiguration.viewMatrix.m[3] =renderingConfiguration.T[0]; - renderingConfiguration.viewMatrix.m[7] =renderingConfiguration.T[1]; - renderingConfiguration.viewMatrix.m[11]=renderingConfiguration.T[2]; - //---------------------------------------- - renderingConfiguration.viewport[0]=0; - renderingConfiguration.viewport[1]=0; - renderingConfiguration.viewport[2]=width; - renderingConfiguration.viewport[3]=height; - renderingConfiguration.width=width; - renderingConfiguration.height=height; - //---------------------------------------- - buildOpenGLProjectionForIntrinsics( - renderingConfiguration.projection.m , - renderingConfiguration.viewport , - renderingConfiguration.fX, - renderingConfiguration.fY, - 1.0,//sr->skew, - renderingConfiguration.cX, - renderingConfiguration.cY, - width, - height, - renderingConfiguration.near, //Near - renderingConfiguration.far //Far - ); - - if ( (renderingConfiguration.k1!=0.0) || (renderingConfiguration.k2!=0.0) || (renderingConfiguration.k3!=0.0) || (renderingConfiguration.p1!=0.0) || (renderingConfiguration.p2!=0.0) ) - { - fprintf(stderr,"The distortion model has not been implemented so the BVH tool is not able to meet your configuration criteria..!\n"); - exit(1); - } - - if ( (rX!=0.0) || (rY!=0.0) || (rZ!=0.0) ) - { - fprintf(stderr,"Camera rotation change has been prohibited, please don't use a camera rotation..!\n"); - exit(1); - } - - if ( (renderingConfiguration.T[0]!=0.0) || (renderingConfiguration.T[1]!=0.0) || (renderingConfiguration.T[2]!=0.0) ) - { - fprintf(stderr,"Camera position change has been prohibited, please don't use a camera rotation..!\n"); - exit(1); - } - - //TODO: Normally at this point we should have defined the matrices needed and set the following switch - // Due to some missing stuff in the pipeline this is deactivated so only fX,fY , cX,cY , Width/Height - // settings are honored by the converter.. - //---------------------------------------- - //renderingConfiguration.isDefined=1; TODO: <- at some point fix this.. - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--changeJointDimensions")==0) - { - if (i+4>=argc) { incorrectArguments(); } - if ( - !bvh_changeJointDimensions( - &bvhMotion, - argv[i+1], - atof(argv[i+2]), - atof(argv[i+3]), - atof(argv[i+4]) - ) - ) - { - fprintf(stderr,RED "failed to change `%s` joint dimensions\n",argv[i+1]); - haltOnError(immediatelyHaltOnError,"Error while changing joint dimensions.."); - } - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--onlyAnimateGivenJoints")==0) - { - unsigned int numberOfArguments=atoi(argv[i+1]); - bvh_onlyAnimateGivenJoints(&bvhMotion,numberOfArguments,argv+i+2); - // - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--scale")==0) - { - if (i+1>=argc) { incorrectArguments(); } - scaleWorld=atof(argv[i+1]); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--filterOccludedJoints")==0) - { - //TEST: ./BVHTester --from brokenHand.bvh --svg ./ --filterOccludedJoints - // ./BVHTester --from Motions/02_03.bvh --filterOccludedJoints --bvh test.bvh - filterOccludedJoints=1; - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--scaleOffsets")==0) - { - if (i+1>=argc) { incorrectArguments(); } - float scaleRatio = atof(argv[i+1]); - fprintf(stderr,"Offset scaling ratio = %0.2f \n",scaleRatio); - bvh_scaleAllOffsets( - &bvhMotion, - scaleRatio - ); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--scaleJointChildrenOffsets")==0) - { - if (i+2>=argc) { incorrectArguments(); } - const char * jointName = argv[i+1]; - float scaleRatio = atof(argv[i+2]); - fprintf(stderr,"Joint Children of %s will get an offset scaling ratio = %0.2f \n",jointName,scaleRatio); - bvh_scaleAllJointChildrenOffsets( - &bvhMotion, - jointName, - scaleRatio - ); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--onlyFirstFrame")==0) - { - bvh_copyMotionFrame(&bvhMotion, 0, 1 ); - bvhMotion.numberOfFrames=2; //Just Render one frame.. - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--maxFrames")==0) - { - if (i+1>=argc) { incorrectArguments();} - unsigned int maxFrames=atoi(argv[i+1]); - //We can limit the number of frames - if (maxFrames!=0) - { - //Only reducing number of frames - if (bvhMotion.numberOfFrames>maxFrames) - { - bvhMotion.numberOfFrames = maxFrames; - } - } - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--from")==0) - { - if (i+1>=argc) { incorrectArguments();} - fromBVHFile=argv[i+1]; - //First of all we need to load the BVH file - if (!bvh_loadBVH(fromBVHFile, &bvhMotion, scaleWorld)) - { - fprintf(stderr,"Error loading file `%s` \n",fromBVHFile); - haltOnError(immediatelyHaltOnError,"Error loading bvh file.."); - } - - //Change joint names.. - bvh_renameJointsForCompatibility(&bvhMotion); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--addfrom")==0) - { - fprintf(stderr,"File %s initially had %u frames\n",bvhMotion.fileName,bvhMotion.numberOfFrames); - if (i+1>=argc) { incorrectArguments();} - const char * addfromBVHFile=argv[i+1]; - struct BVH_MotionCapture addedMotion={0}; - //First of all we need to load the BVH file - if (!bvh_loadBVH(addfromBVHFile, &addedMotion, scaleWorld)) - { - fprintf(stderr,"Error loading file `%s` \n",addfromBVHFile); - haltOnError(immediatelyHaltOnError,"Error loading bvh file.."); - } - - //Change joint names.. - bvh_renameJointsForCompatibility(&addedMotion); - bvh_GrowMocapFileByCopyingOtherMocapFile( - &bvhMotion, - &addedMotion - ); - fprintf(stderr,"After adding %s file %s has %u frames\n",addedMotion.fileName,bvhMotion.fileName,bvhMotion.numberOfFrames); - bvh_free(&addedMotion); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--addpositionalchannels")==0) - { - // ./BVHTester --from dataset/head.bvh --addpositionalchannels --bvh test.bvh - bvh_mergeOffsetsInMotions(&bvhMotion); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--merge")==0) - { - if (i+2>=argc) { incorrectArguments();} - const char * BVHPathToFileToMerge = argv[i+1]; - const char * pathToMergeRules = argv[i+2]; - - struct BVH_MotionCapture bvhMotionToMerge={0}; - if ( bvh_loadBVH(BVHPathToFileToMerge, &bvhMotionToMerge, scaleWorld) ) - { - bvh_renameJointsForCompatibility(&bvhMotionToMerge); - if ( - !bvh_mergeWith( - &bvhMotion, - &bvhMotionToMerge, - pathToMergeRules - ) - ) - { - fprintf(stderr,"Failed to merge files (%s and %s)..\n",fromBVHFile,BVHPathToFileToMerge); - } - } else - { - fprintf(stderr,"Could not open BVH file that was requested to be merged (%s)..\n",BVHPathToFileToMerge); - } - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--blenderCoordinateSystemChange")==0) - { - //Blender uses a different coordinate system for the BVH files - //This call will try to do the coordinate change to return to our coordinate system - //among other things : ( https://projects.blender.org/blender/blender-addons/issues/104549 ) - //hopefully this will solve the most major discrepancies.. - //./BVHTester --from BLENDERheaderWithHeadAndOneMotionTEST.bvh --blenderCoordinateSystemChange --bvh test.bvh - bvh_coordinateSystemChange(&bvhMotion,"XYZ","X-ZY"); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--swap")==0) - { - if (i+2>=argc) { incorrectArguments(); } - bvh_GrowMocapFileBySwappingJointAndItsChildren( - &bvhMotion, - argv[i+1], - argv[i+2], - 0 - ); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--normalizeRotations")==0) - { - bvh_normalizeRotations(&bvhMotion); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--360")==0) - { - if (i+1>=argc) { incorrectArguments(); } - - bvh_GrowMocapFileByGeneratingPoseFromAllViewingAngles( - &bvhMotion, - atoi(argv[i+1]) - ); - } else - //----------------------------------------------------- - // This does not work.. - //if (strcmp(argv[i],"--mirror")==0) - //{ - // if (i+2>=argc) { incorrectArguments(); } - // bvh_MirrorJointsThroughIK( - // &bvhMotion, - // argv[i+1], - // argv[i+2] - // ); - //} else - //----------------------------------------------------- - //----------------------------------------------------- - if (strcmp(argv[i],"--interpolate")==0) - { - bvh_InterpolateMotion( - &bvhMotion - ); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--symmetricflip")==0) - { - BVHFrameID fID = 0; - for (fID=0; fID=argc) { incorrectArguments(); } - bvh_GrowMocapFileByCopyingExistingMotions( - &bvhMotion, - atoi(argv[i+1]) - ); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--set")==0) - { - //./BVHTester --from dataset/lhand.qbvh --repeat 100 --set 3 0.5 --set 4 -0.5 --set 5 -0.5 --set 6 0.5 --bvh restR.bvh - - if (i+2>=argc) { incorrectArguments(); } - - int mID=atoi(argv[i+1]); - float value=atof(argv[i+2]); - - bvh_setMIDValue( - &bvhMotion, - mID, - value - ); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--setPositionRotation")==0) - { - if (i+6>=argc) { incorrectArguments(); } - - struct motionTransactionData cameraPositionRotation={0}; - cameraPositionRotation.data[MOTIONBUFFER_TRANSACTION_DATA_FIELDS_POSITION_X]=-1*atof(argv[i+1])/10; - cameraPositionRotation.data[MOTIONBUFFER_TRANSACTION_DATA_FIELDS_POSITION_Y]=-1*atof(argv[i+2])/10; - cameraPositionRotation.data[MOTIONBUFFER_TRANSACTION_DATA_FIELDS_POSITION_Z]=-1*atof(argv[i+3])/10; - cameraPositionRotation.data[MOTIONBUFFER_TRANSACTION_DATA_FIELDS_ROTATION_X]=atof(argv[i+4]); - cameraPositionRotation.data[MOTIONBUFFER_TRANSACTION_DATA_FIELDS_ROTATION_Y]=atof(argv[i+5]); - cameraPositionRotation.data[MOTIONBUFFER_TRANSACTION_DATA_FIELDS_ROTATION_Z]=atof(argv[i+6]); - bvh_SetPositionRotation( - &bvhMotion, - &cameraPositionRotation - ); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--offsetPositionRotation")==0) - { - if (i+6>=argc) { incorrectArguments(); } - - struct motionTransactionData cameraPositionRotation={0}; - cameraPositionRotation.data[MOTIONBUFFER_TRANSACTION_DATA_FIELDS_POSITION_X]=-1*atof(argv[i+1])/10; - cameraPositionRotation.data[MOTIONBUFFER_TRANSACTION_DATA_FIELDS_POSITION_Y]=-1*atof(argv[i+2])/10; - cameraPositionRotation.data[MOTIONBUFFER_TRANSACTION_DATA_FIELDS_POSITION_Z]=-1*atof(argv[i+3])/10; - cameraPositionRotation.data[MOTIONBUFFER_TRANSACTION_DATA_FIELDS_ROTATION_X]=atof(argv[i+4]); - cameraPositionRotation.data[MOTIONBUFFER_TRANSACTION_DATA_FIELDS_ROTATION_Y]=atof(argv[i+5]); - cameraPositionRotation.data[MOTIONBUFFER_TRANSACTION_DATA_FIELDS_ROTATION_Z]=atof(argv[i+6]); - bvh_OffsetPositionRotation( - &bvhMotion, - &cameraPositionRotation - ); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--perturbJointAngles")==0) - { - if (i+2>=argc) { incorrectArguments(); } - unsigned int numberOfValues=atoi(argv[i+1]); - float deviation=atof(argv[i+2]); - srand(time(NULL)); - if (i+2+numberOfValues>=argc) { incorrectArguments(); } else - { - if ( - !bvh_PerturbJointAngles( - &bvhMotion, - numberOfValues, - deviation, - argv, - i+2 - ) - ) { haltOnError(immediatelyHaltOnError,"Error while perturbing joint angles"); } - } - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--randomizeMID")==0) - { - //------------------------------------------ - BVHMotionChannelID mID=atoi(argv[i+1]); - float startOfRandomization=atof(argv[i+2]); - float endOfRandomization=atof(argv[i+3]); - //------------------------------------------ - if ( - !bvh_RandomizeSingleMIDInRange( - &bvhMotion, - mID, - startOfRandomization, - endOfRandomization - ) - ) { haltOnError(immediatelyHaltOnError,"Error while randomizing a single mID angle"); } - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--randomizeJointAngles")==0) - { - // ./BVHTester --from Motions/DAZFriendlyCMUPlusHeadAndHandsAndFeet.bvh --repeat 100 --randomizeJointAngles 15 -65 0 1 finger5-1.l finger5-2.l finger5-3.l finger4-1.l finger4-2.l finger4-3.l finger3-1.l finger3-2.l finger3-3.l finger2-1.l finger2-2.l finger2-3.l finger1-1.l finger1-2.l finger1-3.l --randomizeJointAngles 5 -90 0 1 finger5-1.l finger4-1.l finger3-1.l finger2-1.l finger1-1.l --randomizeJointAngles 2 -75 75 2 finger1-1.l finger1-2.l --randomizeJointAngles 1 -45 0 3 finger1-1.l --bvh restR.bvh - // ./BVHTester --from Motions/DAZFriendlyCMUPlusHeadAndHandsAndFeet.bvh --repeat 100 --randomizeJointAngles 15 0 65 1 finger5-1.r finger5-2.r finger5-3.r finger4-1.r finger4-2.r finger4-3.r finger3-1.r finger3-2.r finger3-3.r finger2-1.r finger2-2.r finger2-3.r finger1-1.r finger1-2.r finger1-3.r --randomizeJointAngles 5 0 90 1 finger5-1.r finger4-1.r finger3-1.r finger2-1.r finger1-1.r --randomizeJointAngles 2 -75 75 2 finger1-1.r finger1-2.r --randomizeJointAngles 1 0 45 3 finger1-1.r --bvh restR.bvh - if (i+2>=argc) { incorrectArguments(); } - unsigned int numberOfValues=atoi(argv[i+1]); - float startOfRandomization=atof(argv[i+2]); - float endOfRandomization=atof(argv[i+3]); - unsigned int specificChannelRandomization=atoi(argv[i+4]); - srand(time(NULL)); - if (i+2+numberOfValues>=argc) { incorrectArguments(); } else - { - if ( - !bvh_PerturbJointAnglesRange( - &bvhMotion, - numberOfValues, - startOfRandomization, - endOfRandomization, - specificChannelRandomization, - argv, - i+4 - ) - ) { haltOnError(immediatelyHaltOnError,"Error while randomizing joint angles"); } - } - //exit(0); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--importCSVPoses")==0) - { - //./BVHTester --from lhand.qbvh --importCSVPoses sobolLHand_131072.csv - if (i+1>=argc) { incorrectArguments(); } - const char * filenameOfCSVFile=argv[i+1]; - fprintf(stderr,"bvh_ImportCSVPoses(%s)\n",filenameOfCSVFile); - srand(time(NULL)); - if ( - !bvh_ImportCSVPoses( - &bvhMotion, - filenameOfCSVFile - ) - ) { haltOnError(immediatelyHaltOnError,"Error while importing CSV poses"); } - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--randomizeBasedOnIKConstrains")==0) - { - // ./BVHTester --from dataset/lhand.qbvh --repeat 100 --randomizeBasedOnIKConstrains lhand --bvh restR.bvh - if (i+1>=argc) { incorrectArguments(); } - const char * nameOfIKProblem=argv[i+1]; - fprintf(stderr,"bvh_RandomizeBasedOnIKProblem(%s)\n",nameOfIKProblem); - srand(time(NULL)); - if ( - !bvh_RandomizeBasedOnIKProblem( - &bvhMotion, - nameOfIKProblem - ) - ) { haltOnError(immediatelyHaltOnError,"Error while randomizing joint angles based on IK problem"); } - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--selectJoints")==0) - { - if (i+2>=argc) { incorrectArguments(); } - unsigned int includeEndJoints=atoi(argv[i+1]); - unsigned int numberOfValues=atoi(argv[i+2]); - if (i+2+numberOfValues>=argc) { incorrectArguments(); } else - { - if ( - !bvh_selectJoints( - &bvhMotion, - numberOfValues, - includeEndJoints,//include End Joints - argv, - i+2 - ) - ) { haltOnError(immediatelyHaltOnError,"Error while selecting Joints"); } - } - } else - //----------------------------------------------------- - //----------------------------------------------------- - if (strcmp(argv[i],"--hide2DLocationOfJoints")==0) - { - if (i+2>=argc) { incorrectArguments(); } - unsigned int includeEndJoints=atoi(argv[i+1]); - unsigned int numberOfValues=atoi(argv[i+2]); - if (i+2+numberOfValues>=argc) { incorrectArguments(); } else - { - if ( - !bvh_selectJointsToHide2D( - &bvhMotion, - numberOfValues, - includeEndJoints, - argv, - i+2 - ) - ) { haltOnError(immediatelyHaltOnError,"Error while selecting 2D Joints"); } - } - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--eraseJoints")==0) - { - if (i+1>=argc) { incorrectArguments(); } - unsigned int numberOfValues=atoi(argv[i+1]); - if (i+1+numberOfValues>=argc) { incorrectArguments(); } else - { - if ( - !bvh_eraseJoints( - &bvhMotion, - numberOfValues, - 1,//include End Joints - argv, - i+1 - ) - ) { haltOnError(immediatelyHaltOnError,"Error while selecting joints to erase"); } - } - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--randomize2D")==0) - { - // ./BVHTester --from Motions/02_03.bvh --randomize2D 1400 5000 -35 -90 -35 35 90 35 --occlusions --svg tmp/ - if (i+8>=argc) { incorrectArguments(); } - float minimumRotation[3]; - float maximumRotation[3]; - - //Randomize 2D expects millimeters and converts them to centimeters internally - float minimumDepth=-1*atof(argv[i+1])/10; - float maximumDepth=-1*atof(argv[i+2])/10; - //---- - minimumRotation[0]=atof(argv[i+3]); - minimumRotation[1]=atof(argv[i+4]); - minimumRotation[2]=atof(argv[i+5]); - //---- - maximumRotation[0]=atof(argv[i+6]); - maximumRotation[1]=atof(argv[i+7]); - maximumRotation[2]=atof(argv[i+8]); - //---- - - if (bvhMotion.jointHierarchy[bvhMotion.rootJointID].hasQuaternionRotation) - { //BVH Quaternion - fprintf(stderr,"Quaternion rotations handled in bvh_RandomizeRotationsOfFrameBasedOn3D using euler2Quaternions..!\n"); - } - - bvh_RandomizePositionFrom2D( - &bvhMotion, - minimumRotation, - maximumRotation, - minimumDepth, - maximumDepth, - renderingConfiguration.fX, - renderingConfiguration.fY, - renderingConfiguration.cX, - renderingConfiguration.cY, - renderingConfiguration.width, - renderingConfiguration.height - ); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--randomize")==0) - { - if (i+12>=argc) { incorrectArguments(); } - srand(time(NULL)); - - float minimumPosition[3]; - float minimumRotation[3]; - float maximumPosition[3]; - float maximumRotation[3]; - - //---- - minimumPosition[0]=-1*atof(argv[i+1])/10; - minimumPosition[1]=-1*atof(argv[i+2])/10; - minimumPosition[2]=-1*atof(argv[i+3])/10; - //---- - minimumRotation[0]=atof(argv[i+4]); - minimumRotation[1]=atof(argv[i+5]); - minimumRotation[2]=atof(argv[i+6]); - //---- - maximumPosition[0]=-1*atof(argv[i+7])/10; - maximumPosition[1]=-1*atof(argv[i+8])/10; - maximumPosition[2]=-1*atof(argv[i+9])/10; - //---- - maximumRotation[0]=atof(argv[i+10]); - maximumRotation[1]=atof(argv[i+11]); - maximumRotation[2]=atof(argv[i+12]); - - - bvh_RandomizePositionRotation( - &bvhMotion, - minimumPosition, - minimumRotation, - maximumPosition, - maximumRotation - ); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--randomize2Dranges")==0) - { - // ./BVHTester --from Motions/02_03.bvh --randomize2Dranges 1400 5000 -35 -179.999999 -35 35 -90 35 -35 90 -35 35 180 35 --occlusions --svg tmp/ - if (i+14>=argc) { incorrectArguments(); } - float minimumRotationRangeA[3]; - float maximumRotationRangeA[3]; - float minimumRotationRangeB[3]; - float maximumRotationRangeB[3]; - - float minimumDepth=-1*atof(argv[i+1])/10; - float maximumDepth=-1*atof(argv[i+2])/10; - //---- - minimumRotationRangeA[0]=atof(argv[i+3]); - minimumRotationRangeA[1]=atof(argv[i+4]); - minimumRotationRangeA[2]=atof(argv[i+5]); - //---- - maximumRotationRangeA[0]=atof(argv[i+6]); - maximumRotationRangeA[1]=atof(argv[i+7]); - maximumRotationRangeA[2]=atof(argv[i+8]); - //---- - minimumRotationRangeB[0]=atof(argv[i+9]); - minimumRotationRangeB[1]=atof(argv[i+10]); - minimumRotationRangeB[2]=atof(argv[i+11]); - //---- - maximumRotationRangeB[0]=atof(argv[i+12]); - maximumRotationRangeB[1]=atof(argv[i+13]); - maximumRotationRangeB[2]=atof(argv[i+14]); - //---- - - bvh_RandomizePositionFrom2DRotation2Ranges( - &bvhMotion, - minimumRotationRangeA, - maximumRotationRangeA, - minimumRotationRangeB, - maximumRotationRangeB, - minimumDepth, - maximumDepth, - renderingConfiguration.fX, - renderingConfiguration.fY, - renderingConfiguration.cX, - renderingConfiguration.cY, - renderingConfiguration.width, - renderingConfiguration.height - ); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--randomizeranges")==0) - { - if (i+24>=argc) { incorrectArguments(); } - srand(time(NULL)); - - float minimumPositionRangeA[3]; - float minimumRotationRangeA[3]; - float maximumPositionRangeA[3]; - float maximumRotationRangeA[3]; - - float minimumPositionRangeB[3]; - float minimumRotationRangeB[3]; - float maximumPositionRangeB[3]; - float maximumRotationRangeB[3]; - - //---- - minimumPositionRangeA[0]=-1*atof(argv[i+1])/10; - minimumPositionRangeA[1]=-1*atof(argv[i+2])/10; - minimumPositionRangeA[2]=-1*atof(argv[i+3])/10; - //---- - minimumRotationRangeA[0]=atof(argv[i+4]); - minimumRotationRangeA[1]=atof(argv[i+5]); - minimumRotationRangeA[2]=atof(argv[i+6]); - //---- - maximumPositionRangeA[0]=-1*atof(argv[i+7])/10; - maximumPositionRangeA[1]=-1*atof(argv[i+8])/10; - maximumPositionRangeA[2]=-1*atof(argv[i+9])/10; - //---- - maximumRotationRangeA[0]=atof(argv[i+10]); - maximumRotationRangeA[1]=atof(argv[i+11]); - maximumRotationRangeA[2]=atof(argv[i+12]); - - //---- - minimumPositionRangeB[0]=-1*atof(argv[i+13])/10; - minimumPositionRangeB[1]=-1*atof(argv[i+14])/10; - minimumPositionRangeB[2]=-1*atof(argv[i+15])/10; - //---- - minimumRotationRangeB[0]=atof(argv[i+16]); - minimumRotationRangeB[1]=atof(argv[i+17]); - minimumRotationRangeB[2]=atof(argv[i+18]); - //---- - maximumPositionRangeB[0]=-1*atof(argv[i+19])/10; - maximumPositionRangeB[1]=-1*atof(argv[i+20])/10; - maximumPositionRangeB[2]=-1*atof(argv[i+21])/10; - //---- - maximumRotationRangeB[0]=atof(argv[i+22]); - maximumRotationRangeB[1]=atof(argv[i+23]); - maximumRotationRangeB[2]=atof(argv[i+24]); - - bvh_RandomizePositionRotation2Ranges( - &bvhMotion, - minimumPositionRangeA, - minimumRotationRangeA, - maximumPositionRangeA, - maximumRotationRangeA, - minimumPositionRangeB, - minimumRotationRangeB, - maximumPositionRangeB, - maximumRotationRangeB - ); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--to")==0) - { - //./BVHTester --from Motions/02_03.bvh --to Motions/cmuTomakehuman.profile test.conf - if (i+2>=argc) { incorrectArguments(); } - const char * retargetProfile=argv[i+1];//"Motions/cmu.profile"; - const char * toSceneFile=argv[i+2]; - //toSceneFileTRI - - struct bvhToTRI bvhtri={0}; - bvh_loadBVHToTRIAssociationFile(retargetProfile,&bvhtri,&bvhMotion); - dumpBVHToTrajectoryParserTRI(toSceneFileTRI,&bvhMotion,&bvhtri,1/*USE Irugubak oisutuib*/,0); - dumpBVHToTrajectoryParserPrimitives(toSceneFile,&bvhMotion); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--studymid")==0) - { - BVHFrameID fID = atoi(argv[i+1]); - BVHMotionChannelID mID = atoi(argv[i+2]); - float minRange = -180.0; - float maxRange = 180.0; - float resolution = 3.0; - - fprintf(stderr,"abvh_studyMID2DImpact(%u,%u,%0.2f,%0.2f)\n",fID,mID,minRange,maxRange); - bvh_studyMID2DImpact( - &bvhMotion, - &renderingConfiguration, - fID, - mID, - &minRange, - &maxRange, - &resolution - ); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--study3d")==0) - { - BVHFrameID fID = atoi(argv[i+1]); - BVHMotionChannelID jID = atoi(argv[i+2]); - float minRange = -180.0; - float maxRange = 180.0; - float resolution = 6.0; - bvh_study3DJoint2DImpact( - &bvhMotion, - &renderingConfiguration, - fID, - jID, - &minRange, - &maxRange, - &resolution - ); - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--bvh")==0) - { - if (i+1>=argc) { incorrectArguments(); } - const char * toBVHFile=argv[i+1]; - if ( - !dumpBVHToBVH( - toBVHFile, - &bvhMotion, - 1, //Write Hierarchy - 1 //Write Motion - ) - ) { haltOnError(immediatelyHaltOnError,"Error while outputing a BVH file.."); } - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--angleheatmap")==0) - { - convertToAngleHeatmap=1; - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--csv")==0) - { - if (i+3>=argc) { incorrectArguments(); } - toSVGDirectory=argv[i+1]; - toCSVFilename=argv[i+2]; - convertToCSV=1; - if (strcmp(argv[i+3],"2d+3d+bvh")==0){ useCSV_2D_Output=1; useCSV_3D_Output=1; useCSV_BVH_Output=1; } else - if (strcmp(argv[i+3],"2d+bvh")==0 ) { useCSV_2D_Output=1; useCSV_3D_Output=0; useCSV_BVH_Output=1; } else - if (strcmp(argv[i+3],"2d")==0 ) { useCSV_2D_Output=1; useCSV_3D_Output=0; useCSV_BVH_Output=0; } else - if (strcmp(argv[i+3],"3d")==0 ) { useCSV_2D_Output=0; useCSV_3D_Output=1; useCSV_BVH_Output=0; } else - if (strcmp(argv[i+3],"bvh")==0 ) { useCSV_2D_Output=0; useCSV_3D_Output=0; useCSV_BVH_Output=1; } else - { useCSV_2D_Output=1; useCSV_3D_Output=1; useCSV_BVH_Output=1; } - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--json")==0) - { - convertToJSON=1; - if (i+2>=argc) { incorrectArguments(); } - toSVGDirectory=argv[i+1]; - toCSVFilename=argv[i+2]; - //if (i+3>=argc) { incorrectArguments(); } - //if (strcmp(argv[i+3],"2d+bvh")==0 ) { useCSV_2D_Output=1; useCSV_3D_Output=0; useCSV_BVH_Output=1; } else - //if (strcmp(argv[i+3],"2d")==0 ) { useCSV_2D_Output=1; useCSV_3D_Output=0; useCSV_BVH_Output=0; } else - //if (strcmp(argv[i+3],"3d")==0 ) { useCSV_2D_Output=0; useCSV_3D_Output=1; useCSV_BVH_Output=0; } else - //if (strcmp(argv[i+3],"bvh")==0 ) { useCSV_2D_Output=0; useCSV_3D_Output=0; useCSV_BVH_Output=1; } else - // { useCSV_2D_Output=1; useCSV_3D_Output=1; useCSV_BVH_Output=1; } - } else - //----------------------------------------------------- - if (strcmp(argv[i],"--svg")==0) - { - if (i+1>=argc) { incorrectArguments(); } - toSVGDirectory=argv[i+1]; - - char removeOldSVGFilesCommand[512]; - snprintf(removeOldSVGFilesCommand,512,"rm %s/*.svg",toSVGDirectory); - int res = system(removeOldSVGFilesCommand); - if (res!=0) { fprintf(stderr,"Could not clean svg files in %s",toSVGDirectory); } - convertToSVG=1; - } - //----------------------------------------------------- - /* else - //Check for incorrect input, this needs to become a smarter check - { - if ((i>0) && (argv[i][0]!="-")) - { - fprintf(stderr,RED "Unidentified argument %u = %s ..!" NORMAL,i,argv[i]); - incorrectArguments(); - printCallingParameters(argc,argv); - } - }*/ - } - - //SVG or CSV output .. - if ( (convertToJSON) || (convertToSVG) || (convertToCSV) ) - { - struct filteringResults filterStats={0}; - - dumpBVHTo_JSON_SVG_CSV( - toSVGDirectory, - toCSVFilename, - convertToJSON, - convertToSVG, - convertToCSV, - convertToAngleHeatmap, - useCSV_2D_Output,useCSV_3D_Output,useCSV_BVH_Output, - wipe_2D_Output,wipe_3D_Output,wipe_BVH_Output, - &bvhMotion, - &renderingConfiguration, - &filterStats, - sampleSkip, - occlusions, - filterOccludedJoints, - filterBehindCamera,//Filter out all poses where even one joint is behind camera - filterIfAnyJointOutsideof2DFrame,//Filter out all poses where even one joint is outside of 2D frame - filterTopWeirdRandomSkeletons,//Filter top left weird random skelingtons ( skeletons ) - 0//We don't want to convert to radians - ); - } - - - bvh_free(&bvhMotion); - - return 0; -} - - -#ifndef BVH_USE_AS_A_LIBRARY -int main(int argc,const char **argv) -{ - srand(time(NULL)); // randomize seed - fprintf(stderr,"BVH Loader code - v%s\n\n",BVH_LOADER_VERSION_STRING); - return bvhConverter(argc,argv); -} -#endif // BVH_USE_AS_A_LIBRARY - diff --git a/src/python/mnet4/BVH/makeLibrary.sh b/src/python/mnet4/BVH/makeLibrary.sh deleted file mode 100755 index c8c3a2e..0000000 --- a/src/python/mnet4/BVH/makeLibrary.sh +++ /dev/null @@ -1,130 +0,0 @@ -#!/bin/bash - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -cd "$DIR" - -echo "JIT Python/C Compilation *made by AmmarTM* handled by : " -gcc --version - -#in case of a build after ./batherFiles.sh -BVHTESTER_DIR="opengl_acquisition_shared_library/opengl_depth_and_color_renderer/src/Applications/BVHTester" -AMMATRIX_DIRECTORY="tools/AmMatrix" -MODELLOADER_DIRECTORY="opengl_acquisition_shared_library/opengl_depth_and_color_renderer/src/Library/ModelLoader" -BVH_DIRECTORY="opengl_acquisition_shared_library/opengl_depth_and_color_renderer/src/Library/MotionCaptureLoader" -INPUTPARSER_DIRECTORY="opengl_acquisition_shared_library/opengl_depth_and_color_renderer/src/Library/TrajectoryParser" - -BVHTESTER_DIR="." -AMMATRIX_DIRECTORY="../../../../../tools/AmMatrix" -MODELLOADER_DIRECTORY="../../Library/ModelLoader" -BVH_DIRECTORY="../../Library/MotionCaptureLoader" -INPUTPARSER_DIRECTORY="../../Library/TrajectoryParser" - -SOURCE=" -$AMMATRIX_DIRECTORY/matrix3x3Tools.c -$AMMATRIX_DIRECTORY/matrix3x3Tools.h -$AMMATRIX_DIRECTORY/matrix4x4Tools.c -$AMMATRIX_DIRECTORY/matrix4x4Tools.h -$AMMATRIX_DIRECTORY/matrixCalculations.c -$AMMATRIX_DIRECTORY/matrixCalculations.h -$AMMATRIX_DIRECTORY/matrixOpenGL.c -$AMMATRIX_DIRECTORY/matrixOpenGL.h -$AMMATRIX_DIRECTORY/quaternions.c -$AMMATRIX_DIRECTORY/quaternions.h -$AMMATRIX_DIRECTORY/simpleRenderer.c -$AMMATRIX_DIRECTORY/simpleRenderer.h -$AMMATRIX_DIRECTORY/solveLinearSystemGJ.c -$AMMATRIX_DIRECTORY/solveLinearSystemGJ.h -$MODELLOADER_DIRECTORY/hardcoded_shapes.h -$MODELLOADER_DIRECTORY/model_converter.c -$MODELLOADER_DIRECTORY/model_converter.h -$MODELLOADER_DIRECTORY/model_editor.c -$MODELLOADER_DIRECTORY/model_editor.h -$MODELLOADER_DIRECTORY/model_loader.h -$MODELLOADER_DIRECTORY/model_loader_assimp.h -$MODELLOADER_DIRECTORY/model_loader_hardcoded.h -$MODELLOADER_DIRECTORY/model_loader_obj.h -$MODELLOADER_DIRECTORY/model_loader_setup.h -$MODELLOADER_DIRECTORY/model_loader_transform_joints.c -$MODELLOADER_DIRECTORY/model_loader_transform_joints.h -$MODELLOADER_DIRECTORY/model_loader_tri.c -$MODELLOADER_DIRECTORY/model_loader_tri.h -$MODELLOADER_DIRECTORY/model_processor.c -$MODELLOADER_DIRECTORY/model_processor.h -$MODELLOADER_DIRECTORY/tri_bvh_controller.h -$BVH_DIRECTORY/bvh_loader.c -$BVH_DIRECTORY/bvh_loader.h -$BVH_DIRECTORY/calculate/bvh_project.c -$BVH_DIRECTORY/calculate/bvh_project.h -$BVH_DIRECTORY/calculate/bvh_to_tri_pose.c -$BVH_DIRECTORY/calculate/bvh_to_tri_pose.h -$BVH_DIRECTORY/calculate/smoothing.c -$BVH_DIRECTORY/calculate/smoothing.h -$BVH_DIRECTORY/calculate/bvh_transform.c -$BVH_DIRECTORY/calculate/bvh_transform.h -$BVH_DIRECTORY/edit/bvh_cut_paste.c -$BVH_DIRECTORY/edit/bvh_cut_paste.h -$BVH_DIRECTORY/edit/bvh_filter.c -$BVH_DIRECTORY/edit/bvh_filter.h -$BVH_DIRECTORY/edit/bvh_interpolate.c -$BVH_DIRECTORY/edit/bvh_interpolate.h -$BVH_DIRECTORY/edit/bvh_merge.c -$BVH_DIRECTORY/edit/bvh_merge.h -$BVH_DIRECTORY/edit/bvh_randomize.c -$BVH_DIRECTORY/edit/bvh_randomize.h -$BVH_DIRECTORY/edit/bvh_remapangles.c -$BVH_DIRECTORY/edit/bvh_remapangles.h -$BVH_DIRECTORY/edit/bvh_rename.c -$BVH_DIRECTORY/edit/bvh_rename.h -$BVH_DIRECTORY/edit/cTextFileToMemory.h -$BVH_DIRECTORY/export/bvh_export.c -$BVH_DIRECTORY/export/bvh_export.h -$BVH_DIRECTORY/export/bvh_to_bvh.c -$BVH_DIRECTORY/export/bvh_to_bvh.h -$BVH_DIRECTORY/export/bvh_to_c.c -$BVH_DIRECTORY/export/bvh_to_c.h -$BVH_DIRECTORY/export/bvh_to_csv.c -$BVH_DIRECTORY/export/bvh_to_csv.h -$BVH_DIRECTORY/export/bvh_to_svg.c -$BVH_DIRECTORY/export/bvh_to_svg.h -$BVH_DIRECTORY/export/bvh_to_json.c -$BVH_DIRECTORY/export/bvh_to_json.h -$BVH_DIRECTORY/export/bvh_to_trajectoryParserPrimitives.c -$BVH_DIRECTORY/export/bvh_to_trajectoryParserPrimitives.h -$BVH_DIRECTORY/export/bvh_to_trajectoryParserTRI.c -$BVH_DIRECTORY/export/bvh_to_trajectoryParserTRI.h -$BVH_DIRECTORY/ik/bvh_inverseKinematics.c -$BVH_DIRECTORY/ik/bvh_inverseKinematics.h -$BVH_DIRECTORY/ik/hardcodedProblems_inverseKinematics.c -$BVH_DIRECTORY/ik/hardcodedProblems_inverseKinematics.h -$BVH_DIRECTORY/ik/levmar.c -$BVH_DIRECTORY/ik/levmar.h -$BVH_DIRECTORY/import/fromBVH.c -$BVH_DIRECTORY/import/fromBVH.h -$BVH_DIRECTORY/metrics/bvh_measure.c -$BVH_DIRECTORY/metrics/bvh_measure.h -$BVH_DIRECTORY/tests/test.c -$BVH_DIRECTORY/tests/test.h -$INPUTPARSER_DIRECTORY/InputParser_C.c -$INPUTPARSER_DIRECTORY/InputParser_C.h -$BVHTESTER_DIR/bvhLibrary.h -$BVHTESTER_DIR/bvhConverter.c -" - -#$BVHTESTER_DIR/main.c <- This used to be in the same binary with the BVHTester utility, now its split.. - -INTEL_OPTIMIZATIONS=`cat /proc/cpuinfo | grep sse3` - -if [ -z "$var" ] ; then - echo "No intel optimizations available" - EXTRA_FLAGS=" " -else - echo "Intel Optimizations available and will be used" - EXTRA_FLAGS="-DINTEL_OPTIMIZATIONS" -fi - - -gcc -shared -o libBVHConverter.so -O3 -fPIC $EXTRA_FLAGS -march=native -mtune=native -lm -DBVH_USE_AS_A_LIBRARY $SOURCE - - - -exit 0 From 1cb3edb71e95c365ba40978bcb4c37103c3dd9a8 Mon Sep 17 00:00:00 2001 From: Ammar Qammaz Date: Wed, 9 Aug 2023 14:26:30 +0300 Subject: [PATCH 005/154] great news,paper got accepted, uploading new Blender/MPFB2 plugin for face --- src/python/blender/blender_face.py | 1611 +++++++++++++++++ src/python/blender/faceWhiteLists/label.tag | 1 + .../vertexWhitelist_newgirl.body.csv | 2 + .../vertexWhitelist_newgirl.eyebrow002.csv | 2 + .../vertexWhitelist_newgirl.high-poly.csv | 2 + .../blender/fullfaceWhiteLists/label.tag | 1 + .../vertexWhitelist_newgirl.body.csv | 2 + .../vertexWhitelist_newgirl.eyebrow002.csv | 2 + .../vertexWhitelist_newgirl.high-poly.csv | 2 + .../blender/headerWithHeadAndOneMotion.bvh | 1022 +++++++++++ src/python/blender/mouthWhiteLists/label.tag | 1 + .../vertexWhitelist_newgirl.body.csv | 2 + src/python/blender/reyeWhiteLists/label.tag | 1 + .../vertexWhitelist_newgirl.body.csv | 2 + .../vertexWhitelist_newgirl.eyebrow002.csv | 2 + .../vertexWhitelist_newgirl.high-poly.csv | 2 + 16 files changed, 2657 insertions(+) create mode 100644 src/python/blender/blender_face.py create mode 100644 src/python/blender/faceWhiteLists/label.tag create mode 100644 src/python/blender/faceWhiteLists/vertexWhitelist_newgirl.body.csv create mode 100644 src/python/blender/faceWhiteLists/vertexWhitelist_newgirl.eyebrow002.csv create mode 100644 src/python/blender/faceWhiteLists/vertexWhitelist_newgirl.high-poly.csv create mode 100644 src/python/blender/fullfaceWhiteLists/label.tag create mode 100644 src/python/blender/fullfaceWhiteLists/vertexWhitelist_newgirl.body.csv create mode 100644 src/python/blender/fullfaceWhiteLists/vertexWhitelist_newgirl.eyebrow002.csv create mode 100644 src/python/blender/fullfaceWhiteLists/vertexWhitelist_newgirl.high-poly.csv create mode 100644 src/python/blender/headerWithHeadAndOneMotion.bvh create mode 100644 src/python/blender/mouthWhiteLists/label.tag create mode 100644 src/python/blender/mouthWhiteLists/vertexWhitelist_newgirl.body.csv create mode 100644 src/python/blender/reyeWhiteLists/label.tag create mode 100644 src/python/blender/reyeWhiteLists/vertexWhitelist_newgirl.body.csv create mode 100644 src/python/blender/reyeWhiteLists/vertexWhitelist_newgirl.eyebrow002.csv create mode 100644 src/python/blender/reyeWhiteLists/vertexWhitelist_newgirl.high-poly.csv diff --git a/src/python/blender/blender_face.py b/src/python/blender/blender_face.py new file mode 100644 index 0000000..0cad7cd --- /dev/null +++ b/src/python/blender/blender_face.py @@ -0,0 +1,1611 @@ +#Written by Ammar Qammaz 2022-2023 +#This is a Blender Python script that upon loaded can facilitate animating a skinned model created by +#the MakeHuman plugin for Blender ( http://static.makehumancommunity.org/mpfb.html ) +mnetPluginVersion=float(0.34) + +import bpy +from bpy.props import EnumProperty + +import os +import random +import math +import gc +import numpy as np +import array +import csv + + +#Steps to generate a good dataset! +#Load a BVH file and point to it +#Load an armature and point to it +#Run this python script in blender +#Run : sobolRandomDistributionForFace.py to generate a sobol/quasi-random dataset +#Point to the target dataset directory in Dataset Path +#Point to your dataset in Dataset Path: and click Create Dataset from CSV file +# Either : +# run mediapipeDumpHead2DFromRGB.py --from your dataset path +# or +# run associate2DFiles.py to remake associations , update them here and rely on them as 2D data +csvResolutionErrors = 0 + + +class bcolors: + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKGREEN = '\033[92m' + WARNING = '\033[93m' + FAIL = '\033[91m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + +class vertexHolder(): + def __init__(self,v,vID): + self.co = v.co + self.index = vID + +def timeDuration(startTimeInSeconds,endTimeInSeconds): + timeElapsed = endTimeInSeconds - startTimeInSeconds + timeUnit = "seconds" + if (timeElapsed>86400): + timeElapsed = timeElapsed / 86400 + timeUnit = "days" + elif (timeElapsed>3600): + timeElapsed = timeElapsed / 3600 + timeUnit = "hours" + return timeElapsed,timeUnit + +def printSelectedVertex(): + mode = bpy.context.active_object.mode + # Keep track of previous mode + bpy.ops.object.mode_set(mode='OBJECT') + # Go into object mode to update the selected vertices + + obj = bpy.context.object + # Get the currently select object + sel = np.zeros(len(obj.data.vertices), dtype=np.bool) + # Create a numpy array with empty values for each vertex + + obj.data.vertices.foreach_get('select', sel) + # Populate the array with True/False if the vertex is selected + + for ind in np.where(sel==True)[0]: + # Loop over each currently selected vertex + v = obj.data.vertices[ind] + print(bcolors.OKGREEN) + print('Vertex {} at position {} is selected'.format(v.index, v.co)) + print(bcolors.ENDC) + # If you just want the first one you can break directly here + # break + + bpy.ops.object.mode_set(mode=mode) + + +def combineCSVFiles(outputCSVPath,inputCSVPathList): + targetPath = bpy.context.scene.datasetPath + filenames = list() + #================================================= + for csvF in inputCSVPathList.keys(): + #print(sys.argv[i]) + csvFilename = "%s/2d_face_all_blender_%s.csv" % (targetPath,csvF) + filenames.append(csvFilename) + + numberOfFiles = len(filenames) + #================================================= + files = list() + for i in range(0,numberOfFiles): + f = open(filenames[i],'r') + files.append(f) + #================================================= + + #================================================= + f = open(outputCSVPath, 'w') + #================================================= + readingMoreLines = True + while readingMoreLines: + thisTotalLine = "" + for i in range(0,numberOfFiles): + line = files[i].readline().replace('\n', '') + #-------------------------------- + if (i!=0): + f.write(",") + f.write(line) + #-------------------------------- + if not line: + readingMoreLines = False + break + if (readingMoreLines): + f.write("\n") + #================================================= + f.close() + #================================================= + for i in range(0,numberOfFiles): + files[i].close() + #================================================= + + return + #----------------------------------------------------------------- + + +def write_csv_2d_data_header(csvFilename,vertices,verticeCSVWhitelist=dict()): + f = open(csvFilename, 'w') + print(csvFilename," Number of vertices :",len(vertices)) + for v in range(0,len(vertices)): + if (v>0): + f.write(',') + if 'label' in verticeCSVWhitelist: + f.write('2DX_%s,2DY_%s,visible_%s' % (verticeCSVWhitelist['label'][v],verticeCSVWhitelist['label'][v],verticeCSVWhitelist['label'][v])) + else: + f.write('2DX_v%u,2DY_v%u,visible_%u' % (v,v,v)) + f.write('\n') + f.close() + +def write_vertex_csv_2d_data(objName="",baseDirectory="/home/ammar/",fID=0,csvFile=True,svgFile=True,verticeCSVWhitelistForAllObjects=dict()): + import bpy_extras + from bpy_extras.object_utils import world_to_camera_view + + #--------------------------------------------------------------------------- + if (objName==""): + print("Cannot write_vertex_csv_2d_data without an obj name!") + return + #--------------------------------------------------------------------------- + + #--------------------------------------------------------------------------- + verticeCSVWhitelist = dict() + if (len(verticeCSVWhitelistForAllObjects.keys())>0 ): #There is a whitelist declared a.k.a. dict not empty! + if objName in verticeCSVWhitelistForAllObjects: + verticeCSVWhitelist = verticeCSVWhitelistForAllObjects[objName] + else: + #If vertice whitelists exist but not for the particular object then + #we completely ignore the object and just return.. + #print("No vertex whitelist for ",objName,end=" ") + #print("We assume that this means that the whole object is blacklisted! ") + return + #if there is no white list declared we go on as usual dumping everything.. + #--------------------------------------------------------------------------- + + # Get the active object + scene = bpy.context.scene + camera = scene.objects.get("Camera") #bpy.context.scene.camera + obj = scene.objects.get(objName) # "newgirl.body" + + #Apply modifiers + dg = bpy.context.evaluated_depsgraph_get() + obj = obj.evaluated_get(dg) + mesh = obj.to_mesh(preserve_all_data_layers=True, depsgraph=dg) + + #Point vertices to either all vertices or a specific list of vertices + if ('label' in verticeCSVWhitelist) and ('body' in verticeCSVWhitelist): + #We have an active list of vertices to select/transform so we will be economic + #print(verticeCSVWhitelist['label']) + numberOfVertices = len(verticeCSVWhitelist['label']) + vertices = list() + for i in range (0,numberOfVertices): + vertexID = int(verticeCSVWhitelist['body'][0][i]) + newV = vertexHolder(mesh.vertices[vertexID],vertexID) + newV.co = obj.matrix_world @ newV.co + vertices.append(newV) + else: + #Just transform all vertices and pass them all + mesh.transform(obj.matrix_world) # apply loc/rot/scale + vertices = mesh.vertices + + render_scale = scene.render.resolution_percentage / 100 + render_size = (int(scene.render.resolution_x * render_scale),int(scene.render.resolution_y * render_scale),) + width = render_size[0] + height = render_size[1] + + #Zoomed Debugging Vertices + zoom = bpy.context.scene.zoomSVG + if (zoom): + width = 10*width + height = 10*height + + #---------------------------------------------------------------------- + csvFilename = "%s/2d_face_all_blender_%s.csv" % (baseDirectory,objName) + if (fID==0): + write_csv_2d_data_header(csvFilename=csvFilename,vertices=vertices,verticeCSVWhitelist=verticeCSVWhitelist) + if (csvFile): + fCSV = open(csvFilename, 'a') + #---------------------------------------------------------------------- + if (svgFile): + f = open('%s/blender_%s_face_dataset_%04u.svg'%(baseDirectory,objName,fID), 'w') + f.write('\n'%(height,width)) + f.write('\n'%(width,height)) + #---------------------------------------------------------------------- + + + vNum = 0 + for v in vertices: + co_final= v.co# @ obj.matrix_world + # Get the 2D projection of the vertex + coords_2d = bpy_extras.object_utils.world_to_camera_view(bpy.context.scene,camera,co_final) + #print("world_to_camera_view :",coords_2d) + x = coords_2d.x * width + y = (1.0-coords_2d.y) * height + visible = 0.0 + if (0'%(round(x),round(y))); + if ('label' in verticeCSVWhitelist): + f.write('%u %s(%u)\n' % (round(x+2),round(y),vNum,verticeCSVWhitelist['label'][vNum],v.index)); + else: + f.write('%u\n' % (round(x),round(y),vNum)); + f.write('\n' % (x,y,vNum)); + #------------- + if (csvFile): + if (vNum>0): + fCSV.write(',') + fCSV.write('%f,%f,%0.1f' % (coords_2d.x,(1.0-coords_2d.y),visible)) + #------------- + vNum = vNum + 1 + + if (svgFile): + f.write('\n') + f.close() + #--------------------------------------------------------- + if (csvFile): + fCSV.write('\n') + fCSV.close() + #print("fID ",fID," number of vertices :",len(vertices)) + return True + + +def write_csv_2d_data_all_objects(baseDirectory="/home/ammar/",fID=0,csvFile=True,svgFile=True,verticeCSVWhitelistForAllObjects=dict()): + skinnedObjectName = bpy.context.scene.mnetTarget + for obj in bpy.data.objects[skinnedObjectName].children: + #print(skinnedObjectName," has a child ",obj.name) + write_vertex_csv_2d_data( + objName=obj.name, + baseDirectory=baseDirectory, + fID=fID, + csvFile=csvFile, + svgFile=svgFile, + verticeCSVWhitelistForAllObjects=verticeCSVWhitelistForAllObjects + ) + +def write_csv_3d_data_header(filename): + scene = bpy.context.scene + obj = scene.objects.get("newgirl.body") + f = open(filename, 'w') + vertices = obj.data.vertices + print("number of vertices :",len(vertices)) + for v in range(0,len(vertices)): + if (v>0): + f.write(',') + f.write('3DX_v%u,3DY_v%u,3DZ_v%u' % (v,v,v)) + f.write('\n') + f.close() + +def write_vertex_csv_3d_data(filename="/home/ammar/",fID=0): + return get_vertex_2d_projection(filename=filename,fID=fID) + import bpy_extras + from bpy_extras.object_utils import world_to_camera_view + #----------------------------------------------------------------- + scene = bpy.context.scene + camera = scene.objects.get("Camera") #bpy.context.scene.camera + obj = scene.objects.get("newgirl.body") + vertices = obj.data.vertices + #Apply modifiers + dg = bpy.context.evaluated_depsgraph_get() + obj = obj.evaluated_get(dg) + + mesh = obj.to_mesh(preserve_all_data_layers=True, depsgraph=dg) + #co = mesh.vertices[0].co + #co_final = obj.matrix_world @ co + + #mesh = obj.to_mesh(scene, True, 'PREVIEW') # apply modifiers with preview settings + mesh.transform(obj.matrix_world) # apply loc/rot/scale + vertices = mesh.vertices + #----------------------------------------------------------------- + """ + bpy.ops.export_scene.obj( + filepath="%s/blender_%04u.obj"%(filename,fID), + check_existing=False, + axis_forward='-Z', + axis_up='Y', + filter_glob="*.obj;*.mtl", + use_selection=False, + use_animation=False, + use_mesh_modifiers=True, + use_edges=True, + use_smooth_groups=False, + use_smooth_groups_bitflags=False, + use_normals=True, + use_uvs=True, + use_materials=True, + use_triangles=False, + use_nurbs=False, + use_vertex_groups=False, + use_blen_objects=True, + group_by_object=False, + group_by_material=False, + keep_vertex_order=False, + global_scale=1, + path_mode='AUTO' +)""" + #----------------------------------------------------------------- + csvFileName = '%s/blender.csv'%filename + if (fID==0): + write_csv_3d_data_header(csvFileName) + #----------------------------------------------------------------- + f = open(csvFileName,'a') + vNum = 0 + for v in vertices: + print("v :",v.co) + #----------------------------------------------------------------- + if (vNum!=0): + f.write(',') + f.write('%f,%f,%f' % (v.co.x,v.co.y,v.co.z)) + #----------------------------------------------------------------- + vNum = vNum + 1 + #----------------------------------------------------------------- + f.write('\n') + f.close() + print("number of vertices :",len(vertices)) + return True + + +# =================================================================================================================== +# =================================================================================================================== +# =================================================================================================================== +# =================================================================================================================== +def resolveCSVRowColumn(data,label,sampleID): + #--------------------------- + column = 0 + labelLowerCase = label.lower() + for columnLabel in data['label']: + if (columnLabel.lower()==labelLowerCase): + return float(data['body'][sampleID][column]) + column = column+1 + #--------------------------- + global csvResolutionErrors + csvResolutionErrors += 1 + if (csvResolutionErrors < 100): + print("Could not resolve ",label," sample ",sampleID) + elif (csvResolutionErrors == 100): + print("Could not resolve ",label," sample ",sampleID) + print("From now on will supress error output to speed up computation ") + elif (csvResolutionErrors % 30000 == 0): + print("Reminder : Could not resolve ",label," sample ",sampleID," surpressed ",csvResolutionErrors," errors.. ") + + return float(0.0) + +def convert_bytes(num): + """ + this function will convert bytes to MB.... GB... etc + """ + step_unit = 1000.0 #1024 bad the size + + for x in ['bytes', 'KB', 'MB', 'GB', 'TB']: + if num < step_unit: + return "%3.1f %s" % (num, x) + num /= step_unit + +def getNumberOfLines(filename): + print("Counting number of lines in file ",filename) + with open(filename) as f: + return sum(1 for line in f) + +def checkIfPathExists(filename): + import os + return os.path.exists(filename) + +def checkIfFileExists(filename): + import os + return os.path.isfile(filename) + +def readCSVFile(filename,memPercentage=1.0,useHalfFloats=0): + import os + import time + print("CSV file :",filename,"..\n") + + if (not checkIfFileExists(filename)): + print( bcolors.FAIL + "Input file "+filename+" does not exist, cannot read ground truth.." + bcolors.ENDC) + print("Current Directory was "+os.getcwd()) + return dict() + start = time.time() + + dtypeSelected=np.dtype(np.float32) + dtypeSelectedByteSize=int(dtypeSelected.itemsize) + if (useHalfFloats): + dtypeSelected=np.dtype(np.float16) + dtypeSelectedByteSize=int(dtypeSelected.itemsize) + + progress=0.0 + sampleNumber=0 + receivedHeader=False + inputNumberOfColumns=0 + outputNumberOfColumns=0 + + inputLabels=list() + + #------------------------------------------------------------------------------------------------- + numberOfSamplesInput=getNumberOfLines(filename)-1 + print(" Input file has ",numberOfSamplesInput," training samples\n") + #------------------------------------------------------------------------------------------------- + + + numberOfSamples = numberOfSamplesInput + numberOfSamplesLimit=int(numberOfSamples*memPercentage) + #------------------------------------------------------------------------------------------------- + if (memPercentage==0.0): + print("readCSVFile was asked to occupy 0 memory so this probably means we just want one record") + numberOfSamplesLimit=2 + if (memPercentage>1.0): + print("Memory Limit will be interpreted as a raw value..") + numberOfSamplesLimit=int(memPercentage) + #------------------------------------------------------------------------------------------------- + + + #--------------------------------- + thisInput = array.array('f') + #--------------------------------- + + fi = open(filename, "r") + readerIn = csv.reader( fi , delimiter =',', skipinitialspace=True) + for rowIn in readerIn: + #------------------------------------------------------ + if (not receivedHeader): #use header to get labels + #------------------------------------------------------ + inputNumberOfColumns=len(rowIn) + inputLabels = list(rowIn[i] for i in range(0,inputNumberOfColumns) ) + print("Number of Input elements : ",len(inputLabels)) + #------------------------------------------------------ + + if (memPercentage==0): + print("Will only return labels\n") + return {'label':inputLabels}; + + #--------------------------------- + # Allocate Lists + #--------------------------------- + for i in range(inputNumberOfColumns): + thisInput.append(0.0) + #--------------------------------- + + + #--------------------------------- + # Allocate Numpy Arrays + #--------------------------------- + inputSize=0 + startCompressed=0 + inputSize=inputNumberOfColumns + startCompressed=inputNumberOfColumns + + npInputBytesize=0+numberOfSamplesLimit * inputSize * dtypeSelectedByteSize + print(" Input file on disk has a shape of [",numberOfSamples,",",inputSize,"]") + print(" Input we will read has a shape of [",numberOfSamplesLimit,",",inputSize,"]") + print(" Input will occupy ",convert_bytes(npInputBytesize)," of RAM\n") + npInput = np.full([numberOfSamplesLimit,inputSize],fill_value=0,dtype=dtypeSelected,order='C') + #---------------------------------------------------------------------------------------------------------- + receivedHeader=True + #---------------------------------------------------------------------------------------------------------- + else: + #------------------------------------------- + # First convert our string INPUT to floats + #------------------------------------------- + for i in range(inputNumberOfColumns): + try: + thisInput[i]=float(rowIn[i]) + except: + thisInput[i]=0.0 + #------------------------------------------- + for num in range(0,inputNumberOfColumns): + npInput[sampleNumber,num]=float(thisInput[num]); + #------------------------------------------- + sampleNumber=sampleNumber+1 + + if (numberOfSamples>0): + progress=sampleNumber/numberOfSamplesLimit + + if (sampleNumber%1000==0) : + progressString = "%0.2f"%float(100*progress) + print("\rReading from disk (",sampleNumber,") - ",progressString," % \r", end="", flush=True) + + if (numberOfSamplesLimit<=sampleNumber): + print("\rStopping reading file to obey memory limit given by parameter --mem ",memPercentage,"\n") + break + #------------------------------------------- + fi.close() + del readerIn + gc.collect() + + + print("\n read, Samples: ",sampleNumber,", was expecting ",numberOfSamples," samples\n") + print(npInput.shape) + + totalNumberOfBytes=npInput.nbytes; + totalNumberOfGigaBytes=totalNumberOfBytes/1073741824; + print("GPU Size Occupied by data = ",totalNumberOfGigaBytes," GB \n") + + end = time.time() + print("Time elapsed : ",(end-start)/60," mins") + #--------------------------------------------------------------------- + return {'label':inputLabels, 'body':npInput }; + +# =================================================================================================================== +# =================================================================================================================== +# =================================================================================================================== +# =================================================================================================================== + + + +def retrieveSkinToBVHAssotiationDict(doFace=False): + r = dict() + if (doFace): + r["root"]="hip" + r["neck1"]="neck1" + r["head"]="head" + #r["__jaw"]="__jaw" + r["jaw"]="jaw" + #r["special04"]="special04" + #r["oris02"]="oris02" + r["oris01"]="oris01" #<-- + r["oris06.L"]="oris06.l" ##### + r["oris07.L"]="oris07.l" + r["oris06.R"]="oris06.r" ##### + r["oris07.R"]="oris07.r" + #r["tongue00"]="tongue00" + #r["tongue01"]="tongue01" + #r["tongue02"]="tongue02" + #r["tongue03"]="tongue03" + #r["__tongue04"]="__tongue04" + #r["tongue04"]="tongue04" + #r["tongue07.L"]="tongue07.l" + #r["tongue07.R"]="tongue07.r" + #r["tongue06.L"]="tongue06.l" + #r["tongue06.R"]="tongue06.r" + #r["tongue05.L"]="tongue05.l" + #r["tongue05.R"]="tongue05.r" + #r["__levator02.L"]="__levator02.l" + #r["levator02.L"]="levator02.l" + r["levator03.L"]="levator03.l" + #r["levator04.L"]="levator04.l" + #r["levator05.L"]="levator05.l" + #r["__levator02.R"]="__levator02.r" + #r["levator02.R"]="levator02.r" + r["levator03.R"]="levator03.r" + #r["levator04.R"]="levator04.r" + #r["levator05.R"]="levator05.r" + #r["__special01"]="__special01" + #r["special01"]="special01" + r["oris04.L"]="oris04.l" + r["oris03.L"]="oris03.l" + r["oris04.R"]="oris04.r" + r["oris03.R"]="oris03.r" + #r["oris06"]="oris06" + r["oris05"]="oris05" #<-- + #r["__special03"]="__special03" + #r["special03"]="special03" + #r["__levator06.L"]="__levator06.l" + r["levator06.L"]="levator06.l" + #r["__levator06.R"]="__levator06.r" + r["levator06.R"]="levator06.r" + #r["special06.L"]="special06.l" + #r["special05.L"]="special05.l" + r["eye.L"]="eye.l" + r["orbicularis03.L"]="orbicularis03.l" + r["orbicularis04.L"]="orbicularis04.l" + #r["special06.R"]="special06.r" + #r["special05.R"]="special05.r" + r["eye.R"]="eye.r" + r["orbicularis03.R"]="orbicularis03.r" + r["orbicularis04.R"]="orbicularis04.r" + #r["__temporalis01.L"]="__temporalis01.l" + #r["temporalis01.L"]="temporalis01.l" + #r["oculi02.L"]="oculi02.l" ## + r["oculi01.L"]="oculi01.l" + #r["__temporalis01.R"]="__temporalis01.r" + #r["temporalis01.R"]="temporalis01.r" + #r["oculi02.R"]="oculi02.r" ## + r["oculi01.R"]="oculi01.r" + #r["__temporalis02.L"]="__temporalis02.l" + #r["temporalis02.L"]="temporalis02.l" + #r["risorius02.L"]="risorius02.l" + #r["risorius03.L"]="risorius03.l" ## + #r["__temporalis02.R"]="__temporalis02.r" + #r["temporalis02.R"]="temporalis02.r" + #r["risorius02.R"]="risorius02.r" + #r["risorius03.R"]="risorius03.r" ## + return r + +def degToRad(degrees): + return degrees * math.pi / 180 + +def randomize_property(propName): + #prop = bpy.data.scenes[0][propName] + scene = bpy.data.scenes[0] + + prop = None + for p in scene.bl_rna.properties: + #print(p.name) + if (p.name == propName): + #print("FOUND ",propName) + prop=p + break + #else: + # print("`%s`!=`%s`" % (p.name,propName)) + + #print(type(prop)) + if isinstance(prop, bpy.types.FloatProperty): # and prop.has_min and prop.has_max: + # Property is a float with a defined range, use min and max attributes + min_value = prop.soft_min + max_value = prop.soft_max + random_value = random.uniform(min_value, max_value) + # Set the property to the random value + #print(propName,"randomize(%0.1f,%0.1f) = %0.2f "%(min_value,max_value,random_value)) + return random_value + else: + # Property is a different type, handle it differently + # (for example, generate a random value within a reasonable range for the property type) + print("Unable to use min/max for prop ",propName) + return 0.0 + +def dumpBVHFile(r,targetPath,frameID): + if (frameID==0): + f = open('%s/bvh_face_all.csv' % targetPath, 'w') + i=0 + for joint in r.keys(): + if (i>0): + f.write(',') + f.write(joint) + i=i+1 + f.write('\n') + f.close() + f = open('%s/bvh_face_all.csv' % targetPath, 'a') + i=0 + for joint in r.keys(): + if (i>0): + f.write(',') + f.write("%0.2f"%r[joint]) + i=i+1 + f.write('\n') + f.close() + +def setSkeletonRaw(jointName,z,x,y): + context = bpy.context + scene = context.scene + #------------------------------------------------------- + skinnedObjectName = bpy.context.scene.mnetSource + jointName = jointName.lower() + armature_obj = bpy.context.scene.objects.get(bpy.context.scene.mnetSource) + #skinnedObjectName = bpy.context.scene.mnetTarget + #print("skinnedObjectName",skinnedObjectName) + #------------------------------------------------------- + + + skinnedObject = scene.objects.get(skinnedObjectName) + if (skinnedObject is not None) : + # Get the joint object + armature = bpy.data.objects[skinnedObjectName] + bone = armature.pose.bones[jointName] + + # Set the rotation mode to ZXY + bone.rotation_mode = 'ZXY' + + # Set the rotation values + bone.rotation_euler = (degToRad(z),degToRad(x),degToRad(y)) + + #Animation set + #-------------------------------------------------------------------- + # Set the joint values for the current frame + armature_obj.pose.bones[jointName].rotation_euler = (degToRad(z),degToRad(x),degToRad(y)) + # Add a keyframe for the joint values + armature_obj.pose.bones[jointName].keyframe_insert(data_path="rotation_euler", index=-1) + + +def setSkeletonPositionRaw(jointName,x,y,z): + context = bpy.context + scene = context.scene + #------------------------------------------------------- + skinnedObjectName = bpy.context.scene.mnetSource + jointName = jointName.lower() + armature_obj = bpy.context.scene.objects.get(bpy.context.scene.mnetSource) + #skinnedObjectName = bpy.context.scene.mnetTarget + #print("skinnedObjectName",skinnedObjectName) + #------------------------------------------------------- + + skinnedObject = scene.objects.get(skinnedObjectName) + if (skinnedObject is not None) : + # Get the joint object + armature = bpy.data.objects[skinnedObjectName] + bone = armature.pose.bones[jointName] + + # Set the rotation mode to ZXY + #bone.rotation_mode = 'ZXY' + # Set the rotation values + #bone.rotation_euler = (degToRad(90.0),degToRad(0.0),degToRad(0.0)) + + #Animation set + #-------------------------------------------------------------------- + # Set the joint values for the current frame + armature_obj.pose.bones[jointName].location = (x,y,z) + # Add a keyframe for the joint values + armature_obj.pose.bones[jointName].keyframe_insert(data_path="location", index=-1) + + + + +class FaceBVHAnimationPanel(bpy.types.Panel): + """Creates a Panel in the Object properties window""" + bl_label = "Face BVH Animation Helper" + bl_idname = "OBJECT_PT_face_panel" + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "object" + + def draw(self, context): + context = bpy.context + scene = context.scene + layout = self.layout + + obj = context.object + + #layout = layout.split(factor=0.96, align=True) + #------------------------------------------------------------------ + #------------------------------------------------------------------ + row = layout.row() + row.label(text="Face BVH MocapNET Helper v%0.2f" % mnetPluginVersion, icon='WORLD_DATA') + #------------------------------------------------------------------ + row = layout.row() + row.label(text="BVH file to use as source: ") + row = layout.row() + row.prop_search(scene, "mnetSource", scene, "objects", icon='ARMATURE_DATA') + row = layout.row() + row.operator("face.face_op",text='Link BVH').action='LINKBVH' + #------------------------------------------------------------------ + row = layout.row() + row.label(text="Skinned Body to use as target: ") + row = layout.row() + row.prop_search(scene, "mnetTarget", scene, "objects", icon='OUTLINER_OB_ARMATURE') + #------------------------------------------------------------------ + row = layout.row() + row.label(text="Parts of armature to animate: ") + row = layout.row() + row.operator("face.face_op",text='Open Mouth').action='OPENMOUTH' + row.operator("face.face_op",text='Close Mouth').action='CLOSEMOUTH' + row = layout.row() + row.label(text="Positional Component : ") + row = layout.row() + row.operator("face.face_op",text='Open Eyes').action='OPENEYES' + row.operator("face.face_op",text='Close Eyes').action='CLOSEEYES' + + + row = layout.row() + row.label(text="Depth : ") + row = layout.row() + row.prop(scene, 'posX', slider=True) + row = layout.row() + row.prop(scene, 'posY', slider=True) + row = layout.row() + row.prop(scene, 'depth', slider=True) + + row = layout.row() + row.label(text="Neck : ") + row = layout.row() + row.prop(scene, 'neck1Z', slider=True) + row = layout.row() + row.prop(scene, 'neck1X', slider=True) + row = layout.row() + row.prop(scene, 'neck1Y', slider=True) + + + row = layout.row() + row.label(text="Eyes : ") + row = layout.row() + row.prop(scene, 'eyelidLUD', slider=True) + row = layout.row() + row.prop(scene, 'eyelidRUD', slider=True) + row = layout.row() + row.prop(scene, 'eyeLR', slider=True) + row = layout.row() + row.prop(scene, 'eyeUD', slider=True) + + + row = layout.row() + row.label(text="Nose : ") + row = layout.row() + row.prop(scene, 'noseLR', slider=True) + + + row = layout.row() + row.prop(scene, 'REyebrowInUD', slider=True) + row = layout.row() + row.prop(scene, 'LEyebrowInUD', slider=True) + + row = layout.row() + row.label(text="Mouth : ") + row = layout.row() + row.prop(scene, 'smileAD', slider=True) + row = layout.row() + row.prop(scene, 'mouthUD', slider=True) + row = layout.row() + row.prop(scene, 'mouthLR', slider=True) + row = layout.row() + row.prop(scene, 'mouthOC', slider=True) + row = layout.row() + row.prop(scene, 'moustacheLUD', slider=True) + row = layout.row() + row.prop(scene, 'moustacheRUD', slider=True) + + row = layout.row() + row.prop(scene, 'mouthTopL', slider=True) + row = layout.row() + row.prop(scene, 'mouthTopR', slider=True) + + row = layout.row() + row.prop(scene, 'mouthBotL', slider=True) + row = layout.row() + row.prop(scene, 'mouthBotR', slider=True) + + + row = layout.row() + row.label(text="Export controls : ") + row = layout.row() + row.prop(scene, 'dumpSVG') + row.prop(scene, 'zoomSVG') + row = layout.row() + row.prop(scene, 'dumpPNG') + row = layout.row() + row.prop(scene, 'dump2D') + row.prop(scene, 'dump3D') + row = layout.row() + row.prop(scene, 'dumpSpecificVertices') + + row = layout.row() + row.operator("face.face_op",text='Take Picture').action='PHOTO' + row = layout.row() + row.label(text="Path to store generated dataset : ") + row = layout.row() + row.prop_search(scene, "datasetPath", scene, "objects", icon='FILE_FOLDER') + row = layout.row() + row.prop(scene, 'randomFramesNumber', slider=True) + row = layout.row() + row.operator("face.face_op",text='Create Randomized Dataset').action='RANDOM' + + row = layout.row() + row.label(text="Path to load pre-generated dataset : ") + row = layout.row() + row.prop_search(scene, "readDatasetCSVPath", scene, "objects", icon='FILE_HIDDEN') + row = layout.row() + row.operator("face.face_op",text='Create Dataset from CSV file').action='LOADCSV' + row = layout.row() + row.operator("face.face_op",text='Just Render CSV Dataset').action='RENDERCSV' + + + + +class FaceBVHAnimation(bpy.types.Operator): + """Creates a Panel in the Object properties window""" + bl_label = "Face BVH Animation" + bl_idname = "face.face_op" + bl_description = 'MocapNET operation control' + bl_options = {'REGISTER', 'UNDO'} + + action: EnumProperty( + items=[ + ('LOADCSV', 'Load Pose Data From CSV File', 'Load Pose Data From CSV File'), + ('RENDERCSV', 'Render Pose Data From CSV File', 'Render Pose Data From CSV File'), + ('RANDOM', 'Create Randomized Data', 'Create Randomized Data'), + ('PHOTO', 'Take a Picture', 'Take a Picture'), + ('LINKBVH', 'Link BVH File to Face', 'Link BVH File to Face'), + ('OPENMOUTH', 'Link MocapNET to Skinned Model', 'Link MocapNET to Skinned Model'), + ('CLOSEMOUTH', 'Link MocapNET to Upper Body Only', 'Link MocapNET to Upper Body Only'), + ('OPENEYES', 'Link MocapNET to Face', 'Link MocapNET to Face'), + ('CLOSEEYES', 'Link MocapNET positional component', 'Link MocapNET positional component') + ] + ) + + @staticmethod + def add_cube(context): + bpy.ops.mesh.primitive_cube_add() + + @staticmethod + def add_sphere(context): + bpy.ops.mesh.primitive_uv_sphere_add() + + + @staticmethod + def cameraLightAction(context): + #bpy.context.scene.camera object and set its properties such as focal_length, sensor_width, and sensor_height. + bpy.context.scene.camera.location = 0.0,0.0,0.7 + bpy.context.scene.camera.rotation_mode = 'ZXY' + bpy.context.scene.camera.rotation_euler = degToRad(90),degToRad(0),degToRad(0) + #bpy.ops.object.lens_distort + + light = bpy.data.objects['Light'] + light.location.x = 0.0 + light.location.y = -5.0 + light.location.z = 1.0 + + @staticmethod + def takePicture(self,context): + print("takePicture called") + + targetPath = bpy.context.scene.datasetPath + + import os + if (checkIfPathExists(targetPath)): + os.system("rm %s/blender_face_dataset_*.jpg" % (targetPath)) + os.system("rm %s/blender_*_face_dataset_*.svg" % (targetPath)) + else: + print("Cannot take picture, given path %s does not exist" % (targetPath)) + return; + + bpy.context.scene.frame_set(0) # Always revert to first frame on dataset generation + #------------------------------------------------------------- + dumpSVG = bpy.context.scene.dumpSVG + dumpPNG = bpy.context.scene.dumpPNG + dump2D = bpy.context.scene.dump2D + dump3D = bpy.context.scene.dump3D + dumpSpecificVertices = bpy.context.scene.dumpSpecificVertices + #------------------------------------------------------------- + printSelectedVertex() + #------------------------------------------------------------- + csvVertexWhitelist=dict() + if(dumpSpecificVertices): + skinnedObjectName = bpy.context.scene.mnetTarget + for obj in bpy.data.objects[skinnedObjectName].children: + if (checkIfPathExists("%s/vertexWhitelist_%s.csv"%(targetPath,obj.name))): + csvVertexWhitelist[obj.name] = readCSVFile("%s/vertexWhitelist_%s.csv"%(targetPath,obj.name)) + else: + print("Could not find %s/vertexWhitelist_%s.csv "%(targetPath,obj.name)) + #------------------------------------------------------------- + if (dump2D or dumpSVG): + write_csv_2d_data_all_objects(baseDirectory=targetPath,fID=0,csvFile=dump2D,svgFile=dumpSVG,verticeCSVWhitelistForAllObjects=csvVertexWhitelist) + if(dump3D): + write_vertex_csv_3d_data(filename="/home/ammar/",fID=0) + #------------------------------------------------------------- + self.cameraLightAction(context=context) + #------------------------------------------------------------- + bpy.context.scene.render.image_settings.file_format='JPEG' + bpy.context.scene.render.filepath = "/home/ammar/test.jpg" + bpy.ops.render.render(write_still = True) + bpy.data.images['Render Result'].save_render + + @staticmethod + def generateRandomDataset(self,context,useCSV=False,useFFMPEG=False): + randomFramesNumber = bpy.context.scene.randomFramesNumber + print("generateRandomDataset called ",randomFramesNumber) + self.cameraLightAction(context=context) + + targetPath = bpy.context.scene.datasetPath + import time + startAt = time.time() + import os + if (checkIfPathExists(targetPath)): + os.system("rm %s/blender_face_dataset_*.jpg" % (targetPath)) + os.system("rm %s/blender_*_face_dataset_*.svg" % (targetPath)) + else: + print("Cannot generate random dataset, given path %s does not exist" % (targetPath)) + return; + + bpy.context.scene.frame_set(0) # Always revert to first frame on start of dataset generation + #--------------------------------------------------------------------------- + dumpSVG = bpy.context.scene.dumpSVG + dumpPNG = bpy.context.scene.dumpPNG + dump2D = bpy.context.scene.dump2D + dump3D = bpy.context.scene.dump3D + dumpSpecificVertices = bpy.context.scene.dumpSpecificVertices + #--------------------------------------------------------------------------- + csvVertexWhitelist=dict() + if(dumpSpecificVertices): + skinnedObjectName = bpy.context.scene.mnetTarget + for obj in bpy.data.objects[skinnedObjectName].children: + if (checkIfPathExists("%s/vertexWhitelist_%s.csv"%(targetPath,obj.name))): + csvVertexWhitelist[obj.name] = readCSVFile("%s/vertexWhitelist_%s.csv"%(targetPath,obj.name)) + else: + print("Could not find %s/vertexWhitelist_%s.csv "%(targetPath,obj.name)) + #print(csvVertexWhitelist) + #--------------------------------------------------------------------------- + wm = bpy.context.window_manager + #--------------------------------------------------------------------------- + armature_obj = bpy.context.scene.objects.get(bpy.context.scene.mnetSource) + target_obj = bpy.context.scene.objects.get(bpy.context.scene.mnetTarget) + #--------------------------------------------------------------------------- + + if armature_obj and target_obj: + armature_mod = target_obj.modifiers.new(name='Armature', type='ARMATURE') + #--------------------------------------------------------------------------- + if (useCSV): + #In this mode we will use the random poses found in the readDatasetCSVPath given by the user + csvFile = bpy.context.scene.readDatasetCSVPath + csvData = readCSVFile(csvFile) #,memPercentage=100 <- to test + randomFramesNumber = csvData["body"].shape[0] + print("Will now attempt to transmit ",randomFramesNumber," frames from ",csvFile) + wm.progress_begin(0,randomFramesNumber) + for fID in range(0,randomFramesNumber): + if (randomFramesNumber<10000) or (fID%1000==0): + wm.progress_update(fID) # Update mouse pointer progress in a conservative way to avoid X-Server error(?) + if (dumpPNG): + bpy.context.scene.frame_set(fID) # Set the current frame + # Set the joint values for the current frame + thisFaceConfig = self.retrieveFaceControls(self=self,context=context,csvData=csvData,fID=fID) + dumpBVHFile(thisFaceConfig,targetPath,fID) + if (dump2D or dumpSVG): + write_csv_2d_data_all_objects(baseDirectory=targetPath,fID=fID,csvFile=dump2D,svgFile=dumpSVG,verticeCSVWhitelistForAllObjects=csvVertexWhitelist) + if (dump3D): + write_vertex_csv_3d_data(filename="/home/ammar/",fID=0) + else: + wm.progress_begin(0,randomFramesNumber) + #In this more we will generate random poses + for fID in range(0,randomFramesNumber): + if (randomFramesNumber<10000) or (fID%1000==0): + wm.progress_update(fID) # Update mouse pointer progress in a conservative way to avoid X-Server error(?) + if (dumpPNG): + bpy.context.scene.frame_set(fID) # Set the current frame + # Set the joint values for the current frame + thisFaceConfig = self.retrieveFaceControls(self=self,context=context) + dumpBVHFile(thisFaceConfig,targetPath,fID) + if (dump2D or dumpSVG): + write_csv_2d_data_all_objects(baseDirectory=targetPath,fID=fID,csvFile=dump2D,svgFile=dumpSVG,verticeCSVWhitelistForAllObjects=csvVertexWhitelist) + if(dump3D): + write_vertex_csv_3d_data(filename="/home/ammar/",fID=0) + #--------------------------------------------------------------------------- + + if(dumpSpecificVertices): + combineCSVFiles("%s/2d_face_all.csv"%(targetPath),csvVertexWhitelist) + + #At this point we have added all new states to animation + #it has happened that after a lot of hours errors like #X Error of failed request: BadWindow (invalid Window parameter) Major opcode of failed request: 18 (X_ChangeProperty) + #might occur so let's save our blend file to make sure we can re-render if something goes wrong! + os.system("rm %s/faceRandomized.blend" % (targetPath)) + bpy.ops.wm.save_as_mainfile(filepath="%s/faceRandomized.blend" % (targetPath)) + + if (dumpPNG): + print("Will now attempt to render ",randomFramesNumber," frames ") + renderAsAnimation=True + #--------------------------------------------------------------------------- + if (renderAsAnimation): + wm.progress_end() + # Set the render engine and animation settings + #bpy.context.scene.render.engine = "CYCLES" + bpy.context.scene.render.image_settings.file_format='JPEG' + bpy.context.scene.render.filepath = "%s/blender_face_dataset_" % (targetPath) + bpy.context.scene.frame_start = 0 + bpy.context.scene.frame_end = randomFramesNumber-1 + + # Click the Render Animation button + bpy.ops.render.render('INVOKE_AREA',use_viewport = True, animation=True) + else: + #Then playback animation and save each frame as jpeg + for fID in range(0,randomFramesNumber): + bpy.context.scene.frame_set(fID) + wm.progress_update(fID) + bpy.context.view_layer.update() #function to update the view layer and trigger a redraw of the UI. + bpy.context.scene.render.image_settings.file_format='JPEG' + bpy.context.scene.render.filepath = "%s/blender_face_dataset_%04u.jpg" % (targetPath,fID) + bpy.ops.render.render(write_still = True) + bpy.data.images['Render Result'].save_render + if (fID%1000==0): + gc.collect() #Do garbage collection to help with memory leaks ? + wm.progress_end() + #--------------------------------------------------------------------------- + if (useFFMPEG): + print("Will attempt to execute : ") + print("ffmpeg -framerate 30 -i %s/blender_face_dataset_%%04d.jpg -s 1200x720 -y -r 30 -pix_fmt yuv420p -threads 8 %s/blender.mp4"% (targetPath,targetPath)) + os.system("ffmpeg -framerate 30 -i %s/blender_face_dataset_%%04d.jpg -s 1200x720 -y -r 30 -pix_fmt yuv420p -threads 8 %s/blender.mp4" % (targetPath,targetPath)) + #--------------------------------------------------------------------------- + endAt = time.time() + timeElapsed,timeUnit = timeDuration(startAt,endAt) + print("Time required to generate dataset was ",timeElapsed,timeUnit) + #--------------------------------------------------------------------------- + #ffmpeg -framerate 30 -i blender_face_dataset_%04d.jpg -s 1200x720 -y -r 30 -pix_fmt yuv420p -threads 8 livelastRun3DHiRes.mp4 + #ffmpeg -i /media/ammar/CVRL2/ammar/frames/ammarFaceFar.mp4-data/colorFrame_0_%05d.jpg -i /media/ammar/CVRL2/ammar/rendering/blender_face_dataset_%04d.jpg -filter_complex '[1:v]colorkey=0x464646:0.01:0.02[ckout];[0:v][ckout]overlay[out]' -map '[out]' output.mp4 + # or + #ffmpeg -i /media/ammar/games/ammarFaceFar.mp4-data/colorFrame_0_%05d.jpg -i /media/ammar/games/render/blender_face_dataset_%04d.jpg -filter_complex '[1:v]colorkey=0x464646:0.01:0.02[ckout];[0:v][ckout]overlay[out]' -map '[out]' output.mp4 + + @staticmethod + def setSkeleton(context,jointName,z,x,y): + setSkeletonRaw(jointName,z,x,y) + + @staticmethod + def retrieveConstantControls(self,context): + r = dict() + r["orbicularis03.R_Yrotation"]=172.0 + r["orbicularis04.R_Yrotation"]=172.0 + r["orbicularis03.L_Yrotation"]=-172.0 + r["orbicularis04.L_Yrotation"]=172.0 + r["levator06.L_Yrotation"]=-247.0 + r["levator06.R_Yrotation"]=247.0 + r["oris03.L_Xrotation"]=-40.0 + r["oris03.L_Yrotation"]=172.0 + r["oris07.L_Yrotation"]=172.0 + r["oris03.R_Xrotation"]=-40.0 + r["oris03.R_Yrotation"]=179.0 + r["oris07.R_Yrotation"]=172.0 + r["oris05_Xrotation"]=-35.0 + r["oris05_Yrotation"]=-176.0 + return r + + @staticmethod + def retrieveFaceControlsI(self,context): + scene = bpy.data.scenes[0] + #-------------------------------------------------------- + r = dict() + #-------------------------------------------------------- + r["hip_Xposition"] = randomize_property("Pos X") + r["hip_Yposition"] = randomize_property("Pos Y") + r["hip_Zposition"] = randomize_property("Depth") + #-------------------------------------------------------- + r["neck1_Zrotation"] = randomize_property("Neck Z") + r["neck1_Xrotation"] = randomize_property("Neck X") + r["neck1_Yrotation"] = randomize_property("Neck Y") + #-------------------------------------------------------- + r["eye.R_Zrotation"] = randomize_property("Eye Gaze L/R") + r["eye.R_Xrotation"] = randomize_property("Eye Gaze U/D") + r["eye.L_Zrotation"] = r["eye.R_Zrotation"] + r["eye.L_Xrotation"] = r["eye.R_Xrotation"] + #-------------------------------------------------------- + r["oculi01.R_Zrotation"] = randomize_property("R Eyebrow In U/D") + r["oculi01.L_Zrotation"] = randomize_property("L Eyebrow In U/D") + #-------------------------------------------------------- + r["orbicularis03.R_Xrotation"] = randomize_property("Eye Lid R U/D") + r["orbicularis04.R_Xrotation"] = -r["orbicularis03.R_Xrotation"] + r["orbicularis03.L_Xrotation"] = randomize_property("Eye Lid L U/D") + r["orbicularis04.L_Xrotation"] = -r["orbicularis03.L_Xrotation"] + blinkRand = random.uniform(0,1.0) + if (blinkRand<0.85): + r["orbicularis04.R_Xrotation"]=r["orbicularis04.L_Xrotation"] + #-------------------------------------------------------- + r["levator06.L_Xrotation"] = randomize_property("Nose L/R") + r["levator06.R_Xrotation"] = r["levator06.L_Xrotation"] + #-------------------------------------------------------- + r["levator03.L_Zrotation"] = randomize_property("Smile Active/Deactivated") + r["levator03.R_Zrotation"] = -r["levator03.L_Zrotation"] + #-------------------------------------------------------- + r["oris03.L_Zrotation"] = randomize_property("Mouth Top L") + r["oris07.L_Zrotation"] = min(randomize_property("Mouth Sides U/D"),0) + r["oris03.R_Zrotation"] = randomize_property("Mouth Top R") + r["oris07.R_Zrotation"] = r["oris07.L_Zrotation"] + #-------------------------------------------------------- + r["jaw_Xrotation"] = randomize_property("Mouth Open/Close") + r["jaw_Yrotation"] = randomize_property("Mouth L/R") + #-------------------------------------------------------- + r["oris04.L_Zrotation"] = randomize_property("Moustache L U/D") + r["oris04.R_Zrotation"] = -randomize_property("Moustache R U/D") + #-------------------------------------------------------- + r["oris06.L_Zrotation"] = randomize_property("Mouth Bot L") + r["oris06.R_Zrotation"] = -randomize_property("Mouth Bot R") + #-------------------------------------------------------- + return r + + @staticmethod + def retrieveFaceControlsFromCSV(self,context,csvData,fID): + scene = bpy.data.scenes[0] + #-------------------------------------------------------- + r = dict() + #-------------------------------------------------------- + r["hip_Xposition"] = resolveCSVRowColumn(csvData,"hip_Xposition",fID) + r["hip_Yposition"] = resolveCSVRowColumn(csvData,"hip_Yposition",fID) + r["hip_Zposition"] = resolveCSVRowColumn(csvData,"hip_Zposition",fID) + #-------------------------------------------------------- + r["neck1_Zrotation"] = resolveCSVRowColumn(csvData,"neck1_Zrotation",fID) + r["neck1_Xrotation"] = resolveCSVRowColumn(csvData,"neck1_Xrotation",fID) + r["neck1_Yrotation"] = resolveCSVRowColumn(csvData,"neck1_Yrotation",fID) + #-------------------------------------------------------- + r["eye.R_Zrotation"] = resolveCSVRowColumn(csvData,"eye.R_Zrotation",fID) + r["eye.R_Xrotation"] = resolveCSVRowColumn(csvData,"eye.R_Xrotation",fID) + r["eye.L_Zrotation"] = r["eye.R_Zrotation"] + r["eye.L_Xrotation"] = r["eye.R_Xrotation"] + #-------------------------------------------------------- + r["oculi01.R_Zrotation"] = resolveCSVRowColumn(csvData,"oculi01.R_Zrotation",fID) + r["oculi01.L_Zrotation"] = resolveCSVRowColumn(csvData,"oculi01.L_Zrotation",fID) + #-------------------------------------------------------- + r["orbicularis03.R_Xrotation"] = resolveCSVRowColumn(csvData,"orbicularis03.R_Xrotation",fID) + r["orbicularis04.R_Xrotation"] = -r["orbicularis03.R_Xrotation"] + r["orbicularis03.L_Xrotation"] = resolveCSVRowColumn(csvData,"orbicularis03.L_Xrotation",fID) + r["orbicularis04.L_Xrotation"] = -r["orbicularis03.L_Xrotation"] + #-------------------------------------------------------- + r["levator06.L_Xrotation"] = resolveCSVRowColumn(csvData,"levator06.L_Xrotation",fID) + r["levator06.R_Xrotation"] = r["levator06.L_Xrotation"] + #-------------------------------------------------------- + r["levator03.L_Zrotation"] = resolveCSVRowColumn(csvData,"levator03.L_Zrotation",fID) + r["levator03.R_Zrotation"] = -r["levator03.L_Zrotation"] + #-------------------------------------------------------- + r["oris03.L_Zrotation"] = resolveCSVRowColumn(csvData,"oris03.L_Zrotation",fID) + r["oris07.L_Zrotation"] = resolveCSVRowColumn(csvData,"oris07.L_Zrotation",fID) + r["oris03.R_Zrotation"] = resolveCSVRowColumn(csvData,"oris03.R_Zrotation",fID) + r["oris07.R_Zrotation"] = resolveCSVRowColumn(csvData,"oris07.R_Zrotation",fID) + #-------------------------------------------------------- + r["jaw_Xrotation"] = resolveCSVRowColumn(csvData,"jaw_Xrotation",fID) + r["jaw_Yrotation"] = resolveCSVRowColumn(csvData,"jaw_Yrotation",fID) + #-------------------------------------------------------- + r["oris04.L_Zrotation"] = resolveCSVRowColumn(csvData,"oris04.L_Zrotation",fID) + r["oris04.R_Zrotation"] = resolveCSVRowColumn(csvData,"oris04.R_Zrotation",fID) + #-------------------------------------------------------- + r["oris06.L_Zrotation"] = resolveCSVRowColumn(csvData,"oris06.L_Zrotation",fID) + r["oris06.R_Zrotation"] = resolveCSVRowColumn(csvData,"oris06.R_Zrotation",fID) + #-------------------------------------------------------- + return r + + + @staticmethod + def retrieveFaceControls(self,context,csvData=dict(),fID=0): + scene = bpy.data.scenes[0] + doIt=True + r = dict() + if (doIt): + #----------------------------------------------------------------- + if ("body" in csvData): + r.update(self.retrieveFaceControlsFromCSV(self=self,context=context,csvData=csvData,fID=fID)) + else: + r.update(self.retrieveFaceControlsI(self=self,context=context)) + #----------------------------------------------------------------- + scene['posX'] = r["hip_Xposition"] + scene['posY'] = r["hip_Yposition"] + scene['depth'] = r["hip_Zposition"] + bpy.context.scene.depth = scene['depth'] + #----------------------------------------------------------------- + scene['neck1Z'] = r["neck1_Zrotation"] + scene['neck1X'] = r["neck1_Xrotation"] + scene['neck1Y'] = r["neck1_Yrotation"] + bpy.context.scene.neckZ = scene['neck1Z'] + bpy.context.scene.neckX = scene['neck1X'] + bpy.context.scene.neckY = scene['neck1Y'] + #----------------------------------------------------------------- + scene['eyeLR'] = r["eye.R_Zrotation"] + scene['eyeUD'] = r["eye.R_Xrotation"] + bpy.context.scene.eyeLR = scene['eyeLR'] + bpy.context.scene.eyeUD = scene['eyeUD'] + eyeLR = bpy.context.scene.eyeLR + eyeUD = bpy.context.scene.eyeUD + #----------------------------------------------------------------- + scene['REyebrowInUD'] = -r["oculi01.R_Zrotation"] + scene['LEyebrowInUD'] = r["oculi01.L_Zrotation"] + bpy.context.scene.REyebrowInUD = scene['REyebrowInUD'] + bpy.context.scene.LEyebrowInUD = scene['LEyebrowInUD'] + REyebrowInUD = bpy.context.scene.REyebrowInUD + LEyebrowInUD = bpy.context.scene.LEyebrowInUD + #----------------------------------------------------------------- + scene['eyelidLUD'] = r["orbicularis04.L_Xrotation"] + scene['eyelidRUD'] = r["orbicularis04.R_Xrotation"] + bpy.context.scene.eyelidLUD = scene['eyelidLUD'] + bpy.context.scene.eyelidRUD = scene['eyelidRUD'] + eyelidRUD = bpy.context.scene.eyelidRUD + eyelidLUD = bpy.context.scene.eyelidLUD + #----------------------------------------------------------------- + scene['noseLR'] = r["levator06.L_Xrotation"] + bpy.context.scene.noseLR = scene['noseLR'] + noseLR = bpy.context.scene.noseLR + #----------------------------------------------------------------- + scene['smileAD'] = r["levator03.L_Zrotation"] + bpy.context.scene.smileAD = scene['smileAD'] + smileAD = bpy.context.scene.smileAD + #----------------------------------------------------------------- + scene['mouthTopL'] = r["oris03.L_Zrotation"] + scene['mouthTopR'] = -r["oris03.R_Zrotation"] + bpy.context.scene.mouthTopL = scene['mouthTopL'] + bpy.context.scene.mouthTopR = scene['mouthTopR'] + mouthTopL = bpy.context.scene.mouthTopL + mouthTopR = bpy.context.scene.mouthTopR + scene['mouthUD'] = r["oris07.L_Zrotation"] + bpy.context.scene.mouthUD = scene['mouthUD'] + mouthUD = bpy.context.scene.mouthUD + #----------------------------------------------------------------- + scene['mouthLR'] = r["jaw_Yrotation"] + bpy.context.scene.mouthLR = scene['mouthLR'] + mouthLR = bpy.context.scene.mouthLR + scene['mouthOC'] = r["jaw_Xrotation"] + bpy.context.scene.mouthOC = scene['mouthOC'] + mouthOC = bpy.context.scene.mouthOC + #----------------------------------------------------------------- + scene['moustacheLUD'] = r["oris04.L_Zrotation"] + scene['moustacheRUD'] = -r["oris04.R_Zrotation"] + bpy.context.scene.moustacheLUD = scene['moustacheLUD'] + bpy.context.scene.moustacheRUD = scene['moustacheRUD'] + moustacheLUD = bpy.context.scene.moustacheLUD + moustacheRUD = bpy.context.scene.moustacheRUD + #----------------------------------------------------------------- + scene['mouthBotL'] = r["oris06.L_Zrotation"] + scene['mouthBotR'] = -r["oris06.R_Zrotation"] + bpy.context.scene.mouthBotL = scene['mouthBotL'] + bpy.context.scene.mouthBotR = scene['mouthBotR'] + mouthBotL = bpy.context.scene.mouthBotL + mouthBotR = bpy.context.scene.mouthBotR + #----------------------------------------------------------------- + r.update(self.retrieveConstantControls(self=self,context=context)) + #----------------------------------------------------------------- + self.neckUpdate(self=self,context=context) + self.eyeGazeUpdate(self=self,context=context) + self.noseUpdate(self=self,context=context) + self.mouthUpdate(self=self,context=context) + return r + + + @staticmethod + def eyeGazeUpdate(self, context): + eyeLR = bpy.context.scene.eyeLR + eyeUD = bpy.context.scene.eyeUD #x #y #z + FaceBVHAnimation.setSkeleton(context,"eye.R",eyeUD,0,eyeLR) + FaceBVHAnimation.setSkeleton(context,"eye.L",eyeUD,0,eyeLR) + #----------------------------------------------------------------- + REyebrowInUD = bpy.context.scene.REyebrowInUD + LEyebrowInUD = bpy.context.scene.LEyebrowInUD #x #y #z + FaceBVHAnimation.setSkeleton(context,"oculi01.R",0,0,-REyebrowInUD) + FaceBVHAnimation.setSkeleton(context,"oculi01.L",0,0,LEyebrowInUD) + #----------------------------------------------------------------- + eyelidRUD = bpy.context.scene.eyelidRUD + eyelidLUD = bpy.context.scene.eyelidLUD #x #y #z + FaceBVHAnimation.setSkeleton(context,"orbicularis03.R",-eyelidRUD,172,0) + FaceBVHAnimation.setSkeleton(context,"orbicularis04.R",eyelidRUD,172,0) + FaceBVHAnimation.setSkeleton(context,"orbicularis03.L",-eyelidLUD,-172,0) + FaceBVHAnimation.setSkeleton(context,"orbicularis04.L",eyelidLUD,172,0) + + @staticmethod + def noseUpdate(self, context): + noseLR = bpy.context.scene.noseLR #x #y #z + FaceBVHAnimation.setSkeleton(context,"levator06.L",noseLR,-247,0) + FaceBVHAnimation.setSkeleton(context,"levator06.R",noseLR,+247,0) + + @staticmethod + def neckUpdate(self, context): + neckZ = bpy.context.scene.neckZ #x #y #z + neckX = bpy.context.scene.neckX #x #y #z + neckY = bpy.context.scene.neckY #x #y #z + FaceBVHAnimation.setSkeleton(context,"neck1",neckX,neckY,neckZ) + target_obj = bpy.context.scene.objects.get(bpy.context.scene.mnetTarget) + target_obj.location.x = 0.0 + target_obj.location.y = 0.0 + bpy.context.scene.depth + target_obj.location.z = 0.0 + target_obj.rotation_mode = 'ZXY' + target_obj.rotation_euler = (degToRad(0.0),degToRad(0.0),degToRad(0.0)) + setSkeletonPositionRaw("hip",bpy.context.scene.posX,bpy.context.scene.posY,bpy.context.scene.depth) + + @staticmethod + def mouthUpdate(self, context): + mouthTopL = bpy.context.scene.mouthTopL + mouthTopR = bpy.context.scene.mouthTopR + mouthUD = bpy.context.scene.mouthUD #x #y #z + FaceBVHAnimation.setSkeleton(context,"oris03.L",-40,172,mouthUD+mouthTopL) + FaceBVHAnimation.setSkeleton(context,"oris07.L",0,172,max(mouthUD,0)) + FaceBVHAnimation.setSkeleton(context,"oris03.R",-40,179,-mouthUD+mouthTopR) + FaceBVHAnimation.setSkeleton(context,"oris07.R",0,172,min(mouthUD,0)) + #----------------------------------------------------------------- + mouthOC = bpy.context.scene.mouthOC + mouthLR = bpy.context.scene.mouthLR + FaceBVHAnimation.setSkeleton(context,"jaw",mouthOC,mouthLR,0) + #----------------------------------------------------------------- + FaceBVHAnimation.setSkeleton(context,"oris05",-35,-176,0) + #----------------------------------------------------------------- + moustacheLUD = bpy.context.scene.moustacheLUD + moustacheRUD = bpy.context.scene.moustacheRUD #x #y #z + FaceBVHAnimation.setSkeleton(context,"oris04.L",0,0,moustacheLUD) + FaceBVHAnimation.setSkeleton(context,"oris04.R",0,0,moustacheRUD) + #----------------------------------------------------------------- + mouthBotL = bpy.context.scene.mouthBotL + mouthBotR = bpy.context.scene.mouthBotR #x #y #z + FaceBVHAnimation.setSkeleton(context,"oris06.L",0,0,mouthBotL) + FaceBVHAnimation.setSkeleton(context,"oris06.R",0,0,mouthBotR) + #----------------------------------------------------------------- + smileAD = bpy.context.scene.smileAD #x #y #z + FaceBVHAnimation.setSkeleton(context,"levator03.L",0,0,smileAD) + FaceBVHAnimation.setSkeleton(context,"levator03.R",0,0,-smileAD) + #----------------------------------------------------------------- + + @staticmethod + def copyFaceConstraints(context,doPosition=False,doRotation=True,doReverse=False): + FaceBVHAnimation.cameraLightAction(context=context) + context = bpy.context + scene = context.scene + #------------------------------------------------------- + associations = retrieveSkinToBVHAssotiationDict(doFace=True) + #------------------------------------------------------- + bvhObjectName = bpy.context.scene.mnetSource + skinnedObjectName = bpy.context.scene.mnetTarget + print("bvhObjectName",bvhObjectName) + print("skinnedObjectName",skinnedObjectName) + #------------------------------------------------------- + skinnedObject = scene.objects.get(skinnedObjectName) + bvhObject = scene.objects.get(bvhObjectName) + if (skinnedObject is not None) and (bvhObject is not None): + for skinnedBoneName in associations: + #------------------------------------------------ + bvhBoneName = associations[skinnedBoneName] + #------------------------------------------------ + skinnedBone = skinnedObject.pose.bones.get(skinnedBoneName) + bvhBone = bvhObject.pose.bones.get(bvhBoneName) + + if (doReverse): + # give it a copy rotation constraint + if (skinnedBone is not None) and (bvhBone is not None): + if (len(skinnedBone.constraints)>0): + for c in bvhBone.constraints: + bvhBone.constraints.remove(c) # Remove constraint + if (skinnedBoneName=="root") and (doPosition): + crc = bvhBone.constraints.new('COPY_LOCATION') + crc.target = skinnedObject + crc.subtarget = skinnedBoneName + elif (skinnedBoneName!="root") and (doRotation): + crc = bvhBone.constraints.new('COPY_ROTATION') + crc.target = skinnedObject + crc.subtarget = skinnedBoneName + else: + # give it a copy rotation constraint + if (skinnedBone is not None) and (bvhBone is not None): + if (len(skinnedBone.constraints)>0): + for c in skinnedBone.constraints: + skinnedBone.constraints.remove(c) # Remove constraint + if (skinnedBoneName=="root") and (doPosition): + crc = skinnedBone.constraints.new('COPY_LOCATION') + crc.target = bvhObject + crc.subtarget = bvhBoneName + elif (skinnedBoneName!="root") and (doRotation): + crc = skinnedBone.constraints.new('COPY_ROTATION') + crc.target = bvhObject + crc.subtarget = bvhBoneName + #------------------------------------------------------- + + def execute(self, context): + if self.action == 'LOADCSV': + self.generateRandomDataset(self=self,context=context,useCSV=True) + elif self.action == 'RENDERCSV': + dumpSVG = bpy.context.scene.dumpSVG; bpy.context.scene.dumpSVG = False + dumpPNG = bpy.context.scene.dumpPNG; bpy.context.scene.dumpPNG = True + dump2D = bpy.context.scene.dump2D; bpy.context.scene.dump2D = False + dump3D = bpy.context.scene.dump3D; bpy.context.scene.dump3D = False + dumpSpecificVertices = bpy.context.scene.dumpSpecificVertices; bpy.context.scene.dumpSpecificVertices = False + self.generateRandomDataset(self=self,context=context,useCSV=True,useFFMPEG=True) + elif self.action == 'RANDOM': + self.generateRandomDataset(self=self,context=context) + elif self.action == 'LINKBVH': + self.copyFaceConstraints(context=context,doPosition=True) + self.eyeGazeUpdate(self=self,context=context) + self.noseUpdate(self=self,context=context) + self.neckUpdate(self=self,context=context) + self.mouthUpdate(self=self,context=context) + elif self.action == 'REVERSELINKBVH': + self.copyFaceConstraints(context=context,doReverse=True) + elif self.action == 'PHOTO': + self.takePicture(self=self,context=context) + elif self.action == 'OPENMOUTH': + self.setSkeleton(context,"jaw",20,0,0) + elif self.action == 'CLOSEMOUTH': + self.setSkeleton(context,"jaw",0,0,0) + elif self.action == 'OPENEYES': + self.setSkeleton(context,"orbicularis03.R",0,172,0) + self.setSkeleton(context,"orbicularis04.R",0,172,0) + self.setSkeleton(context,"orbicularis03.L",0,172,0) + self.setSkeleton(context,"orbicularis04.L",0,172,0) + self.eyeGazeUpdate(self=self,context=context) + elif self.action == 'CLOSEEYES': + self.setSkeleton(context,"orbicularis03.R",-15,149,0) + self.setSkeleton(context,"orbicularis04.R",15,172,0) + self.setSkeleton(context,"orbicularis03.L",-15,193,0) + self.setSkeleton(context,"orbicularis04.L",15,172,0) + return {'FINISHED'} + +classes = (FaceBVHAnimationPanel,FaceBVHAnimation) + +def register(): + for cls in classes: + bpy.utils.register_class(cls) + + bpy.types.Scene.datasetPath = bpy.props.StringProperty(name="Dataset Path", default="~/", subtype="DIR_PATH") + bpy.types.Scene.readDatasetCSVPath = bpy.props.StringProperty(name="Dataset Path", default="~/", subtype="FILE_PATH") + bpy.types.Scene.mnetSource = bpy.props.StringProperty(name="Source BVH", default="Select Armature Object") + bpy.types.Scene.mnetTarget = bpy.props.StringProperty(name="Target Obj", default="Select Skinned Object") + #-------------------------------------------------------------------------------------------------------------------------------------------- + bpy.types.Scene.zoomSVG = bpy.props.BoolProperty(name="Zoom SVG", default=False) + bpy.types.Scene.dumpSVG = bpy.props.BoolProperty(name="Dump SVG", default=False) + bpy.types.Scene.dumpPNG = bpy.props.BoolProperty(name="Dump PNG", default=True) + bpy.types.Scene.dumpSpecificVertices = bpy.props.BoolProperty(name="Only Dump 2D/3D for Specific Vertices", default=True) + bpy.types.Scene.dump2D = bpy.props.BoolProperty(name="Dump 2D CSV", default=True) + bpy.types.Scene.dump3D = bpy.props.BoolProperty(name="Dump 3D CSV", default=False) + #-------------------------------------------------------------------------------------------------------------------------------------------- + bpy.types.Scene.neckZ = bpy.props.FloatProperty(name="Neck Z", default=0.0, min=-20.0, max=20.0, update=FaceBVHAnimation.neckUpdate) + bpy.types.Scene.neckX = bpy.props.FloatProperty(name="Neck X", default=0.0, min=-20.0, max=20.0, update=FaceBVHAnimation.neckUpdate) + bpy.types.Scene.neckY = bpy.props.FloatProperty(name="Neck Y", default=0.0, min=-30.0, max=30.0, update=FaceBVHAnimation.neckUpdate) + #-------------------------------------------------------------------------------------------------------------------------------------------- + bpy.types.Scene.posX = bpy.props.FloatProperty(name="Pos X", default=0.0, min=-0.24, max=0.24, update=FaceBVHAnimation.neckUpdate) + bpy.types.Scene.posY = bpy.props.FloatProperty(name="Pos Y", default=0.0, min=-0.1, max=0.1, update=FaceBVHAnimation.neckUpdate) + bpy.types.Scene.depth = bpy.props.FloatProperty(name="Depth", default=-1.0, min=-2.4, max=-1.0, update=FaceBVHAnimation.neckUpdate) + #-------------------------------------------------------------------------------------------------------------------------------------------- + bpy.types.Scene.eyeLR = bpy.props.FloatProperty(name="Eye Gaze L/R", default=0.0, min=-45.36, max=45.36, update=FaceBVHAnimation.eyeGazeUpdate) + bpy.types.Scene.eyeUD = bpy.props.FloatProperty(name="Eye Gaze U/D", default=0.0, min=-10.0, max=16.0, update=FaceBVHAnimation.eyeGazeUpdate) + bpy.types.Scene.eyelidLUD = bpy.props.FloatProperty(name="Eye Lid L U/D", default=0.0, min=-15.0, max=15.0, update=FaceBVHAnimation.eyeGazeUpdate) + bpy.types.Scene.eyelidRUD = bpy.props.FloatProperty(name="Eye Lid R U/D", default=0.0, min=-15.0, max=15.0, update=FaceBVHAnimation.eyeGazeUpdate) + #-------------------------------------------------------------------------------------------------------------------------------------------- + bpy.types.Scene.noseLR = bpy.props.FloatProperty(name="Nose L/R", default=0.0, min=-9.0, max=9.0, update=FaceBVHAnimation.noseUpdate) + #-------------------------------------------------------------------------------------------------------------------------------------------- + bpy.types.Scene.mouthUD = bpy.props.FloatProperty(name="Mouth Sides U/D", default=0.0, min=-30.0, max=0.0, update=FaceBVHAnimation.mouthUpdate) + bpy.types.Scene.mouthLR = bpy.props.FloatProperty(name="Mouth L/R", default=0.0, min=-15.0, max=15.0, update=FaceBVHAnimation.mouthUpdate) + bpy.types.Scene.mouthOC = bpy.props.FloatProperty(name="Mouth Open/Close", default=0.0, min=-4.0, max=20.0, update=FaceBVHAnimation.mouthUpdate) + bpy.types.Scene.moustacheLUD = bpy.props.FloatProperty(name="Moustache L U/D", default=0.0, min=-30.0, max=0.0, update=FaceBVHAnimation.mouthUpdate) + bpy.types.Scene.moustacheRUD = bpy.props.FloatProperty(name="Moustache R U/D", default=0.0, min=0.0, max=30.0, update=FaceBVHAnimation.mouthUpdate) + bpy.types.Scene.mouthTopL = bpy.props.FloatProperty(name="Mouth Top L", default=0.0, min=-30.0, max=30.0, update=FaceBVHAnimation.mouthUpdate) + bpy.types.Scene.mouthTopR = bpy.props.FloatProperty(name="Mouth Top R", default=0.0, min=-30.0, max=30.0, update=FaceBVHAnimation.mouthUpdate) + bpy.types.Scene.mouthBotL = bpy.props.FloatProperty(name="Mouth Bot L", default=0.0, min=-30.0, max=30.0, update=FaceBVHAnimation.mouthUpdate) + bpy.types.Scene.mouthBotR = bpy.props.FloatProperty(name="Mouth Bot R", default=0.0, min=-30.0, max=30.0, update=FaceBVHAnimation.mouthUpdate) + #-------------------------------------------------------------------------------------------------------------------------------------------- + bpy.types.Scene.smileAD = bpy.props.FloatProperty(name="Smile Active/Deactivated", default=0.0, min=-8.0, max=9.0, update=FaceBVHAnimation.mouthUpdate) + #-------------------------------------------------------------------------------------------------------------------------------------------- + bpy.types.Scene.randomFramesNumber = bpy.props.IntProperty(name="Random Frames", default=0, min=0, max=200000) #, update=FaceBVHAnimation.generateRandomDataset + #-------------------------------------------------------------------------------------------------------------------------------------------- + bpy.types.Scene.REyebrowInUD = bpy.props.FloatProperty(name="R Eyebrow In U/D", default=0.0, min=-20.0, max=20.0, update=FaceBVHAnimation.eyeGazeUpdate) + bpy.types.Scene.LEyebrowInUD = bpy.props.FloatProperty(name="L Eyebrow In U/D", default=0.0, min=-20.0, max=20.0, update=FaceBVHAnimation.eyeGazeUpdate) + +def unregister(): + for cls in classes: + bpy.utils.unregister_class(cls) + + del bpy.types.Scene.datasetPath + del bpy.types.Scene.readDatasetCSVPath + del bpy.types.Scene.mnetSource + del bpy.types.Scene.mnetTarget + #-------------------------------------------------------------------------------------------------------------------------------------------- + del bpy.types.Scene.zoomSVG + del bpy.types.Scene.dumpSVG + del bpy.types.Scene.dumpPNG + del bpy.types.Scene.dumpSpecificVertices + del bpy.types.Scene.dump2D + del bpy.types.Scene.dump3D + #-------------------------------------------------------------------------------------------------------------------------------------------- + del bpy.types.Scene.neck1Z + del bpy.types.Scene.neck1X + del bpy.types.Scene.neck1Y + #-------------------------------------------------------------------------------------------------------------------------------------------- + del bpy.types.Scene.posX + del bpy.types.Scene.posY + del bpy.types.Scene.depth + del bpy.types.Scene.eyeLR + del bpy.types.Scene.eyeUD + del bpy.types.Scene.eyelidLUD + del bpy.types.Scene.eyelidRUD + #-------------------------------------------------------------------------------------------------------------------------------------------- + del bpy.types.Scene.noseLR + #-------------------------------------------------------------------------------------------------------------------------------------------- + del bpy.types.Scene.mouthUD + del bpy.types.Scene.mouthLR + del bpy.types.Scene.mouthOC + del bpy.types.Scene.moustacheLUD + del bpy.types.Scene.moustacheRUD + del bpy.types.Scene.mouthTopL + del bpy.types.Scene.mouthTopR + del bpy.types.Scene.mouthBotL + del bpy.types.Scene.mouthBotR + #-------------------------------------------------------------------------------------------------------------------------------------------- + del bpy.types.Scene.smileAD + #-------------------------------------------------------------------------------------------------------------------------------------------- + del bpy.types.Scene.randomFramesNumber + #-------------------------------------------------------------------------------------------------------------------------------------------- + del bpy.types.Scene.REyebrowInUD + del bpy.types.Scene.LEyebrowInUD + +if __name__ == "__main__": + register() + # Get a list of all add-ons that are currently activated + activated_addons = [addon.module for addon in bpy.context.preferences.addons if addon] + + haveMPFB = False + # Print the name of each activated add-on + for addon in activated_addons: + print(addon) + if (addon=="mpfb"): + haveMPFB = True + print("We already have MPFB!") + + if (not haveMPFB): + import os + print("Receiving a fresh copy of MPFB!") + current_directory = os.getcwd() + print("Working from ",current_directory," directory") + os.system("wget http://download.tuxfamily.org/makehuman/plugins/mpfb2-latest.zip") + print(" Downloaded mpfb2-latest.zip and will now auto-install it for your convinience ") + bpy.ops.preferences.addon_install(filepath='%s/mpfb2-latest.zip' % os.getcwd()) + bpy.ops.preferences.addon_enable(module='mpfb') + bpy.ops.wm.save_userpref() + + # Get the path to the user preferences file + prefs_file = bpy.utils.user_resource('CONFIG') #bpy.context.preferences.filepath + + # Get the directory that contains the preferences file + prefs_dir = os.path.dirname(prefs_file) + + print(" Also installing the makehuman system assets!") + os.system("cd %s/mpfb/data && wget http://files.makehumancommunity.org/asset_packs/makehuman_system_assets/makehuman_system_assets_cc0.zip && unzip makehuman_system_assets_cc0.zip && rm makehuman_system_assets_cc0.zip" % prefs_dir) diff --git a/src/python/blender/faceWhiteLists/label.tag b/src/python/blender/faceWhiteLists/label.tag new file mode 100644 index 0000000..9271a15 --- /dev/null +++ b/src/python/blender/faceWhiteLists/label.tag @@ -0,0 +1 @@ +face diff --git a/src/python/blender/faceWhiteLists/vertexWhitelist_newgirl.body.csv b/src/python/blender/faceWhiteLists/vertexWhitelist_newgirl.body.csv new file mode 100644 index 0000000..315c54e --- /dev/null +++ b/src/python/blender/faceWhiteLists/vertexWhitelist_newgirl.body.csv @@ -0,0 +1,2 @@ +head_reye_2,head_reye_5,head_leye_1,head_leye_4,head_nostrills_2,head_chin,head_outmouth_0,head_outmouth_3,head_outmouth_6,head_outmouth_9 +4865,36,11483,6820,297,5171,402,466,7162,492 diff --git a/src/python/blender/faceWhiteLists/vertexWhitelist_newgirl.eyebrow002.csv b/src/python/blender/faceWhiteLists/vertexWhitelist_newgirl.eyebrow002.csv new file mode 100644 index 0000000..38de554 --- /dev/null +++ b/src/python/blender/faceWhiteLists/vertexWhitelist_newgirl.eyebrow002.csv @@ -0,0 +1,2 @@ +head_reyebrow_2,head_reyebrow_4,head_leyebrow_2,head_leyebrow_4 +102,121,40,59 diff --git a/src/python/blender/faceWhiteLists/vertexWhitelist_newgirl.high-poly.csv b/src/python/blender/faceWhiteLists/vertexWhitelist_newgirl.high-poly.csv new file mode 100644 index 0000000..9a5e314 --- /dev/null +++ b/src/python/blender/faceWhiteLists/vertexWhitelist_newgirl.high-poly.csv @@ -0,0 +1,2 @@ +head_reye,head_leye +1023,485 diff --git a/src/python/blender/fullfaceWhiteLists/label.tag b/src/python/blender/fullfaceWhiteLists/label.tag new file mode 100644 index 0000000..9271a15 --- /dev/null +++ b/src/python/blender/fullfaceWhiteLists/label.tag @@ -0,0 +1 @@ +face diff --git a/src/python/blender/fullfaceWhiteLists/vertexWhitelist_newgirl.body.csv b/src/python/blender/fullfaceWhiteLists/vertexWhitelist_newgirl.body.csv new file mode 100644 index 0000000..16396c3 --- /dev/null +++ b/src/python/blender/fullfaceWhiteLists/vertexWhitelist_newgirl.body.csv @@ -0,0 +1,2 @@ +head_reye_0,head_reye_1,head_reye_2,head_reye_3,head_reye_4,head_reye_5,head_leye_0,head_leye_1,head_leye_2,head_leye_3,head_leye_4,head_leye_5,head_nosebone_0,head_nosebone_1,head_nosebone_2,head_nosebone_3,head_nostrills_0,head_nostrills_1,head_nostrills_2,head_nostrills_3,head_nostrills_4,head_rchin_0,head_rchin_1,head_rchin_2,head_rchin_3,head_rchin_4,head_rchin_5,head_rchin_6,head_rchin_7,head_chin,head_lchin_7,head_lchin_6,head_lchin_5,head_lchin_4,head_lchin_3,head_lchin_2,head_lchin_1,head_lchin_0,head_outmouth_0,head_outmouth_1,head_outmouth_2,head_outmouth_3,head_outmouth_4,head_outmouth_5,head_outmouth_6,head_outmouth_7,head_outmouth_8,head_outmouth_9,head_outmouth_10,head_outmouth_11,head_inmouth_0,head_inmouth_1,head_inmouth_2,head_inmouth_3,head_inmouth_4,head_inmouth_5,head_inmouth_6,head_inmouth_7 +4854,4849,4865,67,47,36,6851,11483,11467,11472,6820,13368,136,135,5063,5134,5095,295,297,7062,11710,256,5181,5176,5153,5299,5227,5172,5166,5171,11779,11835,11903,11904,11767,11929,11796,7025,402,448,460,466,7219,7208,7162,7233,7245,492,490,478,432,704,468,7429,7192,7279,494,534 diff --git a/src/python/blender/fullfaceWhiteLists/vertexWhitelist_newgirl.eyebrow002.csv b/src/python/blender/fullfaceWhiteLists/vertexWhitelist_newgirl.eyebrow002.csv new file mode 100644 index 0000000..cbc1cb2 --- /dev/null +++ b/src/python/blender/fullfaceWhiteLists/vertexWhitelist_newgirl.eyebrow002.csv @@ -0,0 +1,2 @@ +head_reyebrow_0,head_reyebrow_1,head_reyebrow_2,head_reyebrow_3,head_reyebrow_4,head_leyebrow_4,head_leyebrow_3,head_leyebrow_2,head_leyebrow_1,head_leyebrow_0 +96,93,102,111,121,59,49,40,31,57 diff --git a/src/python/blender/fullfaceWhiteLists/vertexWhitelist_newgirl.high-poly.csv b/src/python/blender/fullfaceWhiteLists/vertexWhitelist_newgirl.high-poly.csv new file mode 100644 index 0000000..9a5e314 --- /dev/null +++ b/src/python/blender/fullfaceWhiteLists/vertexWhitelist_newgirl.high-poly.csv @@ -0,0 +1,2 @@ +head_reye,head_leye +1023,485 diff --git a/src/python/blender/headerWithHeadAndOneMotion.bvh b/src/python/blender/headerWithHeadAndOneMotion.bvh new file mode 100644 index 0000000..5009ccf --- /dev/null +++ b/src/python/blender/headerWithHeadAndOneMotion.bvh @@ -0,0 +1,1022 @@ +HIERARCHY +ROOT hip +{ + OFFSET 0 0 0 + CHANNELS 6 Xposition Yposition Zposition Zrotation Yrotation Xrotation + JOINT abdomen + { + OFFSET 0 20.6881 -0.73152 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT chest + { + OFFSET 0 11.7043 -0.48768 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT neck + { + OFFSET 0 22.1894 -2.19456 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT neck1 + { + OFFSET 0.000000 5.364170 1.574630 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT head + { + OFFSET 0.000000 5.364141 1.574630 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT __jaw + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT jaw + { + OFFSET 0.000000 -13.499860 2.500710 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT special04 + { + OFFSET -0.000000 -6.835370 4.375500 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oris02 + { + OFFSET 0.000000 1.711150 2.820850 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oris01 + { + OFFSET -0.000000 0.972390 0.845650 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.000000 1.162291 0.607091 + } + } + } + JOINT oris06.l + { + OFFSET 0.000000 1.711150 2.820850 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oris07.l + { + OFFSET 1.168850 0.445180 0.506110 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.450611 1.195178 0.204519 + } + } + } + JOINT oris06.r + { + OFFSET 0.000000 1.711150 2.820850 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oris07.r + { + OFFSET -1.168850 0.445180 0.506110 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.450611 1.195173 0.204519 + } + } + } + } + JOINT tongue00 + { + OFFSET -0.000000 -6.835370 4.375500 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT tongue01 + { + OFFSET 0.000000 3.973650 -3.762340 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT tongue02 + { + OFFSET 0.000000 0.429760 2.924710 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT tongue03 + { + OFFSET 0.000000 0.018530 2.059010 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT __tongue04 + { + OFFSET 0.000000 -0.440240 0.838860 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT tongue04 + { + OFFSET 0.000000 0.000000 0.000000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.000000 -0.440230 0.838860 + } + } + } + JOINT tongue07.l + { + OFFSET 0.000000 -0.440240 0.838860 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 1.160923 -0.331531 0.018227 + } + } + JOINT tongue07.r + { + OFFSET 0.000000 -0.440240 0.838860 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -1.160922 -0.331531 0.018227 + } + } + } + JOINT tongue06.l + { + OFFSET 0.000000 0.018530 2.059010 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 1.644752 -0.526075 -0.203281 + } + } + JOINT tongue06.r + { + OFFSET 0.000000 0.018530 2.059010 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -1.644752 -0.526075 -0.203282 + } + } + } + JOINT tongue05.l + { + OFFSET 0.000000 0.429760 2.924710 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 1.971028 -0.388618 0.239206 + } + } + JOINT tongue05.r + { + OFFSET 0.000000 0.429760 2.924710 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -1.971028 -0.388618 0.239205 + } + } + } + } + } + } + JOINT __levator02.l + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator02.l + { + OFFSET 0.313580 -11.321120 11.599360 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator03.l + { + OFFSET 1.681690 -1.563730 -1.357570 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator04.l + { + OFFSET 0.504730 -1.676760 -0.058160 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator05.l + { + OFFSET 0.145440 -1.643170 -0.225470 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.310116 -0.760198 -0.121474 + } + } + } + } + } + } + JOINT __levator02.r + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator02.r + { + OFFSET -0.313580 -11.321120 11.599360 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator03.r + { + OFFSET -1.681690 -1.563740 -1.357570 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator04.r + { + OFFSET -0.504730 -1.676750 -0.058160 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator05.r + { + OFFSET -0.145440 -1.643170 -0.225470 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.310116 -0.760193 -0.121474 + } + } + } + } + } + } + JOINT __special01 + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT special01 + { + OFFSET -0.000000 -14.026930 -5.716970 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oris04.l + { + OFFSET -0.000000 -0.492640 17.312620 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oris03.l + { + OFFSET 1.215520 -0.627430 -0.393050 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.288776 -0.560899 -0.149645 + } + } + } + JOINT oris04.r + { + OFFSET -0.000000 -0.492640 17.312620 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oris03.r + { + OFFSET -1.215520 -0.627440 -0.393050 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.288776 -0.560894 -0.149645 + } + } + } + JOINT oris06 + { + OFFSET -0.000000 -0.492640 17.312620 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oris05 + { + OFFSET -0.000000 -0.486000 0.000950 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.000000 -0.630493 0.197635 + } + } + } + } + } + JOINT __special03 + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT special03 + { + OFFSET 0.000000 -13.499860 2.500710 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT __levator06.l + { + OFFSET -0.000000 1.035800 10.090229 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator06.l + { + OFFSET 0.522240 -0.615720 0.045900 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 1.107505 -0.131898 -2.142227 + } + } + } + JOINT __levator06.r + { + OFFSET -0.000000 1.035800 10.090229 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT levator06.r + { + OFFSET -0.522240 -0.615730 0.045900 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -1.107506 -0.131893 -2.142227 + } + } + } + } + } + JOINT special06.l + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT special05.l + { + OFFSET 2.108890 0.153870 5.595070 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT eye.l + { + OFFSET 0.857170 -10.254801 2.414670 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.111609 0.006490 4.051424 + } + } + JOINT orbicularis03.l + { + OFFSET 0.857170 -10.254801 2.414670 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.097789 1.146431 3.788029 + } + } + JOINT orbicularis04.l + { + OFFSET 0.857170 -10.254801 2.414670 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.113609 -1.130505 3.863064 + } + } + } + } + JOINT special06.r + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT special05.r + { + OFFSET -2.108890 0.153870 5.595070 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT eye.r + { + OFFSET -0.857170 -10.254801 2.414670 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.111609 0.006490 4.051424 + } + } + JOINT orbicularis03.r + { + OFFSET -0.857170 -10.254801 2.414670 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.097789 1.146431 3.788029 + } + } + JOINT orbicularis04.r + { + OFFSET -0.857170 -10.254801 2.414670 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.113609 -1.130505 3.863064 + } + } + } + } + JOINT __temporalis01.l + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT temporalis01.l + { + OFFSET 6.332510 -9.444281 6.595120 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oculi02.l + { + OFFSET -0.804920 0.053010 1.621140 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oculi01.l + { + OFFSET -2.161570 1.690970 2.142660 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -1.740622 0.281157 0.647323 + } + } + } + } + } + JOINT __temporalis01.r + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT temporalis01.r + { + OFFSET -6.332510 -9.444281 6.595120 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oculi02.r + { + OFFSET 0.804920 0.053010 1.621140 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT oculi01.r + { + OFFSET 2.161570 1.690970 2.142660 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 1.740622 0.281157 0.647323 + } + } + } + } + } + JOINT __temporalis02.l + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT temporalis02.l + { + OFFSET 6.377600 -11.680510 6.235180 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT risorius02.l + { + OFFSET -0.814250 0.451130 1.721730 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT risorius03.l + { + OFFSET -0.649710 -2.514660 0.612550 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.479556 -1.760402 -1.642659 + } + } + } + } + } + JOINT __temporalis02.r + { + OFFSET 0.000000 13.604700 -0.502080 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT temporalis02.r + { + OFFSET -6.377600 -11.680510 6.235180 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT risorius02.r + { + OFFSET 0.814250 0.451130 1.721730 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT risorius03.r + { + OFFSET 0.649710 -2.514660 0.612550 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.479556 -1.760402 -1.642659 + } + } + } + } + } + } + } + } + JOINT rCollar + { + OFFSET -2.68224 19.2634 -4.8768 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT rShldr + { + OFFSET -8.77824 -1.95073 1.46304 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT rForeArm + { + OFFSET -28.1742 -1.7115 0.48768 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT rHand + { + + OFFSET -21.049400 0.002190 -0.634230 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT metacarpal1.r + { + OFFSET -2.815680 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger2-1.r + { + OFFSET -6.292930 0.272380 2.520090 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger2-2.r + { + OFFSET -2.310530 -0.320530 -0.060510 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger2-3.r + { + OFFSET -2.051030 -0.295400 -0.164890 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -2.376838 -0.681367 -0.183877 + } + } + } + } + } + JOINT metacarpal2.r + { + OFFSET -2.815680 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger3-1.r + { + OFFSET -6.313640 0.626130 0.318530 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger3-2.r + { + OFFSET -3.015730 -0.589480 -0.088540 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger3-3.r + { + OFFSET -2.482120 -0.426280 0.076670 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -2.344174 -0.731969 0.003260 + } + } + } + } + } + JOINT __metacarpal3.r + { + OFFSET -2.815680 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT metacarpal3.r + { + OFFSET -0.606080 -0.162120 -1.874870 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger4-1.r + { + OFFSET -5.355730 0.702040 0.402510 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger4-2.r + { + OFFSET -2.643900 -0.485530 -0.117520 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger4-3.r + { + OFFSET -2.215850 -0.353160 0.066220 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -2.350273 -0.621223 -0.046375 + } + } + } + } + } + } + JOINT __metacarpal4.r + { + OFFSET -2.815680 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT metacarpal4.r + { + OFFSET -0.606080 -0.162120 -1.874870 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger5-1.r + { + OFFSET -4.761700 0.175470 -1.109590 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger5-2.r + { + OFFSET -1.916360 -0.173360 -0.146170 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger5-3.r + { + OFFSET -1.411290 -0.108670 -0.020110 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -1.799226 -0.102363 -0.078601 + } + } + } + } + } + } + JOINT __rthumb + { + OFFSET -2.815680 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT rthumb + { + OFFSET -0.283040 -0.142720 1.950690 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger1-2.r + { + OFFSET -0.915590 -2.152150 1.546760 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger1-3.r + { + OFFSET -3.213140 -0.470060 0.247480 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -2.521224 -0.161543 -0.511272 + } + } + } + } + } + } + } + } + } + JOINT lCollar + { + OFFSET 2.68224 19.2634 -4.8768 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT lShldr + { + OFFSET 8.77824 -1.95073 1.46304 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT lForeArm + { + OFFSET 28.1742 -1.7115 0.48768 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT lHand + { + + OFFSET 21.049408 0.002200 -0.634230 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT metacarpal1.l + { + OFFSET 2.815670 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger2-1.l + { + OFFSET 6.292930 0.272390 2.520090 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger2-2.l + { + OFFSET 2.310530 -0.320520 -0.060510 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger2-3.l + { + OFFSET 2.051030 -0.295400 -0.164880 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 2.376823 -0.681367 -0.183876 + } + } + } + } + } + JOINT metacarpal2.l + { + OFFSET 2.815670 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger3-1.l + { + OFFSET 6.313640 0.626120 0.318530 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger3-2.l + { + OFFSET 3.015730 -0.589470 -0.088540 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger3-3.l + { + OFFSET 2.482120 -0.426270 0.076670 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 2.344170 -0.731978 0.003260 + } + } + } + } + } + JOINT __metacarpal3.l + { + OFFSET 2.815670 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT metacarpal3.l + { + OFFSET 0.606080 -0.162120 -1.874870 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger4-1.l + { + OFFSET 5.355730 0.702050 0.402510 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger4-2.l + { + OFFSET 2.643900 -0.485530 -0.117510 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger4-3.l + { + OFFSET 2.215840 -0.353150 0.066210 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 2.350273 -0.621228 -0.046377 + } + } + } + } + } + } + JOINT __metacarpal4.l + { + OFFSET 2.815670 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT metacarpal4.l + { + OFFSET 0.606080 -0.162120 -1.874870 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger5-1.l + { + OFFSET 4.761700 0.175480 -1.109600 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger5-2.l + { + OFFSET 1.916350 -0.173360 -0.146170 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger5-3.l + { + OFFSET 1.411290 -0.108670 -0.020110 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 1.799216 -0.102372 -0.078600 + } + } + } + } + } + } + JOINT __lthumb + { + OFFSET 2.815670 -0.279180 0.531660 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT lthumb + { + OFFSET 0.283040 -0.142710 1.950690 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger1-2.l + { + OFFSET 0.915930 -2.151960 1.546820 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT finger1-3.l + { + OFFSET 3.213210 -0.469680 0.247300 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 2.521210 -0.161290 -0.511422 + } + } + } + } + } + } + } + } + } + } + } + JOINT rButtock + { + OFFSET -8.77824 4.35084 1.2192 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT rThigh + { + OFFSET 0 -1.70687 -2.19456 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT rShin + { + OFFSET 0 -36.8199 0.73152 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT rFoot + { + + OFFSET 0.73152 -45.1104 -5.12064 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe1-1.R + { + OFFSET 2.454000 -4.050002 13.194999 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe1-2.R + { + OFFSET -0.214000 -0.646000 2.427000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.401900 -0.827789 2.725930 + } + } + } + JOINT toe2-1.R + { + OFFSET 0.177000 -4.299998 13.329000 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe2-2.R + { + OFFSET -0.177000 -0.323000 2.039000 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe2-3.R + { + OFFSET -0.067000 -0.440998 1.248000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.042990 -0.647306 1.660872 + } + } + } + } + JOINT toe3-1.R + { + OFFSET -1.396000 -4.461999 13.078999 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe3-2.R + { + OFFSET -0.161000 -0.247002 1.809000 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe3-3.R + { + OFFSET -0.033000 -0.441999 1.202000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.032040 -0.433550 1.271800 + } + } + } + } + JOINT toe4-1.R + { + OFFSET -2.888001 -4.480000 12.376999 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe4-2.R + { + OFFSET -0.160000 -0.331998 1.491001 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe4-3.R + { + OFFSET 0.035999 -0.251002 1.138999 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.088911 -0.568814 0.969530 + } + } + } + } + JOINT toe5-1.R + { + OFFSET -4.257999 -4.467001 11.711999 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe5-2.R + { + OFFSET -0.046000 -0.265999 0.982000 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe5-3.R + { + OFFSET 0.086999 -0.372000 0.791000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.044329 -0.555482 1.085780 + } + } + } + } + } + } + } + } + JOINT lButtock + { + OFFSET 8.77824 4.35084 1.2192 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT lThigh + { + OFFSET 0 -1.70687 -2.19456 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT lShin + { + OFFSET 0 -36.8199 0.73152 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT lFoot + { + + OFFSET -0.73152 -45.1104 -5.12064 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe1-1.L + { + OFFSET -2.454000 -4.050002 13.194999 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe1-2.L + { + OFFSET 0.214000 -0.646000 2.427000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.401900 -0.827789 2.725930 + } + } + } + JOINT toe2-1.L + { + OFFSET -0.177000 -4.299998 13.329000 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe2-2.L + { + OFFSET 0.177000 -0.323000 2.039000 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe2-3.L + { + OFFSET 0.067000 -0.440998 1.248000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.042990 -0.647306 1.660872 + } + } + } + } + JOINT toe3-1.L + { + OFFSET 1.396000 -4.461999 13.078999 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe3-2.L + { + OFFSET 0.161000 -0.247002 1.809000 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe3-3.L + { + OFFSET 0.033000 -0.441999 1.202000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET -0.032040 -0.433550 1.271800 + } + } + } + } + JOINT toe4-1.L + { + OFFSET 2.888001 -4.480000 12.376999 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe4-2.L + { + OFFSET 0.160000 -0.331998 1.491001 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe4-3.L + { + OFFSET -0.035999 -0.251002 1.138999 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.088911 -0.568814 0.969530 + } + } + } + } + JOINT toe5-1.L + { + OFFSET 4.257999 -4.467001 11.711999 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe5-2.L + { + OFFSET 0.046000 -0.265999 0.982000 + CHANNELS 3 Zrotation Xrotation Yrotation + JOINT toe5-3.L + { + OFFSET -0.086999 -0.372000 0.791000 + CHANNELS 3 Zrotation Xrotation Yrotation + End Site + { + OFFSET 0.044329 -0.555482 1.085780 + } + } + } + } + } + } + } + } +} +MOTION +Frames: 1 +Frame Time: 0.04 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 diff --git a/src/python/blender/mouthWhiteLists/label.tag b/src/python/blender/mouthWhiteLists/label.tag new file mode 100644 index 0000000..bbdb241 --- /dev/null +++ b/src/python/blender/mouthWhiteLists/label.tag @@ -0,0 +1 @@ +mouth diff --git a/src/python/blender/mouthWhiteLists/vertexWhitelist_newgirl.body.csv b/src/python/blender/mouthWhiteLists/vertexWhitelist_newgirl.body.csv new file mode 100644 index 0000000..f1e57fe --- /dev/null +++ b/src/python/blender/mouthWhiteLists/vertexWhitelist_newgirl.body.csv @@ -0,0 +1,2 @@ +head_nostrills_2,head_chin,head_outmouth_0,head_outmouth_1,head_outmouth_2,head_outmouth_3,head_outmouth_4,head_outmouth_5,head_outmouth_6,head_outmouth_7,head_outmouth_8,head_outmouth_9,head_outmouth_10,head_outmouth_11,head_inmouth_0,head_inmouth_1,head_inmouth_2,head_inmouth_3,head_inmouth_4,head_inmouth_5,head_inmouth_6,head_inmouth_7 +297,5171,402,448,460,466,7219,7208,7162,7233,7245,492,490,478,432,704,468,7429,7192,7279,494,534 diff --git a/src/python/blender/reyeWhiteLists/label.tag b/src/python/blender/reyeWhiteLists/label.tag new file mode 100644 index 0000000..45dea56 --- /dev/null +++ b/src/python/blender/reyeWhiteLists/label.tag @@ -0,0 +1 @@ +reye diff --git a/src/python/blender/reyeWhiteLists/vertexWhitelist_newgirl.body.csv b/src/python/blender/reyeWhiteLists/vertexWhitelist_newgirl.body.csv new file mode 100644 index 0000000..c4f6a6e --- /dev/null +++ b/src/python/blender/reyeWhiteLists/vertexWhitelist_newgirl.body.csv @@ -0,0 +1,2 @@ +head_reye_0,head_reye_1,head_reye_2,head_reye_3,head_reye_4,head_reye_5,head_nostrills_2,head_rchin_0,head_chin +4854,4849,4865,67,47,36,297,256,5171 diff --git a/src/python/blender/reyeWhiteLists/vertexWhitelist_newgirl.eyebrow002.csv b/src/python/blender/reyeWhiteLists/vertexWhitelist_newgirl.eyebrow002.csv new file mode 100644 index 0000000..3991b22 --- /dev/null +++ b/src/python/blender/reyeWhiteLists/vertexWhitelist_newgirl.eyebrow002.csv @@ -0,0 +1,2 @@ +head_reyebrow_0,head_reyebrow_1,head_reyebrow_2,head_reyebrow_3,head_reyebrow_4 +96,93,102,111,121 diff --git a/src/python/blender/reyeWhiteLists/vertexWhitelist_newgirl.high-poly.csv b/src/python/blender/reyeWhiteLists/vertexWhitelist_newgirl.high-poly.csv new file mode 100644 index 0000000..7454ec8 --- /dev/null +++ b/src/python/blender/reyeWhiteLists/vertexWhitelist_newgirl.high-poly.csv @@ -0,0 +1,2 @@ +head_reye +764 From dde02edfab9cf6d4277484ed399f0fd17f7a3803 Mon Sep 17 00:00:00 2001 From: Ammar Qammaz Date: Sat, 9 Sep 2023 01:29:12 +0300 Subject: [PATCH 006/154] update MNET4 branch.. --- README.md | 6 + initialize.ipynb | 4 + src/python/mnet4/MocapNET.py | 35 ++++- src/python/mnet4/MocapNETONNX.py | 3 +- src/python/mnet4/MocapNETTFLite.py | 3 +- src/python/mnet4/MocapNETTensorflow.py | 3 +- src/python/mnet4/MocapNETVisualization.py | 106 ++++++++++++- src/python/mnet4/csvNET.py | 79 ++++++++-- src/python/mnet4/espStream.py | 0 src/python/mnet4/evaluateMocapNET.py | 9 ++ src/python/mnet4/getModelFromDatabase.py | 53 ++++++- .../mnet4/mediapipeHolisticWebcamMocapNET.py | 17 ++- src/python/mnet4/plotCSV.py | 15 +- src/python/mnet4/readCSV.py | 5 +- .../mnet4/sobolRandomDatasetGenerator.py | 144 ++++++++++-------- src/python/mnet4/tools.py | 84 ++++++++++ 16 files changed, 458 insertions(+), 108 deletions(-) create mode 100644 initialize.ipynb mode change 100644 => 100755 src/python/mnet4/csvNET.py mode change 100644 => 100755 src/python/mnet4/espStream.py mode change 100644 => 100755 src/python/mnet4/sobolRandomDatasetGenerator.py diff --git a/README.md b/README.md index f5e255b..d3ef3e7 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,12 @@ It will also be compatible with Raspberry Pi 4 and use Tensorflow /Tf-Lite / ONN This branch is still missing a lot of things so you can safely ignore it for now..! +Click here for one click setup : +[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/FORTH-ModelBasedTracker/MocapNET/blob/mnet4/initialize.ipynb) + + + + ## License ------------------------------------------------------------------ This library is provided under the [FORTH license](https://github.com/FORTH-ModelBasedTracker/MocapNET/blob/master/license.txt) diff --git a/initialize.ipynb b/initialize.ipynb new file mode 100644 index 0000000..916eda7 --- /dev/null +++ b/initialize.ipynb @@ -0,0 +1,4 @@ +import os + +os.chdir("src/python/mnet4") +os.system("setup.sh") diff --git a/src/python/mnet4/MocapNET.py b/src/python/mnet4/MocapNET.py index 9a07220..82b1a9e 100755 --- a/src/python/mnet4/MocapNET.py +++ b/src/python/mnet4/MocapNET.py @@ -19,6 +19,8 @@ from principleComponentAnalysis import PCA #------------------------------------------------------------------------------------------- +MOCAPNET_VERSION="4.0" + #------------------------------------------------------------------------------------------- #------------------------------------------------------------------------------------------- #------------------------------------------------------------------------------------------- @@ -52,6 +54,11 @@ def checkIfAnyListedElementsExistsInString(theList,theString): return True return False #------------------------------------------------------------------------------------------- +def flipHorizontalInput(inputList): + for k in inputList.keys(): + if ("2dx_" in k): + inputList[k]=1.0-inputList[k] + return inputList #------------------------------------------------------------------------------------------- #------------------------------------------------------------------------------------------- def getSymmetricLEyeOutputs(): @@ -259,6 +266,7 @@ def __init__(self, self.leftToRightNames = symmetricNames self.mirroringName = mirroringName self.outputOperationsNeeded = outputOperationsNeeded + self.serial = mirroredModel.serial self.outputBVH = dict() self.outputBVHMinima = dict() self.outputBVHMaxima = dict() @@ -380,8 +388,8 @@ def __init__(self, disableSmoothingCode = 0, doPerformanceProfiling = False, doHCDPostProcessing = 1, - hcdLearningRate = 0.1, - hcdEpochs = 20, + hcdLearningRate = 0.01, + hcdEpochs = 30, hcdIterations = 15, langevinDynamics = 0.0, bvhScale = 1.0, @@ -511,10 +519,13 @@ def __init__(self, self.output3D = dict() self.perfHistorySize = 30 + #------------------------------------------------------------------------------- self.history_hz_NN = [] self.hz_NN = 0.0 self.history_hz_HCD = [] self.hz_HCD = 0.0 + self.history_hz_Vis = [] + self.hz_Vis = 0.0 #------------------------------------------------------------------------------- @@ -522,6 +533,9 @@ def __init__(self, print("Caching networks : ") self.test() print(bcolors.OKGREEN,"MocapNET ready for use! ",bcolors.ENDC) + #------------------------------------------------------------------------------- + from tools import checkVersion + checkVersion(MOCAPNET_VERSION) def recordBVH(self,val:bool): self.record=val @@ -552,6 +566,16 @@ def getModelParameters(self): total = total + self.ensemble[k].getModelParameters() return total + def getEnsembleSerials(self): + description = "" + from datetime import datetime, date, time, timezone + #description = datetime.now().strftime("%Y-%m-%d %H:%M:%S ") + description = datetime.now().strftime("%Y-%m-%d ") + for k in self.ensemble.keys(): + description = description + k + ":" + self.ensemble[k].serial + " " + return description + + def test(self): #------------------------------------------- for k in self.ensemble.keys(): @@ -749,7 +773,8 @@ def predict3DJoints(self,input2D :dict,runNN:bool=True,runHCD:bool=True): else: rawBVHPrediction = self.previousPrediction - + + print("Predictions from ensemble keys : ",rawBVHPrediction.keys()) # Deal with 3D Mode #-------------------------------------------------------------------- @@ -882,8 +907,8 @@ def easyMocapNETConstructor( engine="onnx", doProfiling=False, doHCDPostProcessing=1, - hcdLearningRate = 0.1, - hcdEpochs = 20, + hcdLearningRate = 0.01, + hcdEpochs = 30, hcdIterations = 15, multiThreaded=False, bvhScale=1.0, diff --git a/src/python/mnet4/MocapNETONNX.py b/src/python/mnet4/MocapNETONNX.py index 80ffd09..4aac75b 100755 --- a/src/python/mnet4/MocapNETONNX.py +++ b/src/python/mnet4/MocapNETONNX.py @@ -32,7 +32,7 @@ from readCSV import parseConfiguration,parseConfigurationInputJointMap,transformNetworkInput,initializeDecompositionForExecutionEngine,readGroundTruthFile,readCSVFile,parseOutputNormalization from NSDM import NSDMLabels,createNSDMUsingRules,inputIsEnoughToCreateNSDM,performNSRMAlignment from EDM import EDMLabels,createEDMUsingRules -from tools import bcolors,checkIfFileExists,readListFromFile,convertListToLowerCase,secondsToHz,capitalizeCoordinateTags,getEntryIndexInList +from tools import bcolors,checkIfFileExists,readListFromFile,convertListToLowerCase,secondsToHz,capitalizeCoordinateTags,getEntryIndexInList,parseSerialNumberFromSummary #------------------------------------------------------------------------------------------- from BVH.bvhConverter import BVH #------------------------------------------------------------------------------------------- @@ -83,6 +83,7 @@ def __init__(self, self.inputs = convertListToLowerCase(readListFromFile(self.modelDirectory+"/neuralNetworkJoints.list")) self.outputs = convertListToLowerCase(readListFromFile(self.modelDirectory+"/neuralNetworkOutputs.list")) self.configuration = parseConfigurationInputJointMap(self.configuration,self.inputs) + self.serial = parseSerialNumberFromSummary(self.modelDirectory+"/summary.html") #------------------------------------------------------------------------------- self.inputReadyForTF = np.empty([2, 1]) self.NSRM = np.empty([2, 2]) diff --git a/src/python/mnet4/MocapNETTFLite.py b/src/python/mnet4/MocapNETTFLite.py index 8f81551..17a5318 100755 --- a/src/python/mnet4/MocapNETTFLite.py +++ b/src/python/mnet4/MocapNETTFLite.py @@ -30,7 +30,7 @@ from readCSV import parseConfiguration,parseConfigurationInputJointMap,transformNetworkInput,initializeDecompositionForExecutionEngine,readGroundTruthFile,readCSVFile,parseOutputNormalization from NSDM import NSDMLabels,createNSDMUsingRules,inputIsEnoughToCreateNSDM,performNSRMAlignment from EDM import EDMLabels,createEDMUsingRules -from tools import bcolors,checkIfFileExists,readListFromFile,convertListToLowerCase,secondsToHz,getEntryIndexInList +from tools import bcolors,checkIfFileExists,readListFromFile,convertListToLowerCase,secondsToHz,getEntryIndexInList,parseSerialNumberFromSummary #------------------------------------------------------------------------------------------- from BVH.bvhConverter import BVH #------------------------------------------------------------------------------------------- @@ -98,6 +98,7 @@ def __init__(self, self.inputs = convertListToLowerCase(readListFromFile(self.modelDirectory+"/neuralNetworkJoints.list")) self.outputs = convertListToLowerCase(readListFromFile(self.modelDirectory+"/neuralNetworkOutputs.list")) self.configuration = parseConfigurationInputJointMap(self.configuration,self.inputs) + self.serial = parseSerialNumberFromSummary(self.modelDirectory+"/summary.html") #------------------------------------------------------------------------------- self.inputReadyForTF = np.empty([2, 1]) self.NSRM = np.empty([2, 2]) diff --git a/src/python/mnet4/MocapNETTensorflow.py b/src/python/mnet4/MocapNETTensorflow.py index 0c1efa4..11399c7 100755 --- a/src/python/mnet4/MocapNETTensorflow.py +++ b/src/python/mnet4/MocapNETTensorflow.py @@ -10,7 +10,7 @@ from readCSV import parseConfiguration,parseConfigurationInputJointMap,transformNetworkInput,initializeDecompositionForExecutionEngine,readGroundTruthFile,readCSVFile,parseOutputNormalization from NSDM import NSDMLabels,createNSDMUsingRules,inputIsEnoughToCreateNSDM,performNSRMAlignment from EDM import EDMLabels,createEDMUsingRules -from tools import bcolors,checkIfFileExists,readListFromFile,convertListToLowerCase,secondsToHz,getEntryIndexInList +from tools import bcolors,checkIfFileExists,readListFromFile,convertListToLowerCase,secondsToHz,getEntryIndexInList,parseSerialNumberFromSummary #------------------------------------------------------------------------------------------- from BVH.bvhConverter import BVH #------------------------------------------------------------------------------------------- @@ -89,6 +89,7 @@ def __init__(self, self.inputs = convertListToLowerCase(readListFromFile(self.modelDirectory+"/neuralNetworkJoints.list")) self.outputs = convertListToLowerCase(readListFromFile(self.modelDirectory+"/neuralNetworkOutputs.list")) self.configuration = parseConfigurationInputJointMap(self.configuration,self.inputs) + self.serial = parseSerialNumberFromSummary(self.modelDirectory+"/summary.html") #------------------------------------------------------------------------------- self.inputReadyForTF = np.empty([2, 1]) self.NSRM = np.empty([2, 2]) diff --git a/src/python/mnet4/MocapNETVisualization.py b/src/python/mnet4/MocapNETVisualization.py index 22b927c..ee0017d 100755 --- a/src/python/mnet4/MocapNETVisualization.py +++ b/src/python/mnet4/MocapNETVisualization.py @@ -8,8 +8,7 @@ def getColor(i): if i>107: i = i % 108 - - + #--------------------- if (i==0): return (247,252,253) elif (i==1): @@ -248,7 +247,26 @@ def drawMissingInput(image): image = cv2.putText(image, message , org, font, fontScale, color, thickness, cv2.LINE_AA) return image -def drawMocapNETInput(input2D,image,flipX=False): + +def resolveXY(input2D,joint,width,height,flipX=False): + x2D=0 + y2D=0 + jointName2DX = "2dx_"+joint + jointName2DY = "2dy_"+joint + if ( jointName2DX in input2D ) and ( jointName2DY in input2D ): + if (flipX): + x2D = int((1.0-input2D[jointName2DX])*width) + else: + x2D = int(input2D[jointName2DX]*width) + y2D = int(input2D[jointName2DY]*height) + else: + print("Cannot resolve ",joint) + #print(joint," resolved to ",x2D,",",y2D) + return x2D,y2D + + + +def drawMocapNETInput(input2D,image,flipX=False,doLines=True): import cv2 if (type(image)==type(None)): print("Invalid Image given, can't do anything with it") @@ -257,6 +275,49 @@ def drawMocapNETInput(input2D,image,flipX=False): height = image.shape[0] #print("Drawing output to ",width,"x",height," cvmat") + if (doLines): + #Draw lines + #==================================================================== + t=8 + x1,y1=resolveXY(input2D,"rshoulder",width,height,flipX=flipX) + x2,y2=resolveXY(input2D,"relbow",width,height,flipX=flipX) + cv2.line(image, pt1=(x1,y1), pt2=(x2,y2), color=(0,255,0), thickness=t) + x1,y1=resolveXY(input2D,"rhand",width,height,flipX=flipX) + cv2.line(image, pt1=(x1,y1), pt2=(x2,y2), color=(0,255,0), thickness=t) + x1,y1=resolveXY(input2D,"rshoulder",width,height,flipX=flipX) + x2,y2=resolveXY(input2D,"neck",width,height,flipX=flipX) + cv2.line(image, pt1=(x1,y1), pt2=(x2,y2), color=(0,255,0), thickness=t) + x2,y2=resolveXY(input2D,"hip",width,height,flipX=flipX) + cv2.line(image, pt1=(x1,y1), pt2=(x2,y2), color=(0,255,0), thickness=t) + x1,y1=resolveXY(input2D,"rhip",width,height,flipX=flipX) + cv2.line(image, pt1=(x1,y1), pt2=(x2,y2), color=(0,255,0), thickness=t) + x2,y2=resolveXY(input2D,"rknee",width,height,flipX=flipX) + cv2.line(image, pt1=(x1,y1), pt2=(x2,y2), color=(0,255,0), thickness=t) + x1,y1=resolveXY(input2D,"rfoot",width,height,flipX=flipX) + cv2.line(image, pt1=(x1,y1), pt2=(x2,y2), color=(0,255,0), thickness=t) + + x1,y1=resolveXY(input2D,"lshoulder",width,height,flipX=flipX) + x2,y2=resolveXY(input2D,"lelbow",width,height,flipX=flipX) + cv2.line(image, pt1=(x1,y1), pt2=(x2,y2), color=(0,0,255), thickness=t) + x1,y1=resolveXY(input2D,"lhand",width,height,flipX=flipX) + cv2.line(image, pt1=(x1,y1), pt2=(x2,y2), color=(0,0,255), thickness=t) + x1,y1=resolveXY(input2D,"lshoulder",width,height,flipX=flipX) + x2,y2=resolveXY(input2D,"neck",width,height,flipX=flipX) + cv2.line(image, pt1=(x1,y1), pt2=(x2,y2), color=(0,0,255), thickness=t) + x2,y2=resolveXY(input2D,"hip",width,height,flipX=flipX) + cv2.line(image, pt1=(x1,y1), pt2=(x2,y2), color=(0,0,255), thickness=t) + x1,y1=resolveXY(input2D,"lhip",width,height,flipX=flipX) + cv2.line(image, pt1=(x1,y1), pt2=(x2,y2), color=(0,0,255), thickness=t) + x2,y2=resolveXY(input2D,"lknee",width,height,flipX=flipX) + cv2.line(image, pt1=(x1,y1), pt2=(x2,y2), color=(0,0,255), thickness=t) + x1,y1=resolveXY(input2D,"lfoot",width,height,flipX=flipX) + cv2.line(image, pt1=(x1,y1), pt2=(x2,y2), color=(0,0,255), thickness=t) + + x1,y1=resolveXY(input2D,"neck",width,height,flipX=flipX) + x2,y2=resolveXY(input2D,"head",width,height,flipX=flipX) + cv2.line(image, pt1=(x1,y1), pt2=(x2,y2), color=(0,255,255), thickness=t) + #==================================================================== + font = cv2.FONT_HERSHEY_SIMPLEX fontScale = 0.4 @@ -312,8 +373,8 @@ def drawMocapNETInput(input2D,image,flipX=False): image = cv2.putText(image, "%s" % (joint) , (x2D+2,y2D), font, fontScale, color, thickness, cv2.LINE_AA) cv2.circle(image,(x2D,y2D),circleSize,color,cv2.FILLED) - if ('__' in joint): - image = cv2.putText(image, joint , (x2D+2,y2D), font, fontScale, color, thickness, cv2.LINE_AA) + #if ('__' in joint): #Print __temporalis joint + # image = cv2.putText(image, joint , (x2D+2,y2D), font, fontScale, color, thickness, cv2.LINE_AA) return image def drawMocapNETOutput(mnet,image,xOffset=0): #set xOffset to -400 to make visualization more clean by seperating 2D/3D @@ -726,14 +787,37 @@ def drawMocapNETFrequencyPlots(history): -def visualizeMocapNETEnsemble(mnet,annotated_image,plotBVHChannels=0,bvhAnglesForPlotting=list(),economic=False): + + + +def drawMNETSerials(mnet,image,x,y): + import cv2 + #----------------------------------------- + font = cv2.FONT_HERSHEY_SIMPLEX + fontScale = 0.4 + thickness = 1 + #----------------------------------------- + #print("MNET Serials ",mnet.getEnsembleSerials()) + #----------------------------------------- + color = (0,0,0) + org = (x+2,y+2) + image = cv2.putText(image, mnet.getEnsembleSerials() , org, font, fontScale, color, thickness, cv2.LINE_AA) + org = (x,y) + color = (255,255,255) + image = cv2.putText(image, mnet.getEnsembleSerials() , org, font, fontScale, color, thickness, cv2.LINE_AA) + #----------------------------------------- + + + +def visualizeMocapNETEnsemble(mnet,annotated_image,plotBVHChannels=0,bvhAnglesForPlotting=list(),economic=False,drawOutput=True): try: #from MocapNETVisualization import drawMocapNETOutput,drawMocapNETAllPlots,drawMissingInput,drawDescriptor,drawNSRM,drawMAE2DError #------------------------------------------------------------------------------------ - if ("upperbody" in mnet.ensemble): + if (drawOutput): + if ("upperbody" in mnet.ensemble): drawMocapNETOutput(mnet,annotated_image) #only draw 3D ouput if upperbody is loaded and working.. - drawMocapNETInput(mnet.input2D,annotated_image) + drawMocapNETInput(mnet.input2D,annotated_image,doLines=(drawOutput==False)) if (economic): return annotated_image,annotated_image #------------------------------------------------------------------------------------ @@ -792,6 +876,12 @@ def visualizeMocapNETEnsemble(mnet,annotated_image,plotBVHChannels=0,bvhAnglesFo if (len(mnet.history_hz_HCD)>0): drawMocapNETSinglePlotValueList(mnet.history_hz_HCD,1,"HCD FPS",annotated_image,width-70,220,70,70,0.0,60.0) + if (len(mnet.history_hz_Vis)>0): + drawMocapNETSinglePlotValueList(mnet.history_hz_Vis,1,"Visualization",annotated_image,width-70,320,70,70,0.0,60.0) + + drawMNETSerials(mnet,annotated_image,10,30) + + #if (mnet.incompleteUpperbodyInput and mnet.incompleteLowerbodyInput): # drawMissingInput(annotated_image) if (plotBVHChannels==1): diff --git a/src/python/mnet4/csvNET.py b/src/python/mnet4/csvNET.py old mode 100644 new mode 100755 index 994eb1b..407db4a --- a/src/python/mnet4/csvNET.py +++ b/src/python/mnet4/csvNET.py @@ -16,7 +16,8 @@ from readCSV import parseConfiguration,zeroOutXYJointsThatAreInvisible,performNSRMAlignment,splitNumpyArray from NSDM import NSDMLabels,createNSDMUsingRules -from MocapNET import MocapNET +from MocapNET import MocapNET,flipHorizontalInput +from tools import getDirectoryFromPath, checkIfFileExists, secondsToHz #------------------------------------------------ #------------------------------------------------ @@ -176,7 +177,7 @@ def compareToGroundTruth(self,fID,outputToCompare): return float("nan") - def getMocapNETInputForFrameID(self,fID): + def getMocapNETInputForFrameID(self,fID,img): count = 0 mocapNETInput = dict() for label in self.data2D["label"]: @@ -184,7 +185,7 @@ def getMocapNETInputForFrameID(self,fID): mocapNETInput[label] = self.data2D["body"][fID][count] count = count + 1 #-------------------------------------------------------------------------------------------------- - annotated_image = np.zeros((self.height,self.width,3), np.uint8) + #annotated_image = np.zeros((self.height,self.width,3), np.uint8) #<- this is now provided to this function #from MocapNETVisualization import drawMocapNETInput #drawMocapNETInput(mocapNETInput,annotated_image) #-------------------------------------------------------------------------------------------------- @@ -194,7 +195,7 @@ def getMocapNETInputForFrameID(self,fID): #-------------------------------------------------------------------------------------------- self.output = mocapNETInput #-------------------------------------------------------------------------------------------- - return mocapNETInput,annotated_image + return mocapNETInput,img #------------------------------------------------ #------------------------------------------------ #------------------------------------------------ @@ -233,10 +234,13 @@ def getNSRMInterest(mnet,part=""): def streamPosesFromCameraToMocapNET(): engine = "onnx" dataFile = "face" + whiteBkg = False doProfiling = False + doMnetVisualization = True doVisualization = True csvFilePath = "" saveVideo = False + flipHorizontal = False doBody = True doUpperbody = False, #<- These get auto activated if doBody=True doLowerbody = False, #<- These get auto activated if doBody=True @@ -250,14 +254,16 @@ def streamPosesFromCameraToMocapNET(): mem = 1.0 windowDelay = 1 doHCDPostProcessing = 1 - hcdLearningRate = 0.1 - hcdEpochs = 20 + hcdLearningRate = 0.01 + hcdEpochs = 30 hcdIterations = 15 plotBVHChannels = False bvhAnglesForPlotting = list() bvhAllAnglesForPlotting = list() study = "" calibrationFile = "" + keepColorImage = " && rm colorFrame_0_*.jpg " + keepPlotImage = "&& rm plotFrame_0_*.jpg " #python3 mediapipeHolisticWebcamMocapNET.py --from damien.avi --face --nobody --plot --save #python3 -m csvNET --from ammarFaceFar.csv --study face --face --nobody # python3 -m csvNET --from ammarFaceFar.csv --mouth --reye --nobody --plot --save @@ -265,6 +271,8 @@ def streamPosesFromCameraToMocapNET(): if (len(sys.argv)>1): #print('Argument List:', str(sys.argv)) for i in range(0, len(sys.argv)): + if (sys.argv[i]=="--nomnetvisualization"): + doMnetVisualization = False if (sys.argv[i]=="--novisualization"): doVisualization = False if (sys.argv[i]=="--ik"): @@ -283,8 +291,15 @@ def streamPosesFromCameraToMocapNET(): calibrationFile = sys.argv[i+1] if (sys.argv[i]=="--study"): study = sys.argv[i+1] + if (sys.argv[i]=="--flipHorizontal"): + flipHorizontal = True if (sys.argv[i]=="--plot"): plotBVHChannels=True + if (sys.argv[i]=="--all"): + doBody=True + doREye=True + doMouth=True + doHands=True if (sys.argv[i]=="--nobody"): doBody = False doUpperbody = False @@ -306,6 +321,11 @@ def streamPosesFromCameraToMocapNET(): dataFile="mouth" #Use 2d_mouth_all.csv as input if not --from is activated if (sys.argv[i]=="--hands"): doHands=True + if (sys.argv[i]=="--white"): + whiteBkg = True + if (sys.argv[i]=="--keep"): + keepColorImage = " " + keepPlotImage = " " if (sys.argv[i]=="--save"): saveVideo=True if (sys.argv[i]=="--engine"): @@ -342,22 +362,45 @@ def streamPosesFromCameraToMocapNET(): addNoise=addNoise ) - if (calibrationFile!=""): - print("Enforcing Calibration file : ",calibrationFile) - mnet.bvh.configureRendererFromFile(calibrationFile) - mnet.test() mnet.recordBVH(True) #Body only mp = csvNET(mem=mem,dataFile=dataFile,fromCSV=csvFilePath) + + from folderStream import FolderStreamer + baseNameForFolderThatMayContainImages = getDirectoryFromPath(csvFilePath) + print("baseNameForFolderThatMayContainImages = ",baseNameForFolderThatMayContainImages) + imageStream = FolderStreamer(path=baseNameForFolderThatMayContainImages) + if (checkIfFileExists("%s/color.calib" % baseNameForFolderThatMayContainImages)): + mnet.bvh.configureRendererFromFile("%s/color.calib"%baseNameForFolderThatMayContainImages) + + + if (calibrationFile!=""): + print("Enforcing Calibration file : ",calibrationFile) + mnet.bvh.configureRendererFromFile(calibrationFile) + from MocapNETVisualization import visualizeMocapNETEnsemble #------------------------------------------------ #------------------------------------------------ #------------------------------------------------ for frameNumber in range(0, mp.getNumberOfSamples() ): + #attempt to visualize (!) + #-------------------------------------------------------------------------------------------------------------- + success,img = imageStream.read() + if (not success): + if (whiteBkg): + annotated_image = np.full((mp.height,mp.width,3), 255, dtype=np.uint8) + else: + annotated_image = np.zeros((mp.height,mp.width,3), np.uint8) + else: + annotated_image = img + #-------------------------------------------------------------------------------------------------------------- + start = time.time() # Time elapsed + mocapNETInput,annotated_image = mp.getMocapNETInputForFrameID(frameNumber,annotated_image) #-------------------------------------------------------------------------------------------------------------- - mocapNETInput,annotated_image = mp.getMocapNETInputForFrameID(frameNumber) + if (flipHorizontal): + mocapNETInput = flipHorizontalInput(mocapNETInput) #-------------------------------------------------------------------------------------------------------------- mocapNET3DOutput = mnet.predict3DJoints(mocapNETInput) mocapNETBVHOutput = mnet.outputBVH @@ -375,14 +418,20 @@ def streamPosesFromCameraToMocapNET(): if (doVisualization): #-------------------------------------------------------------------------------------------------------------- - annotated_image,plotImage = visualizeMocapNETEnsemble(mnet,annotated_image,plotBVHChannels=plotBVHChannels,bvhAnglesForPlotting=bvhAnglesForPlotting) + start = time.time() # Time elapsed + annotated_image,plotImage = visualizeMocapNETEnsemble(mnet,annotated_image,plotBVHChannels=plotBVHChannels,bvhAnglesForPlotting=bvhAnglesForPlotting,drawOutput=doMnetVisualization) + end = time.time() # Time elapsed + mnet.hz_Vis = secondsToHz(end - start) + mnet.history_hz_Vis.append(mnet.hz_Vis) + if (len(mnet.history_hz_Vis)>mnet.perfHistorySize): + mnet.history_hz_Vis.pop(0) #Keep mnet history on limits #-------------------------------------------------------------------------------------------------------------- if (saveVideo): cv2.imwrite('colorFrame_0_%05u.jpg'%(frameNumber), annotated_image) if (plotBVHChannels): cv2.imwrite('plotFrame_0_%05u.jpg'%(frameNumber), plotImage) #-------------------------------------------------------------------------------------------------------------- - cv2.imshow('MocapNET + MediaPipe Holistic', annotated_image) + cv2.imshow('MocapNET + MediaPipe CSV', annotated_image) if cv2.waitKey(windowDelay) & 0xFF == 27: break #-------------------------------------------------------------------------------------------------------------- @@ -400,9 +449,9 @@ def streamPosesFromCameraToMocapNET(): mp.saveRSquared("rSquared.csv") if (doVisualization) and (saveVideo): - os.system("ffmpeg -framerate 30 -i colorFrame_0_%05d.jpg -s 1200x720 -y -r 30 -pix_fmt yuv420p -threads 8 livelastRun3DHiRes.mp4 && rm colorFrame_0_*.jpg") + os.system("ffmpeg -framerate 30 -i colorFrame_0_%%05d.jpg -s 1200x720 -y -r 30 -pix_fmt yuv420p -threads 8 livelastRun3DHiRes.mp4 %s" % keepColorImage) if (plotBVHChannels): - os.system("ffmpeg -framerate 30 -i plotFrame_0_%05d.jpg -s 1200x720 -y -r 30 -pix_fmt yuv420p -threads 8 livelastPlot3DHiRes.mp4 && rm plotFrame_0_*.jpg") + os.system("ffmpeg -framerate 30 -i plotFrame_0_%%05d.jpg -s 1200x720 -y -r 30 -pix_fmt yuv420p -threads 8 livelastPlot3DHiRes.mp4 %s" % keepPlotImage) del mnet #So that the out.bvh file gets created.. diff --git a/src/python/mnet4/espStream.py b/src/python/mnet4/espStream.py old mode 100644 new mode 100755 diff --git a/src/python/mnet4/evaluateMocapNET.py b/src/python/mnet4/evaluateMocapNET.py index afc4d03..b0fe7e1 100755 --- a/src/python/mnet4/evaluateMocapNET.py +++ b/src/python/mnet4/evaluateMocapNET.py @@ -32,6 +32,15 @@ """) +print(""" +██████╗ ██████╗ ██████╗ ██╗ ██╗ +██╔══██╗██╔═══██╗██╔══██╗╚██╗ ██╔╝ +██████╔╝██║ ██║██║ ██║ ╚████╔╝ +██╔══██╗██║ ██║██║ ██║ ╚██╔╝ +██████╔╝╚██████╔╝██████╔╝ ██║ +╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ +""") + print("Ensuring the same seed for reproductible results always..\n") from numpy.random import seed seed(1) diff --git a/src/python/mnet4/getModelFromDatabase.py b/src/python/mnet4/getModelFromDatabase.py index 02f1807..391550e 100755 --- a/src/python/mnet4/getModelFromDatabase.py +++ b/src/python/mnet4/getModelFromDatabase.py @@ -6,13 +6,15 @@ License : "FORTH" """ -import os +import os import sys import gc import time import numpy as np #-------------------------------------------------- -from tools import bcolors,notification,checkIfFileExists,getRAMInformation +from tools import bcolors,notification,checkIfPathExists,checkIfPathIsDirectory,checkIfFileExists,getRAMInformation + +ONNX_OPSET=17 # Was 14 def downloadAndParseDatabase(): file = "modelDatabase.json" @@ -52,8 +54,16 @@ def downloadAndCompileSingle(file,part,step0,step1,json,pca,allowQuickCopy=True, else: print("Ensemble %s already exists locally" % (file)) #--------------------------------------------------------------------------------------- - print(bcolors.OKBLUE,"Clean up ",step0," \n",bcolors.ENDC) - os.system('rm -rf %s/'% (step0)) + if checkIfPathExists(step0) and checkIfPathIsDirectory(step0): + print(bcolors.OKBLUE,"Clean up ",step0," \n",bcolors.ENDC) + os.system('rm -rf %s/'% (step0)) + else: + print("Do not need to clear step0 ",step0) + if checkIfPathExists(step1) and checkIfPathIsDirectory(step1): + print(bcolors.OKBLUE,"Clean up ",step1," \n",bcolors.ENDC) + os.system('rm -rf %s/'% (step1)) + else: + print("Do not need to clear step1 ",step1) print(bcolors.OKBLUE,"Extracting models from ",file," \n",bcolors.ENDC) #print(bcolors.OKBLUE,'tar -xf %s' % (file),bcolors.ENDC) os.system('tar -xf %s' % (file)) @@ -73,10 +83,15 @@ def downloadAndCompileSingle(file,part,step0,step1,json,pca,allowQuickCopy=True, print(bcolors.FAIL,"Failed preparing ",part," stopping model database retrieval",bcolors.ENDC) raise IOError #--------------------------------------------------------------------------------------- + gc.collect() + #--------------------------------------------------------------------------------------- RAM = getRAMInformation() - if (RAM["total"]<9000000): - os.system('python3 -m tf2onnx.convert --saved-model %s --opset 14 --tag serve --output %s/model.onnx' % (step1,step1)) + if (RAM["free"]>=2000000): + os.system('python3 -m tf2onnx.convert --saved-model %s --opset %u --tag serve --output %s/model.onnx' % (step1,ONNX_OPSET,step1)) os.system('tflite_convert --saved_model_dir=%s --output_file=%s/model.tflite' % (step1,step1)) + else: + print(bcolors.FAIL,"Not Enough RAM (we have ",RAM["free"],") to perform ONNX/TF-Lite conversions!",bcolors.ENDC) + notification("MocapNET Database","Finished Compiling MocapNET %s ensemble" % part) def downloadAndCompileModel(fileUpper,fileLower,fileHand="",fileFace="",fileReye="",fileMouth="",allowQuickCopy=True,download=1): @@ -436,6 +451,25 @@ def retrieveAndSetupBasedOnSerial(serial:int,allowQuickCopy:bool=True,download:i elif (serial==301): fileUpper = "301-A-Training-23-07-16_17-58-25-upperbody-ffe5156750f3-Ubuntu-20.04.tar.bz2" fileLower = "301-B-Training-23-07-16_22-45-49-lowerbody-ffe5156750f3-Ubuntu-20.04.tar.bz2" + elif (serial==302): + fileUpper = "302-A-Training-23-07-17_06-32-23-upperbody-ffe5156750f3-Ubuntu-20.04.tar.bz2" + fileLower = "302-B-Training-23-07-17_11-35-01-lowerbody-ffe5156750f3-Ubuntu-20.04.tar.bz2" + elif (serial==304): + fileUpper = "302-A-Training-23-07-17_06-32-23-upperbody-ffe5156750f3-Ubuntu-20.04.tar.bz2" + fileLower = "302-B-Training-23-07-17_11-35-01-lowerbody-ffe5156750f3-Ubuntu-20.04.tar.bz2" + fileHand = "290A-Training-23-06-28_10-31-52-lhand-ffe5156750f3-Ubuntu-20.04.tar.bz2" + fileMouth = "299B-Training-23-07-17_08-29-17-mouth-ammar-forth-Ubuntu-20.04.tar.bz2" + fileReye = "304A-Training-23-07-19_16-51-17-reye-ammar-forth-Ubuntu-20.04.tar.bz2" + elif (serial==305): + fileHand = "305B-Training-23-07-29_09-52-21-lhand-ffe5156750f3-Ubuntu-20.04.tar.bz2" + elif (serial==307): + fileHand = "307A-Training-23-08-19_23-23-06-lhand-ffe5156750f3-Ubuntu-20.04.tar.bz2" + elif (serial==310): #COMMON LOSS EXPERIMENTS + fileHand = "307B-Training-23-09-08_13-54-22-lhand-ffe5156750f3-Ubuntu-20.04.tar.bz2" + fileUpper = "309-A-Training-23-09-08_00-20-09-upperbody-ammar-forth-Ubuntu-22.04.tar.bz2" + fileLower = "310B.tar.bz2" + fileMouth = "299B-Training-23-07-17_08-29-17-mouth-ammar-forth-Ubuntu-20.04.tar.bz2" + fileReye = "304A-Training-23-07-19_16-51-17-reye-ammar-forth-Ubuntu-20.04.tar.bz2" else: print(bcolors.WARNING,"Unknown serial ",serial,bcolors.ENDC) print(bcolors.WARNING,"Completely halting execution to avoid a wrong run!\n",bcolors.ENDC) @@ -450,6 +484,13 @@ def retrieveAndSetupBasedOnSerial(serial:int,allowQuickCopy:bool=True,download:i if __name__ == '__main__': startEverythingAt = time.time() + + RAM = getRAMInformation() + print("RAM : ",RAM) + if (RAM["free"]<2000000): + print(bcolors.FAIL,"We do not have enough memory (we have ",RAM["free"],") for ONNX / TF-Lite model so stopping..",bcolors.ENDC) + raise MemoryError + db = downloadAndParseDatabase() print(db) diff --git a/src/python/mnet4/mediapipeHolisticWebcamMocapNET.py b/src/python/mnet4/mediapipeHolisticWebcamMocapNET.py index e8f00d8..02623a9 100755 --- a/src/python/mnet4/mediapipeHolisticWebcamMocapNET.py +++ b/src/python/mnet4/mediapipeHolisticWebcamMocapNET.py @@ -17,7 +17,7 @@ from readCSV import parseConfiguration,zeroOutXYJointsThatAreInvisible,performNSRMAlignment from NSDM import NSDMLabels,createNSDMUsingRules - +from tools import secondsToHz from MocapNET import MocapNET mp_drawing = mp.solutions.drawing_utils @@ -360,8 +360,8 @@ def streamPosesFromCameraToMocapNET(): scale = 1.0 addNoise = 0.0 doHCDPostProcessing = 1 - hcdLearningRate = 0.1 - hcdEpochs = 20 + hcdLearningRate = 0.01 + hcdEpochs = 30 hcdIterations = 15 plotBVHChannels = False calibrationFile = "" @@ -395,6 +395,11 @@ def streamPosesFromCameraToMocapNET(): scale=float(sys.argv[i+1]) if (sys.argv[i]=="--plot"): plotBVHChannels=True + if (sys.argv[i]=="--all"): + doBody=True + doREye=True + doMouth=True + doHands=True if (sys.argv[i]=="--nobody"): doBody=False if (sys.argv[i]=="--face"): @@ -511,7 +516,13 @@ def streamPosesFromCameraToMocapNET(): bvhAnglesForPlotting.pop(0) #-------------------------------------------------------------------------------------------------------------- from MocapNETVisualization import visualizeMocapNETEnsemble + start = time.time() # Time elapsed image,plotImage = visualizeMocapNETEnsemble(mnet,annotated_image,plotBVHChannels=plotBVHChannels,bvhAnglesForPlotting=bvhAnglesForPlotting) + end = time.time() # Time elapsed + mnet.hz_Vis = secondsToHz(end - start) + mnet.history_hz_Vis.append(mnet.hz_Vis) + if (len(mnet.history_hz_Vis)>mnet.perfHistorySize): + mnet.history_hz_Vis.pop(0) #Keep mnet history on limits #-------------------------------------------------------------------------------------------------------------- frameNumber = frameNumber + 1 diff --git a/src/python/mnet4/plotCSV.py b/src/python/mnet4/plotCSV.py index 52d7583..d4c1de0 100755 --- a/src/python/mnet4/plotCSV.py +++ b/src/python/mnet4/plotCSV.py @@ -4,6 +4,7 @@ import matplotlib.pyplot as plt from readCSV import readGroundTruthFile,splitNumpyArray from readCSV import parseConfiguration +from tools import getFileFromPath def splitNumpyArray(model,column): numberOfSamples=len(model) @@ -190,6 +191,17 @@ def plotTestOutputDistribution(csvfiletoplotIn,csvfiletoplotOut,mem): +#Test Plots +#./GroundTruthDumper --from dataset/yawTest.bvh --csv ./ yaw.csv 2d --svg . +# python3 csvNET.py --from 2d_yaw.csv --save --keep --hands --white +# convert colorFrame_0_00000.jpg -crop 234x500+0+90 nsrm_00000.jpg + +#./GroundTruthDumper --from dataset/pitchTest.bvh --csv ./ pitch.csv 2d --svg . +# python3 csvNET.py --from 2d_pitch.csv --save --keep --hands --white + +#./GroundTruthDumper --from dataset/rollTest.bvh --csv ./ roll.csv 2d --svg . +# python3 csvNET.py --from 2d_roll.csv --save --keep --hands --white + def doRun(): label = "nolabel" @@ -218,7 +230,8 @@ def doRun(): for z in range(0,len(data['label'])): print("Plotting Column ",data['label'][z]) specificData=splitNumpyArray(data['body'],z) - plotDistribution(z,'%s-%s' % (label,data['label'][z]),specificData) + filename = getFileFromPath('%s-%s' % (label,data['label'][z])) + plotDistribution(z,filename,specificData) sys.exit(0) #========================================== diff --git a/src/python/mnet4/readCSV.py b/src/python/mnet4/readCSV.py index c2e9777..896cd54 100755 --- a/src/python/mnet4/readCSV.py +++ b/src/python/mnet4/readCSV.py @@ -26,7 +26,8 @@ """ def splitNumpyArray(data,column,columnsToTake,useHalfFloats): #--------------------------------------------------------------------------------------------- - numberOfSamples=len(data) + numberOfSamples = len(data) + #numberOfColumns = len(data[0]) #--------------------------------------------------------------------------------------------- if (useHalfFloats): npOutput = np.full([numberOfSamples,columnsToTake],fill_value=0,dtype=np.float16,order='C') @@ -1264,7 +1265,7 @@ def readCSVFile(filename,memPercentage=1.0,useHalfFloats=False): sampleNumber=sampleNumber+1 if (numberOfSamples>0): - progress=sampleNumber/numberOfSamplesLimit + progress=sampleNumber/numberOfSamplesLimit if (sampleNumber%1000==0) : progressString = "%0.2f"%float(100*progress) diff --git a/src/python/mnet4/sobolRandomDatasetGenerator.py b/src/python/mnet4/sobolRandomDatasetGenerator.py old mode 100644 new mode 100755 index 8bfdb94..f08dfb8 --- a/src/python/mnet4/sobolRandomDatasetGenerator.py +++ b/src/python/mnet4/sobolRandomDatasetGenerator.py @@ -58,9 +58,9 @@ def getUpperbodyList(): joints.append("chest_Zrotation"); minima.append(-63.64); maxima.append(59.23); dof+=1 joints.append("chest_Xrotation"); minima.append(-59.62); maxima.append(42.48); dof+=1 joints.append("chest_Yrotation"); minima.append(-30.52); maxima.append(34.27); dof+=1 - joints.append("neck1_Zrotation"); minima.append(-74.41); maxima.append(97.04); dof+=1 - joints.append("neck1_Xrotation"); minima.append(-63.63); maxima.append(81.78); dof+=1 - joints.append("neck1_Yrotation"); minima.append(-63.31); maxima.append(71.25); dof+=1 + joints.append("neck1_Zrotation"); minima.append(-74.41); maxima.append(97.04); dof+=1 + joints.append("neck1_Xrotation"); minima.append(-63.63); maxima.append(81.78); dof+=1 + joints.append("neck1_Yrotation"); minima.append(-63.31); maxima.append(71.25); dof+=1 joints.append("head_Zrotation"); minima.append(minR); maxima.append(maxR); dof+=1 joints.append("head_Xrotation"); minima.append(-89.24); maxima.append(86.77); dof+=1 joints.append("head_Yrotation"); minima.append(-178.54); maxima.append(maxR); dof+=1 @@ -120,7 +120,7 @@ def getLowerbodyList(): #joints.append("neck1_Yrotation"); minima.append(-63.31); maxima.append(71.64); dof+=1 #------------------------------------------------------------------------------- joints.append("rhip_Zrotation"); minima.append(minR); maxima.append(maxR); dof+=1 - joints.append("rhip_Xrotation"); minima.append(-104.0); maxima.append(94.0); dof+=1 #Default -104.11 .. 73.67 + joints.append("rhip_Xrotation"); minima.append(-104.0); maxima.append(94.0); dof+=1 #Default -104.11 .. 73.67 joints.append("rhip_Yrotation"); minima.append(minR); maxima.append(maxR); dof+=1 joints.append("rknee_Zrotation"); minima.append(minR); maxima.append(maxR); dof+=1 joints.append("rknee_Xrotation"); minima.append(-66.0); maxima.append(94.0); dof+=1 #Default -66.73 .. 94.07 @@ -130,7 +130,7 @@ def getLowerbodyList(): joints.append("rfoot_Yrotation"); minima.append(minR); maxima.append(maxR); dof+=1 #------------------------------------------------------------------------------- joints.append("lhip_Zrotation"); minima.append(minR); maxima.append(maxR); dof+=1 - joints.append("lhip_Xrotation"); minima.append(-104.0); maxima.append(94.0); dof+=1 #Default -104.23 .. 93.92 + joints.append("lhip_Xrotation"); minima.append(-104.0); maxima.append(94.0); dof+=1 #Default -104.23 .. 93.92 joints.append("lhip_Yrotation"); minima.append(minR); maxima.append(maxR); dof+=1 joints.append("lknee_Zrotation"); minima.append(minR); maxima.append(maxR); dof+=1 joints.append("lknee_Xrotation"); minima.append(-66.0); maxima.append(94.0); dof+=1 #Default -46.36 .. 94.34 @@ -154,14 +154,14 @@ def getFullFaceList(): dof = 0 #/home/ammar/Programs/blender-3.4.1-linux-x64/3.4/scripts/addons/io_anim_bvh/import_bvh.py #----------------------------------- - joints.append("hip_Xposition"); minima.append(-0.24); maxima.append(0.24); rootDofs.append(dof); dof+=1 - joints.append("hip_Yposition"); minima.append(-0.10); maxima.append(0.10); rootDofs.append(dof); dof+=1 - joints.append("hip_Zposition"); minima.append(-2.4); maxima.append(-1.0); rootDofs.append(dof); dof+=1 - joints.append("neck1_Zrotation"); minima.append(-20.0); maxima.append(20.0); rootDofs.append(dof); dof+=1 - joints.append("neck1_Xrotation"); minima.append(-20.0); maxima.append(20.0); rootDofs.append(dof); dof+=1 - joints.append("neck1_Yrotation"); minima.append(-30.0); maxima.append(30.0); rootDofs.append(dof); dof+=1 - joints.append("eye.R_Zrotation"); minima.append(-45.36); maxima.append(45.36); dof+=1 - joints.append("eye.R_Xrotation"); minima.append(-10.0); maxima.append(16.0); dof+=1 + joints.append("hip_Xposition"); minima.append(-0.24); maxima.append(0.24); rootDofs.append(dof); dof+=1 + joints.append("hip_Yposition"); minima.append(-0.10); maxima.append(0.10); rootDofs.append(dof); dof+=1 + joints.append("hip_Zposition"); minima.append(-2.4); maxima.append(-1.0); rootDofs.append(dof); dof+=1 + joints.append("neck1_Zrotation"); minima.append(-20.0); maxima.append(20.0); rootDofs.append(dof); dof+=1 + joints.append("neck1_Xrotation"); minima.append(-20.0); maxima.append(20.0); rootDofs.append(dof); dof+=1 + joints.append("neck1_Yrotation"); minima.append(-30.0); maxima.append(30.0); rootDofs.append(dof); dof+=1 + joints.append("eye.R_Zrotation"); minima.append(-45.36); maxima.append(45.36); dof+=1 + joints.append("eye.R_Xrotation"); minima.append(-10.0); maxima.append(16.0); dof+=1 #Let's assume eyes move together.. #joints.append("eye.L_Zrotation"); minima.append(-20.0); maxima.append(20.0) #joints.append("eye.L_Xrotation"); minima.append(-7.0); maxima.append(20.0) @@ -213,18 +213,18 @@ def getReyeList(): rootDofs = list() dof = 0 #----------------------------------- - joints.append("hip_Xposition"); minima.append(-0.24); maxima.append(0.24); rootDofs.append(dof); dof+=1 - joints.append("hip_Yposition"); minima.append(-0.10); maxima.append(0.10); rootDofs.append(dof); dof+=1 - joints.append("hip_Zposition"); minima.append(-2.3); maxima.append(-1.0); rootDofs.append(dof); dof+=1 - joints.append("neck1_Zrotation"); minima.append(-20.0); maxima.append(20.0); rootDofs.append(dof); dof+=1 - joints.append("neck1_Xrotation"); minima.append(-20.0); maxima.append(20.0); rootDofs.append(dof); dof+=1 - joints.append("neck1_Yrotation"); minima.append(-30.0); maxima.append(30.0); rootDofs.append(dof); dof+=1 - joints.append("eye.R_Zrotation"); minima.append(-45.36); maxima.append(45.36); dof+=1 - joints.append("eye.R_Xrotation"); minima.append(-10.0); maxima.append(16.0); dof+=1 - joints.append("oculi01.R_Zrotation"); minima.append(-20.0); maxima.append(20.0); dof+=1 - joints.append("orbicularis03.R_Xrotation"); minima.append(-15.0); maxima.append(15.0); dof+=1 - joints.append("jaw_Xrotation"); minima.append(-4.0); maxima.append(20.0); dof+=1 #Reason being adding some robustness - joints.append("jaw_Yrotation"); minima.append(-15.0); maxima.append(15.0); dof+=1 #Reason being adding some chin robustness + joints.append("hip_Xposition"); minima.append(-0.24); maxima.append(0.24); rootDofs.append(dof); dof+=1 + joints.append("hip_Yposition"); minima.append(-0.10); maxima.append(0.10); rootDofs.append(dof); dof+=1 + joints.append("hip_Zposition"); minima.append(-2.3); maxima.append(-1.0); rootDofs.append(dof); dof+=1 + joints.append("neck1_Zrotation"); minima.append(-20.0); maxima.append(20.0); rootDofs.append(dof); dof+=1 + joints.append("neck1_Xrotation"); minima.append(-20.0); maxima.append(20.0); rootDofs.append(dof); dof+=1 + joints.append("neck1_Yrotation"); minima.append(-30.0); maxima.append(30.0); rootDofs.append(dof); dof+=1 + joints.append("eye.R_Zrotation"); minima.append(-45.36); maxima.append(45.36); dof+=1 + joints.append("eye.R_Xrotation"); minima.append(-10.0); maxima.append(16.0); dof+=1 + joints.append("oculi01.R_Zrotation"); minima.append(-20.0); maxima.append(20.0); dof+=1 + joints.append("orbicularis03.R_Xrotation"); minima.append(-15.0); maxima.append(15.0); dof+=1 + joints.append("jaw_Xrotation"); minima.append(-4.0); maxima.append(20.0); dof+=1 #Reason being adding some robustness + joints.append("jaw_Yrotation"); minima.append(-15.0); maxima.append(15.0); dof+=1 #Reason being adding some chin robustness #----------------------------------- constants["orbicularis03.R_Yrotation"] = 172.0 constants["orbicularis04.R_Yrotation"] = 172.0 @@ -244,13 +244,13 @@ def getMouthList(): joints.append("hip_Xposition"); minima.append(-0.24); maxima.append(0.24); rootDofs.append(dof); dof+=1 joints.append("hip_Yposition"); minima.append(-0.10); maxima.append(0.10); rootDofs.append(dof); dof+=1 joints.append("hip_Zposition"); minima.append(-2.4); maxima.append(-1.0); rootDofs.append(dof); dof+=1 - joints.append("neck1_Zrotation"); minima.append(-20.0); maxima.append(20.0); rootDofs.append(dof); dof+=1 - joints.append("neck1_Xrotation"); minima.append(-20.0); maxima.append(20.0); rootDofs.append(dof); dof+=1 - joints.append("neck1_Yrotation"); minima.append(-30.0); maxima.append(30.0); rootDofs.append(dof); dof+=1 + joints.append("neck1_Zrotation"); minima.append(-20.0); maxima.append(20.0); rootDofs.append(dof); dof+=1 + joints.append("neck1_Xrotation"); minima.append(-20.0); maxima.append(20.0); rootDofs.append(dof); dof+=1 + joints.append("neck1_Yrotation"); minima.append(-30.0); maxima.append(30.0); rootDofs.append(dof); dof+=1 joints.append("levator06.L_Xrotation"); minima.append(-9.0); maxima.append(9.0); dof+=1 - #joints.append("levator06.R_Xrotation"); minima.append(-9.0); maxima.append(9.0); dof+=1 #This is levator06.L_Xrotation + #joints.append("levator06.R_Xrotation"); minima.append(-9.0); maxima.append(9.0); dof+=1 #This is levator06.L_Xrotation joints.append("levator03.L_Zrotation"); minima.append(-8.0); maxima.append(9.0); dof+=1 - #joints.append("levator03.R_Zrotation"); minima.append(-9.0); maxima.append(8.0); dof+=1 #This is flipped levator03.L_Zrotation + #joints.append("levator03.R_Zrotation"); minima.append(-9.0); maxima.append(8.0); dof+=1 #This is flipped levator03.L_Zrotation joints.append("oris03.L_Zrotation"); minima.append(-30.0); maxima.append(30.0); dof+=1 joints.append("oris03.R_Zrotation"); minima.append(-30.0); maxima.append(30.0); dof+=1 joints.append("oris07.L_Zrotation"); minima.append(-30.0); maxima.append(0.0); dof+=1 @@ -287,48 +287,54 @@ def getLHandList(): dof = 0 #-------------------------------------- joints.append("lhand_Xposition"); minima.append(-120.0); maxima.append(120.0); rootDofs.append(dof); dof+=1 #230 - joints.append("lhand_Yposition"); minima.append(-60.0); maxima.append(60.0); rootDofs.append(dof); dof+=1 #92 - joints.append("lhand_Zposition"); minima.append(-250.0); maxima.append(-75.0); rootDofs.append(dof); dof+=1 + joints.append("lhand_Yposition"); minima.append(-60.0); maxima.append(60.0); rootDofs.append(dof); dof+=1 #92 + joints.append("lhand_Zposition"); minima.append(-250.0); maxima.append(-75.0); rootDofs.append(dof); dof+=1 + #joints.append("padding_1"); minima.append(0.0); maxima.append(0.0); rootDofs.append(dof); dof+=1 #<- Align to 4 elements #-------------------------------------- - joints.append("lhand_Wrotation"); minima.append(-1.0); maxima.append(1.0); rootDofs.append(dof); dof+=1 - joints.append("lhand_Xrotation"); minima.append(-1.0); maxima.append(1.0); rootDofs.append(dof); dof+=1 - joints.append("lhand_Yrotation"); minima.append(-1.0); maxima.append(1.0); rootDofs.append(dof); dof+=1 - joints.append("lhand_Zrotation"); minima.append(-1.0); maxima.append(1.0); rootDofs.append(dof); dof+=1 + joints.append("lhand_Wrotation"); minima.append(-1.0); maxima.append(1.0); rootDofs.append(dof); dof+=1 + joints.append("lhand_Xrotation"); minima.append(-1.0); maxima.append(1.0); rootDofs.append(dof); dof+=1 + joints.append("lhand_Yrotation"); minima.append(-1.0); maxima.append(1.0); rootDofs.append(dof); dof+=1 + joints.append("lhand_Zrotation"); minima.append(-1.0); maxima.append(1.0); rootDofs.append(dof); dof+=1 #-------------------------------------- - joints.append("finger2-1.l_Zrotation"); minima.append(-90.0); maxima.append(10.0); dof+=1 - joints.append("finger2-1.l_Yrotation"); minima.append(-20.0); maxima.append(20.0); dof+=1 - joints.append("finger2-2.l_Zrotation"); minima.append(-90.0); maxima.append(0.0); dof+=1 - joints.append("finger2-3.l_Zrotation"); minima.append(-45.0); maxima.append(0.0); dof+=1 + joints.append("finger2-1.l_Zrotation"); minima.append(-90.0); maxima.append(10.0); dof+=1 + joints.append("finger2-1.l_Yrotation"); minima.append(-20.0); maxima.append(20.0); dof+=1 + joints.append("finger2-2.l_Zrotation"); minima.append(-90.0); maxima.append(0.0); dof+=1 + joints.append("finger2-3.l_Zrotation"); minima.append(-45.0); maxima.append(0.0); dof+=1 #-------------------------------------- - joints.append("finger3-1.l_Zrotation"); minima.append(-90.0); maxima.append(10.0); dof+=1 - joints.append("finger3-1.l_Yrotation"); minima.append(-10.0); maxima.append(10.0); dof+=1 - joints.append("finger3-2.l_Zrotation"); minima.append(-90.0); maxima.append(0.0); dof+=1 - joints.append("finger3-3.l_Zrotation"); minima.append(-45.0); maxima.append(0.0); dof+=1 + joints.append("finger3-1.l_Zrotation"); minima.append(-90.0); maxima.append(10.0); dof+=1 + joints.append("finger3-1.l_Yrotation"); minima.append(-10.0); maxima.append(10.0); dof+=1 + joints.append("finger3-2.l_Zrotation"); minima.append(-90.0); maxima.append(0.0); dof+=1 + joints.append("finger3-3.l_Zrotation"); minima.append(-45.0); maxima.append(0.0); dof+=1 #-------------------------------------- - joints.append("finger4-1.l_Zrotation"); minima.append(-90.0); maxima.append(10.0); dof+=1 - joints.append("finger4-1.l_Yrotation"); minima.append(-10.0); maxima.append(10.0); dof+=1 - joints.append("finger4-2.l_Zrotation"); minima.append(-90.0); maxima.append(0.0); dof+=1 - joints.append("finger4-3.l_Zrotation"); minima.append(-45.0); maxima.append(0.0); dof+=1 + joints.append("finger4-1.l_Zrotation"); minima.append(-90.0); maxima.append(10.0); dof+=1 + joints.append("finger4-1.l_Yrotation"); minima.append(-10.0); maxima.append(10.0); dof+=1 + joints.append("finger4-2.l_Zrotation"); minima.append(-90.0); maxima.append(0.0); dof+=1 + joints.append("finger4-3.l_Zrotation"); minima.append(-45.0); maxima.append(0.0); dof+=1 #-------------------------------------- - joints.append("finger5-1.l_Zrotation"); minima.append(-90.0); maxima.append(10.0); dof+=1 - joints.append("finger5-1.l_Yrotation"); minima.append(-8.0); maxima.append(25.0); dof+=1 - joints.append("finger5-2.l_Zrotation"); minima.append(-90.0); maxima.append(0.0); dof+=1 - joints.append("finger5-3.l_Zrotation"); minima.append(-45.0); maxima.append(0.0); dof+=1 + joints.append("finger5-1.l_Zrotation"); minima.append(-90.0); maxima.append(10.0); dof+=1 + joints.append("finger5-1.l_Yrotation"); minima.append(-8.0); maxima.append(25.0); dof+=1 + joints.append("finger5-2.l_Zrotation"); minima.append(-90.0); maxima.append(0.0); dof+=1 + joints.append("finger5-3.l_Zrotation"); minima.append(-45.0); maxima.append(0.0); dof+=1 #-------------------------------------- - joints.append("lthumbBase_Zrotation"); minima.append(0.0); maxima.append(60.0); dof+=1 - joints.append("lthumbBase_Xrotation"); minima.append(-35.0); maxima.append(0.0); dof+=1 - joints.append("lthumbBase_Yrotation"); minima.append(0.0); maxima.append(60.0); dof+=1 + joints.append("lthumbBase_Zrotation"); minima.append(0.0); maxima.append(60.0); dof+=1 + joints.append("lthumbBase_Xrotation"); minima.append(-35.0); maxima.append(0.0); dof+=1 + joints.append("lthumbBase_Yrotation"); minima.append(0.0); maxima.append(60.0); dof+=1 + #joints.append("padding_2"); minima.append(0.0); maxima.append(0.0); #<- Align to 4 elements #-------------------------------------- - joints.append("lthumb_Zrotation"); minima.append(-85.0); maxima.append(85.0); dof+=1 - joints.append("lthumb_Xrotation"); minima.append(-30.0); maxima.append(48.0); dof+=1 - joints.append("lthumb_Yrotation"); minima.append(0.0); maxima.append(85.0); dof+=1 + joints.append("lthumb_Zrotation"); minima.append(-85.0); maxima.append(85.0); dof+=1 + joints.append("lthumb_Xrotation"); minima.append(-30.0); maxima.append(48.0); dof+=1 + joints.append("lthumb_Yrotation"); minima.append(0.0); maxima.append(85.0); dof+=1 + #joints.append("padding_3"); minima.append(0.0); maxima.append(0.0); #<- Align to 4 elements #-------------------------------------- - joints.append("finger1-2.l_Zrotation"); minima.append(-35.0); maxima.append(0.0); dof+=1 - joints.append("finger1-2.l_Xrotation"); minima.append(-40.0); maxima.append(45.0); dof+=1 - joints.append("finger1-2.l_Yrotation"); minima.append(-70.0); maxima.append(35.0); dof+=1 + joints.append("finger1-2.l_Zrotation"); minima.append(-35.0); maxima.append(0.0); dof+=1 + joints.append("finger1-2.l_Xrotation"); minima.append(-40.0); maxima.append(45.0); dof+=1 + joints.append("finger1-2.l_Yrotation"); minima.append(-70.0); maxima.append(35.0); dof+=1 + #joints.append("padding_4"); minima.append(0.0); maxima.append(0.0); #<- Align to 4 elements #-------------------------------------- - joints.append("finger1-3.l_Zrotation"); minima.append(-50.0); maxima.append(0.0); dof+=1 - joints.append("finger1-3.l_Xrotation"); minima.append(0.0); maxima.append(50.0); dof+=1 + joints.append("finger1-3.l_Zrotation"); minima.append(-50.0); maxima.append(0.0); dof+=1 + joints.append("finger1-3.l_Xrotation"); minima.append(0.0); maxima.append(50.0); dof+=1 + #joints.append("padding_5"); minima.append(0.0); maxima.append(0.0); #<- Align to 4 elements + #joints.append("padding_6"); minima.append(0.0); maxima.append(0.0); #<- Align to 4 elements #-------------------------------------- return minima,maxima,joints,constants,dof,rootDofs @@ -396,12 +402,14 @@ def getLHandList(): elif (sys.argv[i]=="--mouth"): datasetPart = "Mouth" minima,maxima,joints,constants,dof,rootDofs = getMouthList() + elif (sys.argv[i]=="--face"): + datasetPart = "Face" + minima,maxima,joints,constants,dof,rootDofs = getFullFaceList() if (len(minima)==0) and (len(maxima)==0) and (len(joints)==0): - datasetPart = "Face" - minima,maxima,joints,constants,dof,rootDofs = getFullFaceList() + print("Please rerun using --face OR --mouth OR --reye OR --lhand OR --upperbody OR --lowerbody to select a target") numberOfDimensions = len(joints) @@ -511,6 +519,7 @@ def getLHandList(): #------------------------------ command1 = "" command2 = "" +command3 = "" havePayload = False minOrientation = -179.90 maxOrientation = 179.90 @@ -518,6 +527,7 @@ def getLHandList(): if (datasetPart == "LHand"): command1 = "rm dataset/generated/2d_lhand_all.csv dataset/generated/bvh_lhand_all.csv" command2 = "./GroundTruthDumper --from dataset/lhand.qbvh --importCSVPoses %s --randomize2D 200 2500 -179.0 -179.0 -179.0 179.0 179.0 179.0 --selectJoints 1 17 lHand finger5-1.l finger5-2.l finger5-3.l finger4-1.l finger4-2.l finger4-3.l finger3-1.l finger3-2.l finger3-3.l finger2-1.l finger2-2.l finger2-3.l lthumbBase lthumb finger1-2.l finger1-3.l --hide2DLocationOfJoints 0 1 lthumbBase --csv dataset/generated/ lhand_all.csv 2d+bvh " % (filename) + command3 = "python3 compressCSVFile.py dataset/generated/bvh_lhand_all.csv && mv dataset/generated/bvh_lhand_all.csv dataset/generated/bvh_lhand_all.csv.original && mv compressed.csv dataset/generated/bvh_lhand_all.csv " havePayload = True elif (datasetPart == "Upperbody"): command1 = "rm dataset/generated/2d_upperbody_all.csv dataset/generated/bvh_upperbody_all.csv" @@ -532,10 +542,14 @@ def getLHandList(): if (doFinalCopy): os.system(command1) os.system(command2) + if (command3!=""): + os.system(command3) else: print("To copy data, please execute : ") print(command1) print(command2) + if (command3!=""): + print(command3) #------------------------------ diff --git a/src/python/mnet4/tools.py b/src/python/mnet4/tools.py index 3bcdad6..5820cc9 100755 --- a/src/python/mnet4/tools.py +++ b/src/python/mnet4/tools.py @@ -244,6 +244,12 @@ def getRAMInformation(): ret['used'] = int(ret['total']) - int(ret['free']) return ret + + + + + + """ Count the number of lines by parsing the file inside python """ @@ -278,6 +284,16 @@ def getNumberOfLinesOS(filename): print("It was ",out[0]) return int(out[0]) + +""" +Check version against master server +""" +def checkVersion(MOCAPNET_VERSION): + import socket + os.system("wget -qO- \"http://ammar.gr/mocapnet/version/index.php?h=%s&v=%s\"&"%(socket.gethostname(),MOCAPNET_VERSION)) + #http://ammar.gr/mocapnet/version/index.php?h=elina-kriti&v=4.0 + + """ Check the number of times a specific keyword *pattern* appears inside the file with the given filename """ @@ -412,6 +428,19 @@ def saveCSVFileFromListOfDicts(filename,inputDicts): f.close() +""" +Given a /path/to/a/specific/file.ext remove file.ext and return the base path +""" +def getDirectoryFromPath(path): + return os.path.dirname(path) + +""" +Given a /path/to/a/specific/file.ext remove /path/to/a/specific/ and return file.ext +""" +def getFileFromPath(path): + return os.path.basename(path) + + """ @@ -469,6 +498,49 @@ def checkIfListsAreTheSame(theListA,theListB): + + +def parseSerialNumberFromSummary(html_path): + import re + content = "" + with open(html_path, 'r', encoding='utf-8') as html_file: + content = html_file.read() + + # Regular expression pattern to match the serial number + pattern = r'Description<\/td>\s*<\/tr>\s*\s*\s*([A-Za-z0-9]+)' + + # Search for the pattern in the HTML content + match = re.search(pattern, content, re.DOTALL) + + if match: + serial_number = match.group(1) + return serial_number + + return "?" + +def filterListOfStringsByRegex(string_list, regex_pattern): + import re + #Usage : + #input_list = ["apple", "banana", "cherry", "date", "elderberry"] + #pattern = r"^[a-c].*" # Matches strings starting with letters a, b, or c + #result = filterListOfStringsByRegex(input_list, pattern) + + matched_strings = [] + + for string in string_list: + if re.match(regex_pattern, string): + matched_strings.append(string) + + return matched_strings + + +def convertListOfRegexToListOfLists(master_string_list,regex_list): + string_list_output = [] + for regex_pattern in regex_list: + string_list_output.append(filterListOfStringsByRegex(master_string_list,regex_pattern)) + return string_list_output + + """ Check if an entry is part of a given list """ @@ -494,6 +566,10 @@ def checkIfEntryIsInConfigurationKey(configuration,theKey,theEntry): """ def getConfigurationJointIsDeclaredInHierarchy(configuration,theEntry): #------------------------------------------------------------------------------------------- + if (theEntry=="everything"): + print(bcolors.WARNING,"EVERYTHING.. is declared always.. ",bcolors.ENDC) + return 1 + #------------------------------------------------------------------------------------------- try: out = theEntry.split('_') theEntry=out[0] @@ -523,6 +599,10 @@ def getConfigurationJointIsDeclaredInHierarchy(configuration,theEntry): """ def getConfigurationJointPriority(configuration,theEntry): #------------------------------------------------------------------------------------------- + if (theEntry=="everything"): + print("EVERYTHING.. has a high priority always.. ") + return 1 + #------------------------------------------------------------------------------------------- try: out = theEntry.split('_') theEntry=out[0] @@ -556,6 +636,10 @@ def getConfigurationJointPriority(configuration,theEntry): """ def getParentNetwork(configuration,theEntry): #------------------------------------------------------------------------------------------- + if (theEntry=="everything"): + print("EVERYTHING.. has no parent.. ") + return "none" + #------------------------------------------------------------------------------------------- try: out = theEntry.split('_') theJoint=out[0] From 52c70ea93063fe5fdba6328cfb0d8444fe1716ee Mon Sep 17 00:00:00 2001 From: Ammar Qammaz Date: Sat, 9 Sep 2023 01:31:40 +0300 Subject: [PATCH 007/154] update mnet4 banner --- README.md | 2 +- doc/method.png | Bin 0 -> 465988 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 doc/method.png diff --git a/README.md b/README.md index d3ef3e7..fff2652 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # MocapNET Project -![MocapNET](https://raw.githubusercontent.com/FORTH-ModelBasedTracker/MocapNET/master/doc/mnet2.png) +![MocapNET](https://raw.githubusercontent.com/FORTH-ModelBasedTracker/MocapNET/mnet4/doc/method.png) Finishing my PhD this will probably be the *final* version of MocapNET! MocapNET 4 will deal with upperbody / lowerbody / hands / eye tracking and / facial capture diff --git a/doc/method.png b/doc/method.png new file mode 100644 index 0000000000000000000000000000000000000000..8fc8543a2a293dfb5089eb508e62df7d9eaf08ea GIT binary patch literal 465988 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