]> git.donarmstrong.com Git - lilypond.git/blob - buildscripts/output-distance.py
* ly/engraver-init.ly: remove Ledger_line_engraver from Voice
[lilypond.git] / buildscripts / output-distance.py
1 #!@TARGET_PYTHON@
2 import sys
3
4 sys.path.insert (0, 'python')
5 import safeeval
6
7 X_AXIS = 0
8 Y_AXIS = 1
9 INFTY = 1e6
10
11 OUTPUT_EXPRESSION_PENALTY = 100
12 ORPHAN_GROB_PENALTY = 1000
13
14 def max_distance (x1, x2):
15     dist = 0.0
16
17     for (p,q) in zip (x1, x2):
18         dist = max (abs (p-q), dist)
19         
20     return dist
21
22
23 empty_interval = (INFTY, -INFTY)
24 empty_bbox = (empty_interval, empty_interval)
25
26 def interval_length (i):
27     return max (i[1]-i[0], 0) 
28     
29 def interval_union (i1, i2):
30     return (min (i1[0], i2[0]),
31             max (i1[1], i2[1]))
32
33 def interval_intersect (i1, i2):
34     return (max (i1[0], i2[0]),
35             min (i1[1], i2[1]))
36
37 def bbox_union (b1, b2):
38     return (interval_union (b1[X_AXIS], b2[X_AXIS]),
39             interval_union (b2[Y_AXIS], b2[Y_AXIS]))
40             
41 def bbox_intersection (b1, b2):
42     return (interval_intersect (b1[X_AXIS], b2[X_AXIS]),
43             interval_intersect (b2[Y_AXIS], b2[Y_AXIS]))
44
45 def bbox_area (b):
46     return interval_length (b[X_AXIS]) * interval_length (b[Y_AXIS])
47
48 def bbox_diameter (b):
49     return max (interval_length (b[X_AXIS]),
50                 interval_length (b[Y_AXIS]))
51                 
52
53 def difference_area (a, b):
54     return bbox_area (a) - bbox_area (bbox_intersection (a,b))
55
56 class GrobSignature:
57     def __init__ (self, exp_list):
58         (self.name, self.origin, bbox_x,
59          bbox_y, self.output_expression) = tuple (exp_list)
60         
61         self.bbox = (bbox_x, bbox_y)
62         self.centroid = (bbox_x[0] + bbox_x[1], bbox_y[0] + bbox_y[1])
63
64     def __repr__ (self):
65         return '%s: (%.2f,%.2f), (%.2f,%.2f)\n' % (self.name,
66                                                  self.bbox[0][0],
67                                                  self.bbox[0][1],
68                                                  self.bbox[1][0],
69                                                  self.bbox[1][1])
70                                                  
71     def axis_centroid (self, axis):
72         return apply (sum, self.bbox[axis])  / 2 
73     
74     def centroid_distance (self, other, scale):
75         return max_distance (self.centroid, other.centroid) / scale 
76         
77     def bbox_distance (self, other):
78         divisor = bbox_area (self.bbox) + bbox_area (other.bbox)
79
80         if divisor:
81             return (difference_area (self.bbox, other.bbox) +
82                     difference_area (other.bbox, self.bbox)) / divisor
83         else:
84             return 0.0
85         
86     def expression_distance (self, other):
87         if self.output_expression == other.output_expression:
88             return 0.0
89         else:
90             return OUTPUT_EXPRESSION_PENALTY
91
92     def distance(self, other, max_distance):
93         return (self.expression_distance (other)
94                 + self.centroid_distance (other, max_distance)
95                 + self.bbox_distance (other))
96             
97 class SystemSignature:
98     def __init__ (self, grob_sigs):
99         d = {}
100         for g in grob_sigs:
101             val = d.setdefault (g.name, [])
102             val += [g]
103
104         self.grob_dict = d
105         self.set_all_bbox (grob_sigs)
106
107     def set_all_bbox (self, grobs):
108         self.bbox = empty_bbox
109         for g in grobs:
110             self.bbox = bbox_union (g.bbox, self.bbox)
111
112     def closest (self, grob_name, centroid):
113         min_d = INFTY
114         min_g = None
115         try:
116             grobs = self.grob_dict[grob_name]
117
118             for g in grobs:
119                 d = max_distance (g.centroid, centroid)
120                 if d < min_d:
121                     min_d = d
122                     min_g = g
123
124
125             return min_g
126
127         except KeyError:
128             return None
129     def grobs (self):
130         return reduce (lambda x,y: x+y, self.grob_dict.values(), [])
131
132 class SystemLink:
133     def __init__ (self, system1, system2):
134         self.system1 = system1
135         self.system2 = system2
136         
137         self.link_list_dict = {}
138         self.back_link_dict = {}
139
140         for g in system1.grobs ():
141             closest = system2.closest (g.name, g.centroid)
142             
143             self.link_list_dict.setdefault (closest, [])
144             self.link_list_dict[closest].append (g)
145             self.back_link_dict[g] = closest
146
147     def distance (self):
148         d = 0.0
149
150         scale = max (bbox_diameter (self.system1.bbox),
151                      bbox_diameter (self.system2.bbox))
152                                       
153         for (g1,g2) in self.back_link_dict.items ():
154             if g2 == None:
155                 d += ORPHAN_GROB_PENALTY
156             else:
157                 d += g1.distance (g2, scale)
158
159         for (g1,g2s) in self.link_list_dict.items ():
160             if len (g2s) != 1:
161                 print g1, g2s 
162                 d += ORPHAN_GROB_PENALTY
163
164         return d
165         
166 def read_signature_file (name):
167     exp_str = ("[%s]" % open (name).read ())
168     entries = safeeval.safe_eval (exp_str)
169
170     grob_sigs = [GrobSignature (e) for e in entries]
171     sig = SystemSignature (grob_sigs)
172     return sig
173
174
175
176
177 def compare_directories (dir1, dir2):
178
179     pass
180
181
182 ################################################################
183 # TESTING
184
185 def test ():
186     def system (x):
187         print 'invoking', x
188         stat = os.system (x)
189         assert stat == 0
190         
191     import os
192     dir = 'output-distance-test'
193
194     print 'test results in dir'
195     system ('rm -rf ' + dir)
196     os.mkdir (dir)
197     os.chdir (dir)
198     ly_template = r"""#(set! toplevel-score-handler print-score-with-defaults)
199 #(set! toplevel-music-handler
200  (lambda (p m)
201  (if (not (eq? (ly:music-property m 'void) #t))
202     (print-score-with-defaults
203     p (scorify-music m p)))))
204
205 #(ly:set-option 'point-and-click)
206
207
208
209 %(papermod)s
210
211 \relative c {
212   c^"%(userstring)s" %(extragrob)s
213   }
214 """
215
216
217     dicts = [{ 'papermod' : '',
218                'name' : '20',
219                'extragrob': '',
220                'userstring': 'test' },
221              { 'papermod' : '#(set-global-staff-size 19.5)',
222                'name' : '19',
223                'extragrob': '',
224                'userstring': 'test' },
225              { 'papermod' : '',
226                'name' : '20expr',
227                'extragrob': '',
228                'userstring': 'blabla' },
229              { 'papermod' : '',
230                'name' : '20grob',
231                'extragrob': 'c4',
232                'userstring': 'test' }]
233
234
235     for d in dicts:
236         open (d['name'] + '.ly','w').write (ly_template % d)
237         
238     names = [d['name'] for d in dicts]
239     
240     system ('lilypond -ddump-signatures -b eps ' + ' '.join (names))
241     
242     sigs = dict ((n, read_signature_file ('%s-0.signature' % n)) for n in names)
243
244     combinations = {}
245     for (n1, s1) in sigs.items():
246         for (n2, s2) in sigs.items():
247             combinations['%s-%s' % (n1, n2)] = SystemLink (s1,s2).distance ()
248             
249
250     results =   combinations.items ()
251     results.sort ()
252     for k,v in results:
253         print '%-20s' % k, v
254
255     assert combinations['20-20'] == 0.0
256     assert combinations['20-20expr'] > 50.0
257     assert combinations['20-19'] < 10.0
258
259
260 def test_sigs (a,b):
261     sa = read_signature_file (a)
262     sb = read_signature_file (b)
263     link = SystemLink (sa, sb)
264     print link.distance()
265
266 if __name__ == '__main__':
267     if sys.argv[1:]:
268         test_sigs (sys.argv[1],
269                     sys.argv[2])
270     else:
271         test ()
272
273