Created
March 7, 2019 10:59
-
-
Save quanhua92/f261dc8be26d85ef65b6a8ee79d557a8 to your computer and use it in GitHub Desktop.
weight estimation of MakeHuman
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python2.7 | |
# -*- coding: utf-8 -*- | |
""" | |
MakeHuman plugin for estimating the weight of the model using BSA (body surface | |
are) based metrics. | |
**Project Name:** MakeHuman | |
**Product Home Page:** http://www.makehuman.org/ | |
**Code Home Page:** https://bitbucket.org/MakeHuman/makehuman/ | |
**Authors:** Jonas Hauquier, Marco Piccirilli | |
**Copyright(c):** MakeHuman Team 2001-2014 | |
**Licensing:** AGPL3 (http://www.makehuman.org/doc/node/the_makehuman_application.html) | |
This file is part of MakeHuman (www.makehuman.org). | |
This program is free software: you can redistribute it and/or modify | |
it under the terms of the GNU Affero General Public License as | |
published by the Free Software Foundation, either version 3 of the | |
License, or (at your option) any later version. | |
This program 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 Affero General Public License for more details. | |
You should have received a copy of the GNU Affero General Public License | |
along with this program. If not, see <http://www.gnu.org/licenses/>. | |
**Coding Standards:** See http://www.makehuman.org/node/165 | |
Abstract | |
-------- | |
Experimental plugin for doing weight estimation on the human using different | |
BSA-based formulas described in scientific literature. | |
""" | |
import gui3d | |
import mh | |
import gui | |
import log | |
import numpy as np | |
import math | |
def calculateSurface(mesh, vertGroups=None, faceMask=None): | |
""" | |
Calculate surface area of a mesh. Specify vertGroups or faceMask to | |
calculate area of a subset of the mesh and filter out other faces. | |
""" | |
if vertGroups is not None: | |
fvert = mesh.getFacesForGroups(vertGroups) | |
elif faceMask is not None: | |
f_idx = np.argwhere(faceMask)[...,0] | |
fvert = mesh.fvert[f_idx] | |
else: | |
fvert = mesh.fvert | |
if mesh.vertsPerPrimitive == 4: | |
# Split quads in triangles (assumes clockwise ordering of verts) | |
t1 = fvert[:,[0,1,2]] | |
t2 = fvert[:,[2,3,0]] | |
v1 = mesh.coord[t1] | |
v2 = mesh.coord[t2] | |
l1 = _sideLengthsFromTris(v1) | |
l2 = _sideLengthsFromTris(v2) | |
l = np.vstack([l1,l2]) | |
return _surfaceOfTris(l) | |
elif mesh.vertsPerPrimitive == 3: | |
v = mesh.coord[fvert] | |
l = _sideLengthsFromTris(v) | |
return _surfaceOfTris(l) | |
else: | |
raise RuntimeError("Only supports meshes with triangle or quad primitives.") | |
def calculateVolume(mesh, vertGroups=None, faceMask=None): | |
""" | |
Calculate the volume of a mesh. | |
Mesh is expected to be closed. | |
""" | |
if vertGroups is not None: | |
fvert = mesh.getFacesForGroups(vertGroups) | |
elif faceMask is not None: | |
f_idx = np.argwhere(faceMask)[...,0] | |
fvert = mesh.fvert[f_idx] | |
else: | |
fvert = mesh.fvert | |
if mesh.vertsPerPrimitive == 4: | |
# Split quads in triangles (assumes clockwise ordering of verts) | |
t1 = fvert[:,[0,1,2]] | |
t2 = fvert[:,[2,3,0]] | |
v1 = mesh.coord[t1] | |
v2 = mesh.coord[t2] | |
v = np.vstack([v1,v2]) | |
return _signedVolumeFromTris(v) | |
elif mesh.vertsPerPrimitive == 3: | |
v = mesh.coord[fvert] | |
return _signedVolumeFromTris(v) | |
else: | |
raise RuntimeError("Only supports meshes with triangle or quad primitives.") | |
def _sideLengthsFromTris(triVects): | |
""" | |
Calculate lengths of the sides of triangles specified by their vectors | |
in clockwise fashion. | |
triVects = [ [T1V1, T1V2, T1V3], [T2V1, T2V2, T2V3], ... ] | |
with Ti a triangle, Vi a triange vector, defined in clockwise fashion | |
and each vector (TiVi) an array [x, y, z] with vector coordinates | |
Returns a list [ [T1L1, T1L2, T1L3], [T2L1, T2L2, T2L3], ...] | |
with Ti a triangle (in the same order as in the input), and Li the length of | |
side i (a float) | |
""" | |
v = triVects | |
s = np.zeros(v.shape, dtype=np.float32) | |
# Get side vectors | |
s[:,0] = v[:,1] - v[:,0] | |
s[:,1] = v[:,2] - v[:,1] | |
s[:,2] = v[:,0] - v[:,2] | |
# Calculate lengths of sides | |
l = s[:,:,0]*s[:,:,0] + s[:,:,1]*s[:,:,1] + s[:,:,2]*s[:,:,2] | |
l = np.sqrt(l) | |
return l | |
def _surfaceOfTris(triSideLengths): | |
""" | |
Calculate total surface area of triangles with sides of specified lengths | |
triSideLengths should be an array of layout | |
[ [T1L1, T1L2, T1L3], [T2L1, T2L2, T2L3], ... ] | |
with Ti a triangle, and Li the length of the ith side of the triangle | |
TiLi should be a float. | |
Returns a float representing the total surface area. | |
""" | |
l = triSideLengths | |
# Heron's formula | |
o = ( l[:,0] +l[:,1] +l[:,2]) * \ | |
( l[:,0] +l[:,1] -l[:,2]) * \ | |
(-l[:,0] +l[:,1] +l[:,2]) * \ | |
( l[:,0] -l[:,1] +l[:,2]) | |
o = np.sqrt(o)/4 | |
return np.sum(o) | |
def _signedVolumeFromTris(triVects): | |
""" | |
Calculate volume of a set of triangles by summing signed volumes of | |
tetrahedrons between those triangles and the origin. | |
""" | |
v = triVects | |
v321 = v[:,2,0] * v[:,1,1] * v[:,0,2] | |
v231 = v[:,1,0] * v[:,2,1] * v[:,0,2] | |
v312 = v[:,2,0] * v[:,0,1] * v[:,1,2] | |
v132 = v[:,0,0] * v[:,2,1] * v[:,1,2] | |
v213 = v[:,1,0] * v[:,0,1] * v[:,2,2] | |
v123 = v[:,0,0] * v[:,1,1] * v[:,2,2] | |
signedVolume = -v321 + v231 + v312 - v132 - v213 + v123 | |
signedVolume /= 6.0 | |
vol = np.sum(signedVolume) | |
return math.fabs(vol) | |
def findVertIndex(mesh, vert): | |
""" | |
Find the index of specified vertex (as an [x, y, z] array) within mesh. | |
""" | |
matches = list(np.where(mesh.coord == vert)[0]) | |
return [idx for idx in set(matches) if matches.count(idx) > 2] | |
class WeightTaskView(gui3d.TaskView): | |
def __init__(self, category): | |
super(WeightTaskView, self).__init__(category, 'Weight Estimation') | |
self.human = gui3d.app.selectedHuman | |
box = self.addLeftWidget(gui.GroupBox('Body Metrics')) | |
self.statureLabel = box.addWidget(gui.TextView('')) | |
self.bsaLabel = box.addWidget(gui.TextView('')) | |
self.volumeLabel = box.addWidget(gui.TextView('')) | |
box = self.addLeftWidget(gui.GroupBox('Estimated Weight')) | |
self.aslaniLabel = box.addWidget(gui.TextView('')) | |
self.DuBoisLabel = box.addWidget(gui.TextView('')) | |
self.ReadingLabel = box.addWidget(gui.TextView('')) | |
self.WangLabel = box.addWidget(gui.TextView('')) | |
self.LivingstonLabel = box.addWidget(gui.TextView('')) | |
self.MostellerLabel = box.addWidget(gui.TextView('')) | |
self.AndersonLabel = box.addWidget(gui.TextView('')) | |
self.HaycockLabel = box.addWidget(gui.TextView('')) | |
self.bmiLabel = box.addWidget(gui.TextView('')) | |
def onHumanChanged(self, event): | |
self.calculateWeight() | |
def calculateWeight(self): | |
humanMesh = self.human.meshData | |
height = self.human.getHeightCm() | |
bsa = calculateSurface(humanMesh, faceMask=humanMesh.getFaceMask())/100 | |
#Shuter and Aslani, 2000 | |
weight_aslani = np.power(bsa /(np.power(height, 0.655) * 0.00949), 1/0.441) | |
#DuBois and DuBois, 1916 | |
weight_DuBois_1916=np.power(bsa /(np.power(height, 0.725) * 0.007184), 1/0.425) | |
#Reading and Freeman, 2005 | |
weight_Reading=np.power(bsa /(np.power(height, 0.5) /60), 1/0.5) | |
#Wang and Hihara, 2004 | |
weight_Wang=np.power(bsa /(np.power(height, 0.5) * 0.0168), 1/0.5) | |
#Livingston and Lee 2001 | |
weight_Livingston=np.power(bsa / 0.1173, 1/0.6466) | |
#Mosteller, 1987 | |
weight_Mosteller=np.power(bsa /(np.power(height, 0.5) * 0.0167), 1/0.5) | |
#Anderson et al., 1985 | |
weight_Anderson=np.power(bsa /(np.power(height, 0.417) * 0.0239), 1/0.517) | |
#Haycock et al., 1978 | |
weight_Haycock=np.power(bsa /(np.power(height, 0.3964) * 0.024265), 1/0.5378) | |
estimated_BMI = weight_aslani / (np.power(height/100,2)) | |
volume = calculateVolume(humanMesh, faceMask=humanMesh.getFaceMask())/100 | |
self.statureLabel.setText('Stature %.2f cm' % height) | |
self.bsaLabel.setText('BSA %.2f m^2' % bsa) | |
self.volumeLabel.setText('Volume %.2f m^3' % volume) | |
self.aslaniLabel.setText('Shuter & Aslani: %.2f kg' % weight_aslani) | |
self.DuBoisLabel.setText('DuBois: %.2f kg' % weight_DuBois_1916) | |
self.ReadingLabel.setText('Reading and Freeman: %.2f kg' % weight_Reading) | |
self.WangLabel.setText('Wang and Hihara: %.2f kg' % weight_Wang) | |
self.LivingstonLabel.setText('Livingston and Lee: %.2f kg' % weight_Livingston) | |
self.MostellerLabel.setText('Mosteller: %.2f kg' % weight_Mosteller) | |
self.AndersonLabel.setText('Anderson et al: %.2f kg' % weight_Anderson) | |
self.HaycockLabel.setText('Haycock et al.: %.2f kg' % weight_Haycock) | |
self.bmiLabel.setText('BMI from Aslani BSA: %.2f' % estimated_BMI) | |
def onShow(self, event): | |
super(WeightTaskView, self).onShow(event) | |
self.calculateWeight() | |
def load(app): | |
category = app.getCategory('Utilities') | |
taskview = category.addTask(WeightTaskView(category)) | |
def unload(app): | |
pass |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment