From f3224d7d34213310a9e90f1d9c871cdc41eede8a Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Mon, 20 Feb 2017 13:18:08 -0600 Subject: [PATCH] Move StatisticsPen into fontTools.pens.statisticsPen --- Lib/fontTools/pens/statisticsPen.py | 79 ++++++++++++++++++++++++++++ Snippets/interpolatable.py | 2 +- Snippets/symfont.py | 80 ----------------------------- 3 files changed, 80 insertions(+), 81 deletions(-) create mode 100644 Lib/fontTools/pens/statisticsPen.py diff --git a/Lib/fontTools/pens/statisticsPen.py b/Lib/fontTools/pens/statisticsPen.py new file mode 100644 index 000000000..18ef44545 --- /dev/null +++ b/Lib/fontTools/pens/statisticsPen.py @@ -0,0 +1,79 @@ +from __future__ import print_function, division, absolute_import +from fontTools.misc.py23 import * +import math +from fontTools.pens.momentsPen import MomentsPen + +class StatisticsPen(MomentsPen): + + # Center of mass + # https://en.wikipedia.org/wiki/Center_of_mass#A_continuous_volume + @property + def meanX(self): + return self.momentX / self.area if self.area else 0 + @property + def meanY(self): + return self.momentY / self.area if self.area else 0 + + # Var(X) = E[X^2] - E[X]^2 + @property + def varianceX(self): + return self.momentXX / self.area - self.meanX**2 if self.area else 0 + @property + def varianceY(self): + return self.momentYY / self.area - self.meanY**2 if self.area else 0 + + @property + def stddevX(self): + return math.copysign(abs(self.varianceX)**.5, self.varianceX) + @property + def stddevY(self): + return math.copysign(abs(self.varianceY)**.5, self.varianceY) + + # Covariance(X,Y) = ( E[X.Y] - E[X]E[Y] ) + @property + def covariance(self): + return self.momentXY / self.area - self.meanX*self.meanY if self.area else 0 + + # Correlation(X,Y) = Covariance(X,Y) / ( stddev(X) * stddev(Y) ) + # https://en.wikipedia.org/wiki/Pearson_product-moment_correlation_coefficient + @property + def correlation(self): + correlation = self.covariance / (self.stddevX * self.stddevY) if self.area else 0 + return correlation if abs(correlation) > 1e-3 else 0 + + @property + def slant(self): + slant = self.covariance / self.varianceY if self.area else 0 + return slant if abs(slant) > 1e-3 else 0 + + +def _test(glyphset, upem, glyphs): + from fontTools.pens.transformPen import TransformPen + from fontTools.misc.transform import Scale + + print('upem', upem) + + for glyph_name in glyphs: + print() + print("glyph:", glyph_name) + glyph = glyphset[glyph_name] + pen = StatisticsPen(glyphset=glyphset) + transformer = TransformPen(pen, Scale(1./upem)) + glyph.draw(transformer) + for item in ['area', 'momentX', 'momentY', 'momentXX', 'momentYY', 'momentXY', 'meanX', 'meanY', 'varianceX', 'varianceY', 'stddevX', 'stddevY', 'covariance', 'correlation', 'slant']: + if item[0] == '_': continue + print ("%s: %g" % (item, getattr(pen, item))) + +def main(args): + if not args: + return + filename, glyphs = args[0], args[1:] + if not glyphs: + glyphs = ['e', 'o', 'I', 'slash', 'E', 'zero', 'eight', 'minus', 'equal'] + from fontTools.ttLib import TTFont + font = TTFont(filename) + _test(font.getGlyphSet(), font['head'].unitsPerEm, glyphs) + +if __name__ == '__main__': + import sys + main(sys.argv[1:]) diff --git a/Snippets/interpolatable.py b/Snippets/interpolatable.py index f591df33c..666b0f6d8 100755 --- a/Snippets/interpolatable.py +++ b/Snippets/interpolatable.py @@ -9,7 +9,7 @@ from __future__ import print_function, division, absolute_import from fontTools.misc.py23 import * from fontTools.pens.basePen import BasePen -from symfont import StatisticsPen +from fontTools.pens.statisticsPen import StatisticsPen import itertools class PerContourOrComponentPen(BasePen): diff --git a/Snippets/symfont.py b/Snippets/symfont.py index a90b68639..eea18e375 100755 --- a/Snippets/symfont.py +++ b/Snippets/symfont.py @@ -12,11 +12,7 @@ from fontTools.misc.py23 import * import sympy as sp import sys -import math from fontTools.pens.basePen import BasePen -from fontTools.pens.transformPen import TransformPen -from fontTools.pens.momentsPen import MomentsPen -from fontTools.misc.transform import Scale from functools import partial from itertools import count @@ -188,79 +184,3 @@ MomentXYPen = partial(GreenPen, func=x*y) -# -# Glyph statistics object -# - -class StatisticsPen(MomentsPen): - - # Center of mass - # https://en.wikipedia.org/wiki/Center_of_mass#A_continuous_volume - @property - def meanX(self): - return self.momentX / self.area if self.area else 0 - @property - def meanY(self): - return self.momentY / self.area if self.area else 0 - - # Var(X) = E[X^2] - E[X]^2 - @property - def varianceX(self): - return self.momentXX / self.area - self.meanX**2 if self.area else 0 - @property - def varianceY(self): - return self.momentYY / self.area - self.meanY**2 if self.area else 0 - - @property - def stddevX(self): - return math.copysign(abs(self.varianceX)**.5, self.varianceX) - @property - def stddevY(self): - return math.copysign(abs(self.varianceY)**.5, self.varianceY) - - # Covariance(X,Y) = ( E[X.Y] - E[X]E[Y] ) - @property - def covariance(self): - return self.momentXY / self.area - self.meanX*self.meanY if self.area else 0 - - # Correlation(X,Y) = Covariance(X,Y) / ( stddev(X) * stddev(Y) ) - # https://en.wikipedia.org/wiki/Pearson_product-moment_correlation_coefficient - @property - def correlation(self): - correlation = self.covariance / (self.stddevX * self.stddevY) if self.area else 0 - return correlation if abs(correlation) > 1e-3 else 0 - - @property - def slant(self): - slant = self.covariance / self.varianceY if self.area else 0 - return slant if abs(slant) > 1e-3 else 0 - - -def test(glyphset, upem, glyphs): - print('upem', upem) - - for glyph_name in glyphs: - print() - print("glyph:", glyph_name) - glyph = glyphset[glyph_name] - pen = StatisticsPen(glyphset=glyphset) - transformer = TransformPen(pen, Scale(1./upem)) - glyph.draw(transformer) - for item in ['area', 'momentX', 'momentY', 'momentXX', 'momentYY', 'momentXY', 'meanX', 'meanY', 'varianceX', 'varianceY', 'stddevX', 'stddevY', 'covariance', 'correlation', 'slant']: - if item[0] == '_': continue - print ("%s: %g" % (item, getattr(pen, item))) - - -def main(args): - if not args: - return - filename, glyphs = args[0], args[1:] - if not glyphs: - glyphs = ['e', 'o', 'I', 'slash', 'E', 'zero', 'eight', 'minus', 'equal'] - from fontTools.ttLib import TTFont - font = TTFont(filename) - test(font.getGlyphSet(), font['head'].unitsPerEm, glyphs) - -if __name__ == '__main__': - import sys - main(sys.argv[1:])