def test_elementbyid(self): mei = MeiElement("mei") mus = MeiElement("music") body = MeiElement("body") staff = MeiElement("staff") staff2 = MeiElement("staff") n1 = MeiElement("note") wantedId = n1.id n2 = MeiElement("note") n3 = MeiElement("note") n4 = MeiElement("note") mei.addChild(mus) mus.addChild(body) body.addChild(staff) body.addChild(staff2) staff.addChild(n1) staff.addChild(n2) staff.addChild(n3) staff2.addChild(n4) doc = MeiDocument() self.assertEqual(None, doc.getElementById(wantedId)) doc.root = mei self.assertEqual(n1, doc.getElementById(wantedId)) self.assertEqual(None, doc.getElementById("unknownID")) n5 = MeiElement("note") newid = n5.id staff2.addChild(n5) self.assertEqual(n5, doc.getElementById(newid)) staff2.removeChild(n5) self.assertEqual(None, doc.getElementById(newid))
class AomrMeiOutput(object): # define the form of a neume. # form: [ num, interval_dir... ] # e.g., clivis: [2, 'd'] # torculus: [3, 'u', 'd'] NEUME_NOTES = { 'punctum': [], 'virga': [], 'cephalicus': ['d'], 'clivis': ['d'], 'epiphonus': ['u'], 'podatus': ['u'], 'porrectus': ['d', 'u'], 'salicus': ['u', 'u'], 'scandicus': ['u', 'u'], 'torculus': ['u', 'd'], 'ancus': ['d', 'd'], # See note 1 below } # given an alternate form, how many notes does it add to the neume? ADD_NOTES = { 'flexus': ['d'], # scandicus.flexus, porrectus.flexus 'resupinus': ['u'], # torculus.resupinus } SCALE = ['a', 'b', 'c', 'd', 'e', 'f', 'g'] def __init__(self, incoming_data, original_image, page_number=None): self._recognition_results = incoming_data # self.mei = mod.mei_() self.mei = MeiElement("mei") self.staff = None # Hack: I'm not sure what staff_num is. In any case it's strange to set it to None. # I therefore set it to 0 if page_number is None. if page_number is not None: self.staff_num = int(page_number) else: self.staff_num = 0 self.glyph = None self._note_elements = None self._neume_pitches = [] # set up a basic MEI document structure # header # self.meihead = mod.meiHead_() self.meihead = MeiElement("meiHead") # self.filedesc = mod.fileDesc_() self.filedesc = MeiElement("fileDesc") # self.titlestmt = mod.titleStmt_() self.titlestmt = MeiElement("titleStmt") # self.title = mod.title_() self.title = MeiElement("title") # self.pubstmt = mod.pubStmt_() self.pubstmt = MeiElement("pubStmt") self.titlestmt.addChild(self.title) self.filedesc.addChild(self.titlestmt) self.filedesc.addChild(self.pubstmt) self.meihead.addChild(self.filedesc) self.mei.addChild(self.meihead) # music # self.music = mod.music_() self.music = MeiElement("music") self.facsimile = self._create_facsimile_element() self.surface = self._create_surface_element() self.graphic = self._create_graphic_element(original_image) lg.debug("SELF GRAPHIC:{0}".format( XmlExport.meiElementToText(self.graphic))) self.surface.addChild(self.graphic) self.facsimile.addChild(self.surface) self.music.addChild(self.facsimile) self.layout = self._create_layout_element() self.pg = self._create_page_element() if page_number: # self.pg.attributes = {"n": page_number} self.pg.addAttribute("n", page_number) self.layout.addChild(self.pg) self.music.addChild(self.layout) # self.body = mod.body_() self.body = MeiElement("body") self.music.addChild(self.body) self.mdiv = MeiElement("mdiv") # self.mdiv = mod.mdiv_() self.mdiv.addAttribute("type", "solesmes") self.body.addChild(self.mdiv) # self.score = mod.score_() self.score = MeiElement("score") self.mdiv.addChild(self.score) # self.scoredef = mod.scoreDef_() self.scoredef = MeiElement("scoreDef") self.score.addChild(self.scoredef) # self.section = mod.section_() self.section = MeiElement("section") self.pagebreak = self._create_pb_element() # self.pagebreak.attributes = {"pageref": self.pg.id} self.pagebreak.addAttribute("pageref", self.pg.id) self.section.addChild(self.pagebreak) self.score.addChild(self.section) self.staffgrp = self._create_staffgrp_element() self.staffdef = self._create_staffdef_element() self.staffdef.addAttribute("n", str(self.staff_num)) # trouble self.staffgrp.addChild(self.staffdef) self.scoredef.addChild(self.staffgrp) self.layer = self._create_layer_element() self.layer.addAttribute("n", "1") self.staffel = self._create_staff_element() self.staffel.addAttribute("n", str(self.staff_num)) # trouble self.section.addChild(self.staffel) self.staffel.addChild(self.layer) for sysnum in sorted(self._recognition_results.keys()): syst = self._recognition_results[sysnum] lg.debug("sysnum:{0}".format(sysnum)) self.system = syst self.systembreak = self._parse_system(sysnum, syst) # z = mod.zone_() z = MeiElement("zone") # z.id = self._idgen() # z.attributes = {'ulx': self.system['coord'][0], 'uly': self.system['coord'][1], \ # 'lrx': self.system['coord'][2], 'lry': self.system['coord'][3]} z.addAttribute("ulx", str(self.system['coord'][0])) z.addAttribute("uly", str(self.system['coord'][1])) z.addAttribute("lrx", str(self.system['coord'][2])) z.addAttribute("lry", str(self.system['coord'][3])) self.surface.addChild(z) # self.system.facs = z.id s = self._create_system_element() s.facs = z.id s.addAttribute("facs", s.facs) self.pg.addChild(s) self.systembreak.addAttribute("systemref", s.id) self.mei.addChild(self.music) # if not self.staffel.descendants_by_name('neume'): if not self.staffel.getDescendantsByName("neume"): self.staffgrp.removeChild(self.staffdef) self.section.removeChild(self.staffel) # self.md = MeiDocument.MeiDocument() # self.md.addelement(self.mei) self.md = MeiDocument() self.md.setRootElement(self.mei) print XmlExport.meiElementToText( self.md.getElementById(self.graphic.getId())) def _parse_system(self, sysnum, syst): sysbrk = self._create_sb_element() # sysbrk.attributes = {"n": sysnum + 1} sysnum = int(sysnum) sysbrk.addAttribute("n", "%d" % (sysnum + 1)) self.layer.addChild(sysbrk) # staffel = self._create_staff_element() # staffel.addAttribute("n", stfnum) for c in self.system['content']: # parse the glyphs per staff. self.glyph = c if c['type'] == 'neume': if not self.glyph['form']: lg.debug("Skipping glyph: {0}".format(self.glyph)) continue if self.glyph['form'][0] not in self.NEUME_NOTES.keys(): continue else: self.layer.addChild(self._create_neume_element()) elif c['type'] == 'clef': self.layer.addChild(self._create_clef_element()) elif c['type'] == 'division': self.layer.addChild(self._create_division_element()) if "final" in c['form']: self.staff_num += 1 new_staff = self._create_staff_element() new_staffdef = self._create_staffdef_element() # new_staffdef.attributes = {'n': self.staff_num} TROUBLE new_staffdef.addAttribute('n', str(self.staff_num)) # new_staff.attributes = {'n': self.staff_num} TROUBLE new_staff.addAttribute('n', str(self.staff_num)) new_layer = self._create_layer_element() # new_layer.attributes = {'n': 1} TROUBLE new_layer.addAttribute('n', str(1)) self.layer = new_layer self.staffel = new_staff self.staffdef = new_staffdef self.staffgrp.addChild(self.staffdef) self.staffel.addChild(self.layer) self.section.addChild(self.staffel) elif c['type'] == 'custos': self.layer.addChild(self._create_custos_element()) elif c['type'] == "alteration": # staffel.addChild(self._create_alteration_element()) #GVM_OLD pass return sysbrk def _create_graphic_element(self, imgfile): graphic = MeiElement("graphic") # xlink = MeiNamespace("xlink", "http://www.w3.org/1999/xlink") # ns_attr = MeiAttribute("xlink") graphic.addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink") graphic.addAttribute("xlink:href", imgfile) return graphic def _create_alteration_element(self): # accid = mod.accid_() accid = MeiElement("accid") accid.id = self._idgen() if self.glyph['form'] is "sharp": accid.addAttribute("accid", "s") elif self.glyph['form'] is "flat": accid.addAttribute("accid", "f") # zone = self._create_zone_element() # note.facs = zone.id return accid def _create_surface_element(self): # surface = mod.surface_() surface = MeiElement("surface") return surface def _create_facsimile_element(self): # facsimile = mod.facsimile_() facsimile = MeiElement("facsimile") # facsimile.id = self._idgen() return facsimile def _create_zone_element(self): zone = MeiElement("zone") # zone = mod.zone_() # zone.id = self._idgen() # zone.attributes = {'ulx': self.glyph['coord'][0], 'uly': self.glyph['coord'][1], \ # 'lrx': self.glyph['coord'][2], 'lry': self.glyph['coord'][3]} zone.addAttribute("ulx", str(self.glyph['coord'][0])) zone.addAttribute("uly", str(self.glyph['coord'][1])) zone.addAttribute("lrx", str(self.glyph['coord'][2])) zone.addAttribute("lry", str(self.glyph['coord'][3])) self.surface.addChild(zone) return zone def _create_layer_element(self): layer = MeiElement("layer") # layer = mod.layer_() # layer.id = self._idgen() lg.debug("layer:{0}".format(layer)) return layer def _create_staffgrp_element(self): # stfgrp = mod.staffGrp_() stfgrp = MeiElement("staffGrp") # stfgrp.id = self._idgen() return stfgrp def _create_staffdef_element(self): stfdef = MeiElement("staffDef") # stfdef = mod.staffDef_() # stfdef.id = self._idgen() return stfdef def _create_staff_element(self): # staff = mod.staff_() staff = MeiElement("staff") # staff.id = self._idgen() return staff def _create_sb_element(self): sb = MeiElement("sb") # sb = mod.sb_() # sb.id = self._idgen() return sb def _create_pb_element(self): # pb = mod.pb_() pb = MeiElement("pb") # pb.id = self._idgen() return pb def _create_layout_element(self): layout = MeiElement("layout") # GVM_FIXED return layout def _create_page_element(self): page = MeiElement("page") # FIXED? # page = mod.page_() # page.id = self._idgen() return page def _create_system_element(self): system = MeiElement("system") # FIXED? # system = mod.system_() # system.id = self._idgen() return system def _create_episema_element(self): epi = MeiElement("episema") # epi = mod.episema_() # epi.id = self._idgen() return epi def _create_neume_element(self): full_width_episema = False has_dot = False has_vertical_episema = False has_horizontal_episema = False has_quilisma = False this_neume_form = None local_horizontal_episema = None start_octave = self.glyph['octv'] clef_pos = self.glyph['clef_pos'] clef_type = self.glyph['clef'].split(".")[-1] # f or c. # neume = mod.neume_() neume = MeiElement("neume") # CHECK! # neume.id = self._idgen() zone = self._create_zone_element() neume.facs = zone.id neume.addAttribute("facs", neume.facs) # neumecomponent = mod.nc_() neumecomponent = MeiElement("nc") # CHECK! # neumecomponent.id = self._idgen() neume.addChild(neumecomponent) if self.glyph['form'][0] == "he": full_width_episema = True del self.glyph['form'][0] # we've removed any global he's, so # any leftovers should be local. if 'he' in self.glyph['form']: has_horizontal_episema = True if 'dot' in self.glyph['form']: has_dot = True if 'q' in self.glyph['form']: has_quilisma = True if 've' in self.glyph['form']: has_vertical_episema = True if 'inclinatum' in self.glyph['form']: # neumecomponent.attributes = {'inclinatum': 'true'} neumecomponent.addAttribute("inclinatum", "true") # neume.attributes = {'name': self.glyph['form'][0]} neume.addAttribute("name", str(self.glyph['form'][0])) if 'compound' in self.glyph['form']: # do something and create a new set of pitch contours this_neume_form = [ y for y in (self.__parse_contour(n) for n in self.glyph['form']) if y ] self._note_elements = [ y for y in (self.__parse_steps(n) for n in self.glyph['form']) if y ] else: this_neume_form = copy.deepcopy( self.NEUME_NOTES[self.glyph['form'][0]]) self._note_elements = self.glyph['form'][1:] # get the form so we can find the number of notes we need to construct. num_notes = len(this_neume_form) + 1 # we don't have an off-by-one problem here, since an added interval means an added note check_additional = [ i for i in self.ADD_NOTES.keys() if i in self.glyph['form'][1:] ] if check_additional: for f in check_additional: this_neume_form.extend(self.ADD_NOTES[f]) ## THIS SHOULD BE CHANGED. Otherwise we may end up with two attributes with the # same name. neume.addAttribute("variant", str(f)) num_notes = num_notes + len(check_additional) self._neume_pitches = [] # note elements are everything after the first form. This determines the shape a note takes. self._neume_pitches.append(self.glyph['strt_pitch']) nc = [] note_octaves = [start_octave] if num_notes > 1: # we need to figure out the rest of the pitches in the neume. ivals = [int(d) for d in self._note_elements if d.isdigit()] idx = self.SCALE.index(self.glyph['strt_pitch']) if len(ivals) != (num_notes - 1): if 'scandicus' in self.glyph['form']: diffr = abs(len(ivals) - (num_notes - 1)) num_notes = num_notes + diffr this_neume_form.extend(diffr * 'u') else: raise AomrMeiNoteIntervalMismatchError( "There is a mismatch between the number of notes and number of intervals." ) # note elements = torculus.2.2.he.ve # ivals = [2,2] # torculus = ['u','d'] this_pos = copy.deepcopy(self.glyph['strt_pos']) for n in xrange(len(ivals)): # get the direction dir = this_neume_form[n] iv = ivals[n] n_idx = idx if dir == "u": n_idx = ((idx + iv) % len(self.SCALE)) - 1 this_pos -= (iv - 1) elif dir == "d": n_idx = idx - (iv - 1) this_pos += (iv - 1) if n_idx < 0: n_idx += len(self.SCALE) idx = n_idx self._neume_pitches.append(self.SCALE[n_idx]) actual_line = 10 - (2 * (clef_pos - 1)) if clef_type: if this_pos <= actual_line: note_octaves.append(4) elif this_pos > actual_line + 7: note_octaves.append(2) else: note_octaves.append(3) # elif clef_type == "f": # if (actual_line + 3) >= this_pos > (actual_line - 3): # note_octaves.append(3) # elif this_pos < (actual_line - 3): # note_octaves.append(4) # elif this_pos > (actual_line + 3): # note_octaves.append(2) if full_width_episema is True: epi = self._create_episema_element() # epi.attributes = {"form": "horizontal"} TROUBLE epi.addAttribute("form", "horizontal") self.layer.addChild(epi) qidxs = [] if has_quilisma: self.__note_addition_figurer_outer("q", qidxs) dotidxs = [] if has_dot: self.__note_addition_figurer_outer("dot", dotidxs) veidxs = [] if has_vertical_episema: self.__note_addition_figurer_outer("ve", veidxs) heidxs = [] if has_horizontal_episema: self.__note_addition_figurer_outer("he", heidxs) for n in xrange(num_notes): p = self._neume_pitches[n] o = note_octaves[n] # lg.debug("n:{0}, p:{1}, o:{2}".format(n, p, o)) nt = self._create_note_element(p) nt.addAttribute("oct", str(o)) # lg.debug("nt.pitchname:{0}".format(nt.pname)) if n == 0 and full_width_episema is True: epi.addAttribute("startid", str(nt.id)) elif n == num_notes and full_width_episema is True: epi.addAttribute("endid", str(nt.id)) if has_quilisma: if n in qidxs: neumecomponent.addAttribute("quilisma", "true") if has_dot: if n in dotidxs: d = self._create_dot_element() nt.addChild(d) if has_vertical_episema: if n in veidxs: ep = self._create_episema_element() ep.addAttribute("form", "vertical") ep.addAttribute("startid", str(nt.id)) self.layer.addChild(ep) if has_horizontal_episema: if n in heidxs: local_horizontal_episema = self._create_episema_element() local_horizontal_episema.addAttribute("form", "horizontal") local_horizontal_episema.addAttribute( "startid", str(nt.id)) self.layer.addChild(local_horizontal_episema) if n == num_notes - 1 and local_horizontal_episema: # we've reached the end, and we have an HE we need to close up. local_horizontal_episema.addAttribute("endid", str(nt.id)) nc.append(nt) for c in nc: neumecomponent.addChild(c) return neume def _create_note_element(self, pname=None): # note = mod.note_() note = MeiElement("note") # note.id = self._idgen() note.addAttribute("pname", str(pname)) return note def _create_dot_element(self): # dot = mod.dot_() dot = MeiElement("dot") # dot.id = self._idgen() dot.addAttribute("form", "aug") return dot def _create_custos_element(self): custos = MeiElement("custos") # custos = mod.custos_() # custos.id = self._idgen() zone = self._create_zone_element() custos.facs = zone.id custos.addAttribute("pname", str(self.glyph['strt_pitch'])) custos.addAttribute("oct", str(self.glyph['octv'])) custos.addAttribute("facs", str(custos.facs)) return custos def _create_clef_element(self): clef = MeiElement("clef") # clef = mod.clef_() # clef.id = self._idgen() zone = self._create_zone_element() clef.facs = zone.id clef.addAttribute("facs", str(clef.facs)) # clef.attributes = {"line": self.glyph['strt_pos'], 'shape': self.glyph['form'][0].upper() } clef.addAttribute("line", str(self.glyph['strt_pos'])) clef.addAttribute("shape", str(self.glyph['form'][0].upper())) lg.debug("clef:{0}".format(clef)) return clef def _create_division_element(self): division = MeiElement("division") # division = mod.division_() # division.id = self._idgen() zone = self._create_zone_element() division.addAttribute("facs", str(zone.id)) if self.glyph['form']: div = str(self.glyph['form'][0]) else: div = "minor" division.addAttribute("form", div) return division def __parse_contour(self, form): # removes the contour indicator from the neume # and creates a neume form. if len(form) is 2 and (form.startswith("u") or form.startswith("d")): # do something return form[0] else: return None def __parse_steps(self, form): if len(form) is 2 and (form.startswith("u") or form.startswith("d")): return form[1] else: return None def __note_addition_figurer_outer(self, ntype, idxarray): for i, n in enumerate(self.glyph['form']): if n == ntype: j = copy.copy(i) - 1 if j == 0: idxarray.append(0) while j: if self.__is_valid_note_indicator(self.glyph['form'][j]): idxarray.append(j) break else: j -= 1 def __is_valid_note_indicator(self, form): # used to test if a form is a valid indicator of a note (and not a q, dot, or anything else) if form.isdigit(): return True elif len(form) == 2 and form.startswith("u") or form.startswith("d"): return True else: return False
class BarlineDataConverter: ''' Convert the output of the barline detection algorithm to MEI. ''' def __init__(self, staff_bb, bar_bb, verbose): ''' Initialize the converter ''' self.staff_bb = staff_bb self.bar_bb = bar_bb self.verbose = verbose def bardata_to_mei(self, sg_hint, image_path, image_width, image_height, image_dpi): ''' Perform the data conversion to mei ''' self.meidoc = MeiDocument() mei = MeiElement('mei') self.meidoc.setRootElement(mei) ########################### # MetaData # ########################### mei_head = self._create_header() mei.addChild(mei_head) ########################### # Body # ########################### music = MeiElement('music') body = MeiElement('body') mdiv = MeiElement('mdiv') score = MeiElement('score') score_def = MeiElement('scoreDef') section = MeiElement('section') # physical location data facsimile = MeiElement('facsimile') surface = MeiElement('surface') graphic = self._create_graphic(image_path, image_width, image_height) surface.addChild(graphic) # parse staff group hint to generate staff group sg_hint = sg_hint.split(" ") systems = [] for s in sg_hint: parser = nestedExpr() sg_list = parser.parseString(s).asList()[0] staff_grp, n = self._create_staff_group(sg_list, MeiElement('staffGrp'), 0) # parse repeating staff groups (systems) num_sb = 1 match = re.search('(?<=x)(\d+)$', s) if match is not None: # there are multiple systems of this staff grouping num_sb = int(match.group(0)) for i in range(num_sb): systems.append(staff_grp) if self.verbose: print "number of staves in system: %d x %d system(s)" % (n, num_sb) # there may be hidden staves in a system # make the encoded staff group the largest number of staves in a system final_staff_grp = max(systems, key=lambda x: len(x.getDescendantsByName('staffDef'))) mei.addChild(music) music.addChild(facsimile) facsimile.addChild(surface) # list of staff bounding boxes within a system staves = [] for staff_bb in self.staff_bb: # get bounding box of the staff # parse bounding box integers #staff_bb = [int(x) for x in staff_bb] staves.append(staff_bb[1:]) music.addChild(body) body.addChild(mdiv) mdiv.addChild(score) score.addChild(score_def) score_def.addChild(final_staff_grp) score.addChild(section) # parse barline data file [staffnum][barlinenum_ulx] barlines = [] for i, bar in enumerate(self.bar_bb): staff_num = int(bar[0]) ulx = bar[1] try: barlines[staff_num-1].append(ulx) except IndexError: barlines.append([ulx]) staff_offset = 0 n_measure = 1 for s_ind, s in enumerate(systems): # measures in a system s_measures = [] staff_defs = s.getDescendantsByName('staffDef') # for each staff in the system for i in range(len(staff_defs)): staff_num = staff_offset + i s_bb = staves[staff_num] # bounding box of the staff s_ulx = s_bb[0] s_uly = s_bb[1] s_lrx = s_bb[2] s_lry = s_bb[3] # for each barline on this staff try: staff_bars = barlines[staff_num] except IndexError: # a staff was found, but no bar candidates have been found on the staff continue # for each barline on this staff for n, b in enumerate(staff_bars[:-1]): # calculate bounding box of the measure m_uly = s_uly m_lry = s_lry m_ulx = b m_lrx = staff_bars[n+1] zone = self._create_zone(m_ulx, m_uly, m_lrx, m_lry) surface.addChild(zone) if len(sg_hint) == 1 or len(staff_defs) == len(final_staff_grp.getDescendantsByName('staffDef')): staff_n = str(i+1) else: # take into consideration hidden staves staff_n = i + self._calc_staff_num(len(staff_defs), [final_staff_grp]) + 1 staff = self._create_staff(staff_n, zone) #print ' ', staff_n, m_ulx, m_uly, m_lrx, m_lry try: s_measures[n].addChild(staff) except IndexError: # create a new measure measure = self._create_measure(str(n_measure)) s_measures.append(measure) section.addChild(measure) measure.addChild(staff) n_measure += 1 # calculate min/max of measure/staff bounding boxes to get measure zone self._calc_measure_zone(s_measures) staff_offset += len(staff_defs) # add a system break, if necessary if s_ind+1 < len(systems): sb = MeiElement('sb') section.addChild(sb) def _calc_staff_num(self, num_staves, staff_grps): ''' In the case where there are hidden staves, search for the correct staff number within the staff group definition. ''' if len(staff_grps) == 0: # termination condition (or no match found) return 0 else: sg_staves = staff_grps[0].getChildrenByName('staffDef') sgs = staff_grps[0].getChildrenByName('staffGrp') if num_staves == len(sg_staves): # no need to look at subsequent staff groups n = int(sg_staves[0].getAttribute('n').value) else: n = self._calc_staff_num(num_staves, sgs) return n + self._calc_staff_num(num_staves, staff_grps[1:]) def _calc_measure_zone(self, measures): ''' Calculate the bounding box of the provided measures by calculating the min and max of the bounding boxes of the staves which compose the measure. ''' # for each measure for m in measures: staff_measure_zones = [] min_ulx = sys.maxint min_uly = sys.maxint max_lrx = -sys.maxint - 1 max_lry = -sys.maxint - 1 for s in m.getChildrenByName('staff'): # have to skip # at the beginning of the id ref since using URIs s_zone = self.meidoc.getElementById(s.getAttribute('facs').value[1:]) ulx = int(s_zone.getAttribute('ulx').value) if ulx < min_ulx: min_ulx = ulx uly = int(s_zone.getAttribute('uly').value) if uly < min_uly: min_uly = uly lrx = int(s_zone.getAttribute('lrx').value) if lrx > max_lrx: max_lrx = lrx lry = int(s_zone.getAttribute('lry').value) if lry > max_lry: max_lry = lry m_zone = self._create_zone(min_ulx, min_uly, max_lrx, max_lry) m.addAttribute('facs', '#'+m_zone.getId()) surface = self.meidoc.getElementsByName('surface')[0] surface.addChild(m_zone) def _create_header(self, rodan_version='0.1'): ''' Create a meiHead element ''' mei_head = MeiElement('meiHead') today = datetime.date.today().isoformat() app_name = 'RODAN/barlineFinder' # file description file_desc = MeiElement('fileDesc') title_stmt = MeiElement('titleStmt') title = MeiElement('title') resp_stmt = MeiElement('respStmt') corp_name = MeiElement('corpName') corp_name.setValue('Distributed Digital Music Archives and Libraries Lab (DDMAL)') title_stmt.addChild(title) title_stmt.addChild(resp_stmt) resp_stmt.addChild(corp_name) pub_stmt = MeiElement('pubStmt') resp_stmt = MeiElement('respStmt') corp_name = MeiElement('corpName') corp_name.setValue('Distributed Digital Music Archives and Libraries Lab (DDMAL)') pub_stmt.addChild(resp_stmt) resp_stmt.addChild(corp_name) mei_head.addChild(file_desc) file_desc.addChild(title_stmt) file_desc.addChild(pub_stmt) # encoding description encoding_desc = MeiElement('encodingDesc') app_info = MeiElement('appInfo') application = MeiElement('application') application.addAttribute('version', rodan_version) name = MeiElement('name') name.setValue(app_name) ptr = MeiElement('ptr') ptr.addAttribute('target', 'https://github.com/DDMAL/barlineFinder') mei_head.addChild(encoding_desc) encoding_desc.addChild(app_info) app_info.addChild(application) application.addChild(name) application.addChild(ptr) # revision description revision_desc = MeiElement('revisionDesc') change = MeiElement('change') change.addAttribute('n', '1') resp_stmt = MeiElement('respStmt') corp_name = MeiElement('corpName') corp_name.setValue('Distributed Digital Music Archives and Libraries Lab (DDMAL)') change_desc = MeiElement('changeDesc') ref = MeiElement('ref') ref.addAttribute('target', '#'+application.getId()) ref.setValue(app_name) ref.setTail('.') p = MeiElement('p') p.addChild(ref) p.setValue('Encoded using ') date = MeiElement('date') date.setValue(today) mei_head.addChild(revision_desc) revision_desc.addChild(change) change.addChild(resp_stmt) resp_stmt.addChild(corp_name) change.addChild(change_desc) change_desc.addChild(p) change.addChild(date) return mei_head def _create_graphic(self, image_path, image_width, image_height): ''' Create a graphic element. ''' graphic = MeiElement('graphic') graphic.addAttribute('height', str(image_height)) graphic.addAttribute('width', str(image_width)) graphic.addAttribute('target', image_path) graphic.addAttribute('unit', 'px') return graphic def _create_staff_group(self, sg_list, staff_grp, n): ''' Recursively create the staff group element from the parsed user input of the staff groupings ''' if not sg_list: return staff_grp, n else: if type(sg_list[0]) is list: new_staff_grp, n = self._create_staff_group(sg_list[0], MeiElement('staffGrp'), n) staff_grp.addChild(new_staff_grp) else: # check for barthrough character if sg_list[0][-1] == '|': # the barlines go through all the staves in the staff group staff_grp.addAttribute('barthru', 'true') # remove the barthrough character, should now only be an integer sg_list[0] = sg_list[0][:-1] n_staff_defs = int(sg_list[0]) # get current staffDef number for i in range(n_staff_defs): staff_def = MeiElement('staffDef') staff_def.addAttribute('n', str(n+i+1)) staff_def.addAttribute('lines', '5') staff_grp.addChild(staff_def) n += n_staff_defs return self._create_staff_group(sg_list[1:], staff_grp, n) def _create_staff(self, n, zone): ''' Create a staff element, and attach a zone reference to it ''' staff = MeiElement('staff') staff.addAttribute('n', str(n)) staff.addAttribute('facs', '#'+zone.getId()) return staff def _create_measure(self, n, zone = None): ''' Create a measure element and attach a zone reference to it. The zone element is optional, since the zone of the measure is calculated once all of the staves within a measure have been added to the MEI. ''' measure = MeiElement('measure') measure.addAttribute('n', str(n)) if zone is not None: measure.addAttribute('facs', '#'+zone.getId()) return measure def _create_zone(self, ulx, uly, lrx, lry): ''' Create a zone element ''' zone = MeiElement('zone') zone.addAttribute('ulx', str(ulx)) zone.addAttribute('uly', str(uly)) zone.addAttribute('lrx', str(lrx)) zone.addAttribute('lry', str(lry)) return zone def output_mei(self, output_path): ''' Write the generated mei to disk ''' # output mei file XmlExport.meiDocumentToFile(self.meidoc, output_path)
class AomrMeiOutput(object): # define the form of a neume. # form: [ num, interval_dir... ] # e.g., clivis: [2, 'd'] # torculus: [3, 'u', 'd'] NEUME_NOTES = { 'punctum': [], 'virga': [], 'cephalicus': ['d'], 'clivis': ['d'], 'epiphonus': ['u'], 'podatus': ['u'], 'porrectus': ['d', 'u'], 'salicus': ['u', 'u'], 'scandicus': ['u', 'u'], 'torculus': ['u', 'd'], 'ancus': ['d', 'd'], # See note 1 below } # given an alternate form, how many notes does it add to the neume? ADD_NOTES = { 'flexus': ['d'], # scandicus.flexus, porrectus.flexus 'resupinus': ['u'], # torculus.resupinus } SCALE = ['a', 'b', 'c', 'd', 'e', 'f', 'g'] def __init__(self, incoming_data, original_image, page_number=None): self._recognition_results = incoming_data # self.mei = mod.mei_() self.mei = MeiElement("mei") self.staff = None # Hack: I'm not sure what staff_num is. In any case it's strange to set it to None. # I therefore set it to 0 if page_number is None. if page_number is not None: self.staff_num = int(page_number) else: self.staff_num = 0 self.glyph = None self._note_elements = None self._neume_pitches = [] # set up a basic MEI document structure # header # self.meihead = mod.meiHead_() self.meihead = MeiElement("meiHead") # self.filedesc = mod.fileDesc_() self.filedesc = MeiElement("fileDesc") # self.titlestmt = mod.titleStmt_() self.titlestmt = MeiElement("titleStmt") # self.title = mod.title_() self.title = MeiElement("title") # self.pubstmt = mod.pubStmt_() self.pubstmt = MeiElement("pubStmt") self.titlestmt.addChild(self.title) self.filedesc.addChild(self.titlestmt) self.filedesc.addChild(self.pubstmt) self.meihead.addChild(self.filedesc) self.mei.addChild(self.meihead) # music # self.music = mod.music_() self.music = MeiElement("music") self.facsimile = self._create_facsimile_element() self.surface = self._create_surface_element() self.graphic = self._create_graphic_element(original_image) lg.debug("SELF GRAPHIC:{0}".format(XmlExport.meiElementToText(self.graphic))) self.surface.addChild(self.graphic) self.facsimile.addChild(self.surface) self.music.addChild(self.facsimile) self.layout = self._create_layout_element() self.pg = self._create_page_element() if page_number: # self.pg.attributes = {"n": page_number} self.pg.addAttribute("n", page_number) self.layout.addChild(self.pg) self.music.addChild(self.layout) # self.body = mod.body_() self.body = MeiElement("body") self.music.addChild(self.body) self.mdiv = MeiElement("mdiv") # self.mdiv = mod.mdiv_() self.mdiv.addAttribute("type", "solesmes") self.body.addChild(self.mdiv) # self.score = mod.score_() self.score = MeiElement("score") self.mdiv.addChild(self.score) # self.scoredef = mod.scoreDef_() self.scoredef = MeiElement("scoreDef") self.score.addChild(self.scoredef) # self.section = mod.section_() self.section = MeiElement("section") self.pagebreak = self._create_pb_element() # self.pagebreak.attributes = {"pageref": self.pg.id} self.pagebreak.addAttribute("pageref", self.pg.id) self.section.addChild(self.pagebreak) self.score.addChild(self.section) self.staffgrp = self._create_staffgrp_element() self.staffdef = self._create_staffdef_element() self.staffdef.addAttribute("n", str(self.staff_num)) # trouble self.staffgrp.addChild(self.staffdef) self.scoredef.addChild(self.staffgrp) self.layer = self._create_layer_element() self.layer.addAttribute("n", "1") self.staffel = self._create_staff_element() self.staffel.addAttribute("n", str(self.staff_num)) # trouble self.section.addChild(self.staffel) self.staffel.addChild(self.layer) for sysnum in sorted(self._recognition_results.keys()): syst = self._recognition_results[sysnum] lg.debug("sysnum:{0}".format(sysnum)) self.system = syst self.systembreak = self._parse_system(sysnum, syst) # z = mod.zone_() z = MeiElement("zone") # z.id = self._idgen() # z.attributes = {'ulx': self.system['coord'][0], 'uly': self.system['coord'][1], \ # 'lrx': self.system['coord'][2], 'lry': self.system['coord'][3]} z.addAttribute("ulx", str(self.system['coord'][0])) z.addAttribute("uly", str(self.system['coord'][1])) z.addAttribute("lrx", str(self.system['coord'][2])) z.addAttribute("lry", str(self.system['coord'][3])) self.surface.addChild(z) # self.system.facs = z.id s = self._create_system_element() s.facs = z.id s.addAttribute("facs", s.facs) self.pg.addChild(s) self.systembreak.addAttribute("systemref", s.id) self.mei.addChild(self.music) # if not self.staffel.descendants_by_name('neume'): if not self.staffel.getDescendantsByName("neume"): self.staffgrp.removeChild(self.staffdef) self.section.removeChild(self.staffel) # self.md = MeiDocument.MeiDocument() # self.md.addelement(self.mei) self.md = MeiDocument() self.md.setRootElement(self.mei) print XmlExport.meiElementToText(self.md.getElementById(self.graphic.getId())) def _parse_system(self, sysnum, syst): sysbrk = self._create_sb_element() # sysbrk.attributes = {"n": sysnum + 1} sysnum = int(sysnum) sysbrk.addAttribute("n", "%d" % (sysnum+1)) self.layer.addChild(sysbrk) # staffel = self._create_staff_element() # staffel.addAttribute("n", stfnum) for c in self.system['content']: # parse the glyphs per staff. self.glyph = c if c['type'] == 'neume': if not self.glyph['form']: lg.debug("Skipping glyph: {0}".format(self.glyph)) continue if self.glyph['form'][0] not in self.NEUME_NOTES.keys(): continue else: self.layer.addChild(self._create_neume_element()) elif c['type'] == 'clef': self.layer.addChild(self._create_clef_element()) elif c['type'] == 'division': self.layer.addChild(self._create_division_element()) if "final" in c['form']: self.staff_num += 1 new_staff = self._create_staff_element() new_staffdef = self._create_staffdef_element() # new_staffdef.attributes = {'n': self.staff_num} TROUBLE new_staffdef.addAttribute('n', str(self.staff_num)) # new_staff.attributes = {'n': self.staff_num} TROUBLE new_staff.addAttribute('n', str(self.staff_num)) new_layer = self._create_layer_element() # new_layer.attributes = {'n': 1} TROUBLE new_layer.addAttribute('n', str(1)) self.layer = new_layer self.staffel = new_staff self.staffdef = new_staffdef self.staffgrp.addChild(self.staffdef) self.staffel.addChild(self.layer) self.section.addChild(self.staffel) elif c['type'] == 'custos': self.layer.addChild(self._create_custos_element()) elif c['type'] == "alteration": # staffel.addChild(self._create_alteration_element()) #GVM_OLD pass return sysbrk def _create_graphic_element(self, imgfile): graphic = MeiElement("graphic") # xlink = MeiNamespace("xlink", "http://www.w3.org/1999/xlink") # ns_attr = MeiAttribute("xlink") graphic.addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink") graphic.addAttribute("xlink:href", imgfile) return graphic def _create_alteration_element(self): # accid = mod.accid_() accid = MeiElement("accid") accid.id = self._idgen() if self.glyph['form'] is "sharp": accid.addAttribute("accid", "s") elif self.glyph['form'] is "flat": accid.addAttribute("accid", "f") # zone = self._create_zone_element() # note.facs = zone.id return accid def _create_surface_element(self): # surface = mod.surface_() surface = MeiElement("surface") return surface def _create_facsimile_element(self): # facsimile = mod.facsimile_() facsimile = MeiElement("facsimile") # facsimile.id = self._idgen() return facsimile def _create_zone_element(self): zone = MeiElement("zone") # zone = mod.zone_() # zone.id = self._idgen() # zone.attributes = {'ulx': self.glyph['coord'][0], 'uly': self.glyph['coord'][1], \ # 'lrx': self.glyph['coord'][2], 'lry': self.glyph['coord'][3]} zone.addAttribute("ulx", str(self.glyph['coord'][0])) zone.addAttribute("uly", str(self.glyph['coord'][1])) zone.addAttribute("lrx", str(self.glyph['coord'][2])) zone.addAttribute("lry", str(self.glyph['coord'][3])) self.surface.addChild(zone) return zone def _create_layer_element(self): layer = MeiElement("layer") # layer = mod.layer_() # layer.id = self._idgen() lg.debug("layer:{0}".format(layer)) return layer def _create_staffgrp_element(self): # stfgrp = mod.staffGrp_() stfgrp = MeiElement("staffGrp") # stfgrp.id = self._idgen() return stfgrp def _create_staffdef_element(self): stfdef = MeiElement("staffDef") # stfdef = mod.staffDef_() # stfdef.id = self._idgen() return stfdef def _create_staff_element(self): # staff = mod.staff_() staff = MeiElement("staff") # staff.id = self._idgen() return staff def _create_sb_element(self): sb = MeiElement("sb") # sb = mod.sb_() # sb.id = self._idgen() return sb def _create_pb_element(self): # pb = mod.pb_() pb = MeiElement("pb") # pb.id = self._idgen() return pb def _create_layout_element(self): layout = MeiElement("layout") # GVM_FIXED return layout def _create_page_element(self): page = MeiElement("page") # FIXED? # page = mod.page_() # page.id = self._idgen() return page def _create_system_element(self): system = MeiElement("system") # FIXED? # system = mod.system_() # system.id = self._idgen() return system def _create_episema_element(self): epi = MeiElement("episema") # epi = mod.episema_() # epi.id = self._idgen() return epi def _create_neume_element(self): full_width_episema = False has_dot = False has_vertical_episema = False has_horizontal_episema = False has_quilisma = False this_neume_form = None local_horizontal_episema = None start_octave = self.glyph['octv'] clef_pos = self.glyph['clef_pos'] clef_type = self.glyph['clef'].split(".")[-1] # f or c. # neume = mod.neume_() neume = MeiElement("neume") # CHECK! # neume.id = self._idgen() zone = self._create_zone_element() neume.facs = zone.id neume.addAttribute("facs", neume.facs) # neumecomponent = mod.nc_() neumecomponent = MeiElement("nc") # CHECK! # neumecomponent.id = self._idgen() neume.addChild(neumecomponent) if self.glyph['form'][0] == "he": full_width_episema = True del self.glyph['form'][0] # we've removed any global he's, so # any leftovers should be local. if 'he' in self.glyph['form']: has_horizontal_episema = True if 'dot' in self.glyph['form']: has_dot = True if 'q' in self.glyph['form']: has_quilisma = True if 've' in self.glyph['form']: has_vertical_episema = True if 'inclinatum' in self.glyph['form']: # neumecomponent.attributes = {'inclinatum': 'true'} neumecomponent.addAttribute("inclinatum", "true") # neume.attributes = {'name': self.glyph['form'][0]} neume.addAttribute("name", str(self.glyph['form'][0])) if 'compound' in self.glyph['form']: # do something and create a new set of pitch contours this_neume_form = [y for y in (self.__parse_contour(n) for n in self.glyph['form']) if y] self._note_elements = [y for y in (self.__parse_steps(n) for n in self.glyph['form']) if y] else: this_neume_form = copy.deepcopy(self.NEUME_NOTES[self.glyph['form'][0]]) self._note_elements = self.glyph['form'][1:] # get the form so we can find the number of notes we need to construct. num_notes = len(this_neume_form) + 1 # we don't have an off-by-one problem here, since an added interval means an added note check_additional = [i for i in self.ADD_NOTES.keys() if i in self.glyph['form'][1:]] if check_additional: for f in check_additional: this_neume_form.extend(self.ADD_NOTES[f]) ## THIS SHOULD BE CHANGED. Otherwise we may end up with two attributes with the # same name. neume.addAttribute("variant", str(f)) num_notes = num_notes + len(check_additional) self._neume_pitches = [] # note elements are everything after the first form. This determines the shape a note takes. self._neume_pitches.append(self.glyph['strt_pitch']) nc = [] note_octaves = [start_octave] if num_notes > 1: # we need to figure out the rest of the pitches in the neume. ivals = [int(d) for d in self._note_elements if d.isdigit()] idx = self.SCALE.index(self.glyph['strt_pitch']) if len(ivals) != (num_notes - 1): if 'scandicus' in self.glyph['form']: diffr = abs(len(ivals) - (num_notes - 1)) num_notes = num_notes + diffr this_neume_form.extend(diffr * 'u') else: raise AomrMeiNoteIntervalMismatchError("There is a mismatch between the number of notes and number of intervals.") # note elements = torculus.2.2.he.ve # ivals = [2,2] # torculus = ['u','d'] this_pos = copy.deepcopy(self.glyph['strt_pos']) for n in xrange(len(ivals)): # get the direction dir = this_neume_form[n] iv = ivals[n] n_idx = idx if dir == "u": n_idx = ((idx + iv) % len(self.SCALE)) - 1 this_pos -= (iv - 1) elif dir == "d": n_idx = idx - (iv - 1) this_pos += (iv - 1) if n_idx < 0: n_idx += len(self.SCALE) idx = n_idx self._neume_pitches.append(self.SCALE[n_idx]) actual_line = 10 - (2*(clef_pos-1)) if clef_type: if this_pos <= actual_line: note_octaves.append(4) elif this_pos > actual_line + 7: note_octaves.append(2) else: note_octaves.append(3) # elif clef_type == "f": # if (actual_line + 3) >= this_pos > (actual_line - 3): # note_octaves.append(3) # elif this_pos < (actual_line - 3): # note_octaves.append(4) # elif this_pos > (actual_line + 3): # note_octaves.append(2) if full_width_episema is True: epi = self._create_episema_element() # epi.attributes = {"form": "horizontal"} TROUBLE epi.addAttribute("form", "horizontal") self.layer.addChild(epi) qidxs = [] if has_quilisma: self.__note_addition_figurer_outer("q", qidxs) dotidxs = [] if has_dot: self.__note_addition_figurer_outer("dot", dotidxs) veidxs = [] if has_vertical_episema: self.__note_addition_figurer_outer("ve", veidxs) heidxs = [] if has_horizontal_episema: self.__note_addition_figurer_outer("he", heidxs) for n in xrange(num_notes): p = self._neume_pitches[n] o = note_octaves[n] # lg.debug("n:{0}, p:{1}, o:{2}".format(n, p, o)) nt = self._create_note_element(p) nt.addAttribute("oct", str(o)) # lg.debug("nt.pitchname:{0}".format(nt.pname)) if n == 0 and full_width_episema is True: epi.addAttribute("startid", str(nt.id)) elif n == num_notes and full_width_episema is True: epi.addAttribute("endid", str(nt.id)) if has_quilisma: if n in qidxs: neumecomponent.addAttribute("quilisma", "true") if has_dot: if n in dotidxs: d = self._create_dot_element() nt.addChild(d) if has_vertical_episema: if n in veidxs: ep = self._create_episema_element() ep.addAttribute("form", "vertical") ep.addAttribute("startid", str(nt.id)) self.layer.addChild(ep) if has_horizontal_episema: if n in heidxs: local_horizontal_episema = self._create_episema_element() local_horizontal_episema.addAttribute("form", "horizontal") local_horizontal_episema.addAttribute("startid", str(nt.id)) self.layer.addChild(local_horizontal_episema) if n == num_notes - 1 and local_horizontal_episema: # we've reached the end, and we have an HE we need to close up. local_horizontal_episema.addAttribute("endid", str(nt.id)) nc.append(nt) for c in nc: neumecomponent.addChild(c) return neume def _create_note_element(self, pname=None): # note = mod.note_() note = MeiElement("note") # note.id = self._idgen() note.addAttribute("pname", str(pname)) return note def _create_dot_element(self): # dot = mod.dot_() dot = MeiElement("dot") # dot.id = self._idgen() dot.addAttribute("form", "aug") return dot def _create_custos_element(self): custos = MeiElement("custos") # custos = mod.custos_() # custos.id = self._idgen() zone = self._create_zone_element() custos.facs = zone.id custos.addAttribute("pname", str(self.glyph['strt_pitch'])) custos.addAttribute("oct", str(self.glyph['octv'])) custos.addAttribute("facs", str(custos.facs)) return custos def _create_clef_element(self): clef = MeiElement("clef") # clef = mod.clef_() # clef.id = self._idgen() zone = self._create_zone_element() clef.facs = zone.id clef.addAttribute("facs", str(clef.facs)) # clef.attributes = {"line": self.glyph['strt_pos'], 'shape': self.glyph['form'][0].upper() } clef.addAttribute("line", str(self.glyph['strt_pos'])) clef.addAttribute("shape", str(self.glyph['form'][0].upper())) lg.debug("clef:{0}".format(clef)) return clef def _create_division_element(self): division = MeiElement("division") # division = mod.division_() # division.id = self._idgen() zone = self._create_zone_element() division.addAttribute("facs", str(zone.id)) if self.glyph['form']: div = str(self.glyph['form'][0]) else: div = "minor" division.addAttribute("form", div) return division def __parse_contour(self, form): # removes the contour indicator from the neume # and creates a neume form. if len(form) is 2 and (form.startswith("u") or form.startswith("d")): # do something return form[0] else: return None def __parse_steps(self, form): if len(form) is 2 and (form.startswith("u") or form.startswith("d")): return form[1] else: return None def __note_addition_figurer_outer(self, ntype, idxarray): for i,n in enumerate(self.glyph['form']): if n == ntype: j = copy.copy(i) - 1 if j == 0: idxarray.append(0) while j: if self.__is_valid_note_indicator(self.glyph['form'][j]): idxarray.append(j) break else: j -= 1 def __is_valid_note_indicator(self, form): # used to test if a form is a valid indicator of a note (and not a q, dot, or anything else) if form.isdigit(): return True elif len(form) == 2 and form.startswith("u") or form.startswith("d"): return True else: return False
class BarlineDataConverter: ''' Convert the output of the barline detection algorithm to MEI. ''' def __init__(self, staff_bb, bar_bb, verbose=False): ''' Initialize the converter ''' self.staff_bb = staff_bb self.bar_bb = bar_bb self.verbose = verbose def bardata_to_mei(self, sg_hint, image_path, image_width, image_height, image_dpi=72): ''' Perform the data conversion to mei ''' self.meidoc = MeiDocument() mei = MeiElement('mei') self.meidoc.setRootElement(mei) ########################### # MetaData # ########################### mei_head = self._create_header() mei.addChild(mei_head) ########################### # Body # ########################### music = MeiElement('music') body = MeiElement('body') mdiv = MeiElement('mdiv') score = MeiElement('score') score_def = MeiElement('scoreDef') section = MeiElement('section') # physical location data facsimile = MeiElement('facsimile') surface = MeiElement('surface') graphic = self._create_graphic(image_path, image_width, image_height) surface.addChild(graphic) # parse staff group hint to generate staff group sg_hint = sg_hint.split(" ") systems = [] for s in sg_hint: parser = nestedExpr() sg_list = parser.parseString(s).asList()[0] staff_grp, n = self._create_staff_group(sg_list, MeiElement('staffGrp'), 0) # parse repeating staff groups (systems) num_sb = 1 match = re.search('(?<=x)(\d+)$', s) if match is not None: # there are multiple systems of this staff grouping num_sb = int(match.group(0)) for i in range(num_sb): systems.append(staff_grp) if self.verbose: print "number of staves in system: %d x %d system(s)" % (n, num_sb) # there may be hidden staves in a system # make the encoded staff group the largest number of staves in a system final_staff_grp = max(systems, key=lambda x: len(x.getDescendantsByName('staffDef'))) mei.addChild(music) music.addChild(facsimile) facsimile.addChild(surface) # list of staff bounding boxes within a system staves = [] for staff_bb in self.staff_bb: # get bounding box of the staff # parse bounding box integers #staff_bb = [int(x) for x in staff_bb] staves.append(staff_bb[1:]) music.addChild(body) body.addChild(mdiv) mdiv.addChild(score) score.addChild(score_def) score_def.addChild(final_staff_grp) score.addChild(section) # parse barline data file [staffnum][barlinenum_ulx] barlines = [] for i, bar in enumerate(self.bar_bb): staff_num = int(bar[0]) ulx = bar[1] try: barlines[staff_num - 1].append(ulx) except IndexError: barlines.append([ulx]) staff_offset = 0 n_measure = 1 b1_thresh = 1.25 bn_thresh = 1.25 # for each system for s_ind, s in enumerate(systems): # measures in a system s_measures = [] staff_defs = s.getDescendantsByName('staffDef') # for each staff in the system for i in range(len(staff_defs)): staff_num = staff_offset + i s_bb = staves[staff_num] # bounding box of the staff s_ulx = s_bb[0] s_uly = s_bb[1] s_lrx = s_bb[2] s_lry = s_bb[3] # for each barline on this staff try: staff_bars = barlines[staff_num] except IndexError: # a staff was found, but no bar candidates have been found on the staff continue # check the first barline candidate # If it is sufficiently close to the beginning of the staff then ignore it. b1_x = staff_bars[0] if abs(b1_x / image_dpi - s_ulx / image_dpi) < b1_thresh: del staff_bars[0] # check the last barline candidate # if there is no candidate near the end of the interior of the staff, add one bn_x = staff_bars[-1] if bn_x < s_lrx and abs(bn_x / image_dpi - s_lrx / image_dpi) > bn_thresh: staff_bars.append(s_lrx) for n, b in enumerate(staff_bars): # calculate bounding box of the measure m_uly = s_uly m_lry = s_lry m_lrx = b if n == len(staff_bars) - 1: m_lrx = s_lrx if n == 0: m_ulx = s_ulx else: m_ulx = staff_bars[n - 1] # create staff element zone = self._create_zone(m_ulx, m_uly, m_lrx, m_lry) surface.addChild(zone) if len(sg_hint) == 1 or len(staff_defs) == len(final_staff_grp.getDescendantsByName('staffDef')): staff_n = str(i + 1) else: # take into consideration hidden staves staff_n = i + self._calc_staff_num(len(staff_defs), [final_staff_grp]) staff = self._create_staff(staff_n, zone) try: s_measures[n].addChild(staff) except IndexError: # create a new measure measure = self._create_measure(str(n_measure)) s_measures.append(measure) section.addChild(measure) measure.addChild(staff) n_measure += 1 # calculate min/max of measure/staff bounding boxes to get measure zone self._calc_measure_zone(s_measures) staff_offset += len(staff_defs) # add a system break, if necessary if s_ind + 1 < len(systems): sb = MeiElement('sb') section.addChild(sb) def _calc_staff_num(self, num_staves, staff_grps): ''' In the case where there are hidden staves, search for the correct staff number within the staff group definition. ''' if len(staff_grps) == 0: # termination condition (or no match found) return 0 else: sg_staves = staff_grps[0].getChildrenByName('staffDef') sgs = staff_grps[0].getChildrenByName('staffGrp') if num_staves == len(sg_staves): # no need to look at subsequent staff groups n = int(sg_staves[0].getAttribute('n').value) else: n = self._calc_staff_num(num_staves, sgs) return n + self._calc_staff_num(num_staves, staff_grps[1:]) def _calc_measure_zone(self, measures): ''' Calculate the bounding box of the provided measures by calculating the min and max of the bounding boxes of the staves which compose the measure. ''' # for each measure for m in measures: min_ulx = sys.maxint min_uly = sys.maxint max_lrx = -sys.maxint - 1 max_lry = -sys.maxint - 1 for s in m.getChildrenByName('staff'): # have to skip # at the beginning of the id ref since using URIs s_zone = self.meidoc.getElementById(s.getAttribute('facs').value[1:]) ulx = int(s_zone.getAttribute('ulx').value) if ulx < min_ulx: min_ulx = ulx uly = int(s_zone.getAttribute('uly').value) if uly < min_uly: min_uly = uly lrx = int(s_zone.getAttribute('lrx').value) if lrx > max_lrx: max_lrx = lrx lry = int(s_zone.getAttribute('lry').value) if lry > max_lry: max_lry = lry m_zone = self._create_zone(min_ulx, min_uly, max_lrx, max_lry) m.addAttribute('facs', '#' + m_zone.getId()) surface = self.meidoc.getElementsByName('surface')[0] surface.addChild(m_zone) def _create_header(self, rodan_version='0.1'): ''' Create a meiHead element ''' mei_head = MeiElement('meiHead') today = datetime.date.today().isoformat() app_name = 'RODAN/barlineFinder' # file description file_desc = MeiElement('fileDesc') title_stmt = MeiElement('titleStmt') title = MeiElement('title') resp_stmt = MeiElement('respStmt') corp_name = MeiElement('corpName') corp_name.setValue('Distributed Digital Music Archives and Libraries Lab (DDMAL)') title_stmt.addChild(title) title_stmt.addChild(resp_stmt) resp_stmt.addChild(corp_name) pub_stmt = MeiElement('pubStmt') resp_stmt = MeiElement('respStmt') corp_name = MeiElement('corpName') corp_name.setValue('Distributed Digital Music Archives and Libraries Lab (DDMAL)') pub_stmt.addChild(resp_stmt) resp_stmt.addChild(corp_name) mei_head.addChild(file_desc) file_desc.addChild(title_stmt) file_desc.addChild(pub_stmt) # encoding description encoding_desc = MeiElement('encodingDesc') app_info = MeiElement('appInfo') application = MeiElement('application') application.addAttribute('version', rodan_version) name = MeiElement('name') name.setValue(app_name) ptr = MeiElement('ptr') ptr.addAttribute('target', 'https://github.com/DDMAL/barlineFinder') mei_head.addChild(encoding_desc) encoding_desc.addChild(app_info) app_info.addChild(application) application.addChild(name) application.addChild(ptr) # revision description revision_desc = MeiElement('revisionDesc') change = MeiElement('change') change.addAttribute('n', '1') resp_stmt = MeiElement('respStmt') corp_name = MeiElement('corpName') corp_name.setValue('Distributed Digital Music Archives and Libraries Lab (DDMAL)') change_desc = MeiElement('changeDesc') ref = MeiElement('ref') ref.addAttribute('target', '#' + application.getId()) ref.setValue(app_name) ref.setTail('.') p = MeiElement('p') p.addChild(ref) p.setValue('Encoded using ') date = MeiElement('date') date.setValue(today) mei_head.addChild(revision_desc) revision_desc.addChild(change) change.addChild(resp_stmt) resp_stmt.addChild(corp_name) change.addChild(change_desc) change_desc.addChild(p) change.addChild(date) return mei_head def _create_graphic(self, image_path, image_width, image_height): ''' Create a graphic element. ''' graphic = MeiElement('graphic') graphic.addAttribute('height', str(image_height)) graphic.addAttribute('width', str(image_width)) graphic.addAttribute('target', str(image_path)) graphic.addAttribute('unit', 'px') return graphic def _create_staff_group(self, sg_list, staff_grp, n): ''' Recursively create the staff group element from the parsed user input of the staff groupings ''' if not sg_list: return staff_grp, n else: if type(sg_list[0]) is list: new_staff_grp, n = self._create_staff_group(sg_list[0], MeiElement('staffGrp'), n) staff_grp.addChild(new_staff_grp) else: # check for barthrough character if sg_list[0][-1] == '|': # the barlines go through all the staves in the staff group staff_grp.addAttribute('barthru', 'true') # remove the barthrough character, should now only be an integer sg_list[0] = sg_list[0][:-1] n_staff_defs = int(sg_list[0]) # get current staffDef number for i in range(n_staff_defs): staff_def = MeiElement('staffDef') staff_def.addAttribute('n', str(n + i + 1)) staff_def.addAttribute('lines', '5') staff_grp.addChild(staff_def) n += n_staff_defs return self._create_staff_group(sg_list[1:], staff_grp, n) def _create_staff(self, n, zone): ''' Create a staff element, and attach a zone reference to it ''' staff = MeiElement('staff') staff.addAttribute('n', str(n)) staff.addAttribute('facs', '#' + zone.getId()) return staff def _create_measure(self, n, zone=None): ''' Create a measure element and attach a zone reference to it. The zone element is optional, since the zone of the measure is calculated once all of the staves within a measure have been added to the MEI. ''' measure = MeiElement('measure') measure.addAttribute('n', str(n)) if zone is not None: measure.addAttribute('facs', '#' + zone.getId()) return measure def _create_zone(self, ulx, uly, lrx, lry): ''' Create a zone element ''' zone = MeiElement('zone') zone.addAttribute('ulx', str(ulx)) zone.addAttribute('uly', str(uly)) zone.addAttribute('lrx', str(lrx)) zone.addAttribute('lry', str(lry)) return zone def output_mei(self, output_path): ''' Write the generated mei to disk ''' # output mei file XmlExport.meiDocumentToFile(self.meidoc, output_path) def get_wrapped_mei(self): ''' Return the generated mei document ''' mw = MeiWrapper(self.meidoc) return mw