1*e1fe3e4aSElliott Hughesfrom .interpolatableHelpers import * 2*e1fe3e4aSElliott Hughes 3*e1fe3e4aSElliott Hughes 4*e1fe3e4aSElliott Hughesdef test_starting_point(glyph0, glyph1, ix, tolerance, matching): 5*e1fe3e4aSElliott Hughes if matching is None: 6*e1fe3e4aSElliott Hughes matching = list(range(len(glyph0.isomorphisms))) 7*e1fe3e4aSElliott Hughes contour0 = glyph0.isomorphisms[ix] 8*e1fe3e4aSElliott Hughes contour1 = glyph1.isomorphisms[matching[ix]] 9*e1fe3e4aSElliott Hughes m0Vectors = glyph0.greenVectors 10*e1fe3e4aSElliott Hughes m1Vectors = [glyph1.greenVectors[i] for i in matching] 11*e1fe3e4aSElliott Hughes 12*e1fe3e4aSElliott Hughes c0 = contour0[0] 13*e1fe3e4aSElliott Hughes # Next few lines duplicated below. 14*e1fe3e4aSElliott Hughes costs = [vdiff_hypot2_complex(c0[0], c1[0]) for c1 in contour1] 15*e1fe3e4aSElliott Hughes min_cost_idx, min_cost = min(enumerate(costs), key=lambda x: x[1]) 16*e1fe3e4aSElliott Hughes first_cost = costs[0] 17*e1fe3e4aSElliott Hughes proposed_point = contour1[min_cost_idx][1] 18*e1fe3e4aSElliott Hughes reverse = contour1[min_cost_idx][2] 19*e1fe3e4aSElliott Hughes 20*e1fe3e4aSElliott Hughes if min_cost < first_cost * tolerance: 21*e1fe3e4aSElliott Hughes # c0 is the first isomorphism of the m0 master 22*e1fe3e4aSElliott Hughes # contour1 is list of all isomorphisms of the m1 master 23*e1fe3e4aSElliott Hughes # 24*e1fe3e4aSElliott Hughes # If the two shapes are both circle-ish and slightly 25*e1fe3e4aSElliott Hughes # rotated, we detect wrong start point. This is for 26*e1fe3e4aSElliott Hughes # example the case hundreds of times in 27*e1fe3e4aSElliott Hughes # RobotoSerif-Italic[GRAD,opsz,wdth,wght].ttf 28*e1fe3e4aSElliott Hughes # 29*e1fe3e4aSElliott Hughes # If the proposed point is only one off from the first 30*e1fe3e4aSElliott Hughes # point (and not reversed), try harder: 31*e1fe3e4aSElliott Hughes # 32*e1fe3e4aSElliott Hughes # Find the major eigenvector of the covariance matrix, 33*e1fe3e4aSElliott Hughes # and rotate the contours by that angle. Then find the 34*e1fe3e4aSElliott Hughes # closest point again. If it matches this time, let it 35*e1fe3e4aSElliott Hughes # pass. 36*e1fe3e4aSElliott Hughes 37*e1fe3e4aSElliott Hughes num_points = len(glyph1.points[ix]) 38*e1fe3e4aSElliott Hughes leeway = 3 39*e1fe3e4aSElliott Hughes if not reverse and ( 40*e1fe3e4aSElliott Hughes proposed_point <= leeway or proposed_point >= num_points - leeway 41*e1fe3e4aSElliott Hughes ): 42*e1fe3e4aSElliott Hughes # Try harder 43*e1fe3e4aSElliott Hughes 44*e1fe3e4aSElliott Hughes # Recover the covariance matrix from the GreenVectors. 45*e1fe3e4aSElliott Hughes # This is a 2x2 matrix. 46*e1fe3e4aSElliott Hughes transforms = [] 47*e1fe3e4aSElliott Hughes for vector in (m0Vectors[ix], m1Vectors[ix]): 48*e1fe3e4aSElliott Hughes meanX = vector[1] 49*e1fe3e4aSElliott Hughes meanY = vector[2] 50*e1fe3e4aSElliott Hughes stddevX = vector[3] * 0.5 51*e1fe3e4aSElliott Hughes stddevY = vector[4] * 0.5 52*e1fe3e4aSElliott Hughes correlation = vector[5] / abs(vector[0]) 53*e1fe3e4aSElliott Hughes 54*e1fe3e4aSElliott Hughes # https://cookierobotics.com/007/ 55*e1fe3e4aSElliott Hughes a = stddevX * stddevX # VarianceX 56*e1fe3e4aSElliott Hughes c = stddevY * stddevY # VarianceY 57*e1fe3e4aSElliott Hughes b = correlation * stddevX * stddevY # Covariance 58*e1fe3e4aSElliott Hughes 59*e1fe3e4aSElliott Hughes delta = (((a - c) * 0.5) ** 2 + b * b) ** 0.5 60*e1fe3e4aSElliott Hughes lambda1 = (a + c) * 0.5 + delta # Major eigenvalue 61*e1fe3e4aSElliott Hughes lambda2 = (a + c) * 0.5 - delta # Minor eigenvalue 62*e1fe3e4aSElliott Hughes theta = atan2(lambda1 - a, b) if b != 0 else (pi * 0.5 if a < c else 0) 63*e1fe3e4aSElliott Hughes trans = Transform() 64*e1fe3e4aSElliott Hughes # Don't translate here. We are working on the complex-vector 65*e1fe3e4aSElliott Hughes # that includes more than just the points. It's horrible what 66*e1fe3e4aSElliott Hughes # we are doing anyway... 67*e1fe3e4aSElliott Hughes # trans = trans.translate(meanX, meanY) 68*e1fe3e4aSElliott Hughes trans = trans.rotate(theta) 69*e1fe3e4aSElliott Hughes trans = trans.scale(sqrt(lambda1), sqrt(lambda2)) 70*e1fe3e4aSElliott Hughes transforms.append(trans) 71*e1fe3e4aSElliott Hughes 72*e1fe3e4aSElliott Hughes trans = transforms[0] 73*e1fe3e4aSElliott Hughes new_c0 = ( 74*e1fe3e4aSElliott Hughes [complex(*trans.transformPoint((pt.real, pt.imag))) for pt in c0[0]], 75*e1fe3e4aSElliott Hughes ) + c0[1:] 76*e1fe3e4aSElliott Hughes trans = transforms[1] 77*e1fe3e4aSElliott Hughes new_contour1 = [] 78*e1fe3e4aSElliott Hughes for c1 in contour1: 79*e1fe3e4aSElliott Hughes new_c1 = ( 80*e1fe3e4aSElliott Hughes [ 81*e1fe3e4aSElliott Hughes complex(*trans.transformPoint((pt.real, pt.imag))) 82*e1fe3e4aSElliott Hughes for pt in c1[0] 83*e1fe3e4aSElliott Hughes ], 84*e1fe3e4aSElliott Hughes ) + c1[1:] 85*e1fe3e4aSElliott Hughes new_contour1.append(new_c1) 86*e1fe3e4aSElliott Hughes 87*e1fe3e4aSElliott Hughes # Next few lines duplicate from above. 88*e1fe3e4aSElliott Hughes costs = [ 89*e1fe3e4aSElliott Hughes vdiff_hypot2_complex(new_c0[0], new_c1[0]) for new_c1 in new_contour1 90*e1fe3e4aSElliott Hughes ] 91*e1fe3e4aSElliott Hughes min_cost_idx, min_cost = min(enumerate(costs), key=lambda x: x[1]) 92*e1fe3e4aSElliott Hughes first_cost = costs[0] 93*e1fe3e4aSElliott Hughes if min_cost < first_cost * tolerance: 94*e1fe3e4aSElliott Hughes # Don't report this 95*e1fe3e4aSElliott Hughes # min_cost = first_cost 96*e1fe3e4aSElliott Hughes # reverse = False 97*e1fe3e4aSElliott Hughes # proposed_point = 0 # new_contour1[min_cost_idx][1] 98*e1fe3e4aSElliott Hughes pass 99*e1fe3e4aSElliott Hughes 100*e1fe3e4aSElliott Hughes this_tolerance = min_cost / first_cost if first_cost else 1 101*e1fe3e4aSElliott Hughes log.debug( 102*e1fe3e4aSElliott Hughes "test-starting-point: tolerance %g", 103*e1fe3e4aSElliott Hughes this_tolerance, 104*e1fe3e4aSElliott Hughes ) 105*e1fe3e4aSElliott Hughes return this_tolerance, proposed_point, reverse 106