def count_cropobjects_and_relationships(annot_file): cropobjects = parse_cropobject_list(annot_file) n_inlinks = 0 for c in cropobjects: if c.inlinks is not None: n_inlinks += len(c.inlinks) return len(cropobjects), n_inlinks
def main(args): logging.info('Starting main...') _start_time = time.clock() ########################################################################## logging.info('Import the CropObject list') if not os.path.isfile(args.annot): raise ValueError('Annotation file {0} not found!' ''.format(args.annot)) cropobjects = parse_cropobject_list(args.annot) output_cropobjects = add_staff_relationships( cropobjects, notehead_staffspace_threshold=args.notehead_staffspace_threshold) ########################################################################## logging.info('Export the combined list.') cropobject_string = export_cropobject_list(output_cropobjects) if args.export is not None: with open(args.export, 'w') as hdl: hdl.write(cropobject_string) else: print(cropobject_string) _end_time = time.clock() logging.info('add_staff_reationships.py done in {0:.3f} s' ''.format(_end_time - _start_time))
def main(args): logging.info('Starting main...') _start_time = time.clock() # Parse individual CropObject lists. cropobject_lists = [] _n_parsed_cropobjects = 0 for i, f in enumerate(args.input): cs = parse_cropobject_list(f) cropobject_lists.append(cs) # Logging progress _n_parsed_cropobjects += len(cs) if i % 10 == 0 and i > 0: _time_parsing = time.clock() - _start_time _cropobjects_per_second = _n_parsed_cropobjects / _time_parsing logging.info('Parsed {0} cropobjects in {1:.2f} s ({2:.2f} objs/s)' ''.format(_n_parsed_cropobjects, _time_parsing, _cropobjects_per_second)) # Merge the CropObject lists into one. # This is done so that the resulting object graph can be manipulated # at once, without objid clashes. cropobjects = merge_cropobject_lists(*cropobject_lists) edges = export_cropobject_graph(cropobjects) _parse_end_time = time.clock() logging.info('Parsing took {0:.2f} s'.format(_parse_end_time - _start_time)) ########################################################################## # Analysis # Here's where the results are stored, for export into various # formats. (Currently, we only print them.) stats = compute_cropobject_stats(cropobjects, edges=edges) ########################################################################## # Export if args.emit == 'print': emit_stats_pprint(stats) # More export options: # - json # - latex table _end_time = time.clock() logging.info('analyze_annotations.py done in {0:.3f} s' ''.format(_end_time - _start_time))
def main(args): logging.info('Starting main...') _start_time = time.clock() # Your code goes here ########################################################################## logging.info('Import the CropObject list') if not os.path.isfile(args.annot): raise ValueError('Annotation file {0} not found!' ''.format(args.annot)) cropobjects = parse_cropobject_list(args.annot) ########################################################################## staff_cropobjects_dict = {c.objid: c for c in cropobjects if c.clsname in STAFF_CLSNAMES} output_cropobjects = [] for c in cropobjects: if c.objid in staff_cropobjects_dict: continue new_c = copy.deepcopy(c) new_c.inlinks = [i for i in c.inlinks if i not in staff_cropobjects_dict] new_c.outlinks = [o for o in c.outlinks if o not in staff_cropobjects_dict] output_cropobjects.append(new_c) ########################################################################## logging.info('Export the stripped list.') cropobject_string = export_cropobject_list(output_cropobjects) if args.export is not None: with open(args.export, 'w') as hdl: hdl.write(cropobject_string) else: print(cropobject_string) _end_time = time.clock() logging.info('strip_staffline_symbols.py done in {0:.3f} s'.format(_end_time - _start_time))
def main(args): logging.info('Starting main...') _start_time = time.clock() # The algorithm: # - build the cost function(s) for a pair of CropObjects # - align the objects, using the cost function # First alignment: try just matching a predicted object to the nearest # true object. # First distance function: proportion of shared pixels. # Rule: if two objects don't share a pixel, they cannot be considered related. # Object classes do not factor into this so far. truth = parse_cropobject_list(args.true) prediction = parse_cropobject_list(args.prediction) _parse_time = time.clock() logging.info( 'Parsing {0} true and {1} prediction cropobjects took {2:.2f} s' ''.format(len(truth), len(prediction), _parse_time - _start_time)) r, p, f = cropobjects_rpf(truth, prediction) _rpf_time = time.clock() logging.info('Computing {0} entries of r/p/f matrices took {1:.2f} s' ''.format( len(truth) * len(prediction), _rpf_time - _parse_time)) alignment_tp = align_cropobjects(truth, prediction, fscore=f) alignment_pt = align_cropobjects(prediction, truth, fscore=f.T) # Intersect alignments _aln_tp_set = frozenset(alignment_tp) alignment_tp_symmetric = [ (t, p) for p, t in alignment_pt if (t, p) in _aln_tp_set and (truth[t].clsname == prediction[p].clsname) ] truth_not_aligned = [ t for p, t in alignment_pt if (t, p) not in alignment_tp_symmetric ] n_truth_not_aligned = len(truth_not_aligned) preds_not_aligned = [ p for t, p in alignment_tp if (t, p) not in alignment_tp_symmetric ] n_preds_not_aligned = len(preds_not_aligned) n_not_aligned = n_truth_not_aligned + n_preds_not_aligned _aln_time = time.clock() logging.info('Computing alignment took {0:.2f} s' ''.format(_aln_time - _rpf_time)) # Now compute agreement: precision and recall on pixels # of the aligned CropObjects. # We apply strict clsnames only here, after the CropObjects have been # aligned to each other using pixel metrics. _strict_clsnames = (not args.no_strict_clsnames) total_r, total_p, total_f = rpf_given_alignment( alignment_tp_symmetric, r, p, n_not_aligned=n_not_aligned, strict_clsnames=_strict_clsnames, truths=truth, predictions=prediction) if not args.print_fscore_only: print('Truth objs.:\t{0}'.format(len(truth))) print('Pred. objs.:\t{0}'.format(len(prediction))) print('Aligned objs.:\t{0}'.format(len(alignment_tp_symmetric))) print('==============================================') print('Recall:\t\t{0:.3f}\nPrecision:\t{1:.3f}\nF-score:\t{2:.3f}' ''.format(total_r, total_p, total_f)) print('') else: print('{0:.3f}'.format(total_f)) return if args.log_alignment: print('==============================================') print('Alignments:\n{0}'.format('\n'.join([ '({0}: {1}) -- ({2}: {3})'.format(truth[t].objid, truth[t].clsname, prediction[p].objid, prediction[p].clsname) for t, p in alignment_tp_symmetric ]))) print('Truth, not aligned:\n{0}'.format('\n'.join([ '({0}: {1})'.format(truth[t].objid, truth[t].clsname) for t in truth_not_aligned ]))) print('Preds, not aligned:\n{0}'.format('\n'.join([ '({0}: {1})'.format(prediction[p].objid, prediction[p].clsname) for p in preds_not_aligned ]))) ########################################################################## # Check if the alignment is a pairing -- find truth objects # with more than one prediction aligned to them. if args.analyze_alignment: t_aln_dict = collections.defaultdict(list) for i, j in alignment_tp_symmetric: t_aln_dict[i].append(prediction[j]) multiple_truths = [ truth[i] for i in t_aln_dict if len(t_aln_dict[i]) > 1 ] multiple_truths_aln_dict = { t: t_aln_dict[t] for t in t_aln_dict if len(t_aln_dict[t]) > 1 } print('Truth multi-aligned CropObject classes:\n{0}' ''.format( pprint.pformat({(truth[t].objid, truth[t].clsname): [(p.objid, p.clsname) for p in t_aln_dict[t]] for t in multiple_truths_aln_dict}))) ########################################################################## # Check if the aligned objects have the same classes if args.analyze_clsnames: different_clsnames_pairs = [] for i, j in alignment_tp_symmetric: if truth[i].clsname != prediction[j].clsname: different_clsnames_pairs.append((truth[i], prediction[j])) print('Aligned pairs with different clsnames:\n{0}' ''.format('\n'.join([ '{0}.{1}\t{2}.{3}' ''.format(t.objid, t.clsname, p.objid, p.clsname) for t, p in different_clsnames_pairs ]))) _end_time = time.clock() logging.info('analyze_agreement.py done in {0:.3f} s'.format(_end_time - _start_time))
def main(args): logging.info('Starting main...') _start_time = time.clock() # Your code goes here if not os.path.isfile(args.annot): raise ValueError('Annotation file {0} not found!' ''.format(args.annot)) cropobjects = parse_cropobject_list(args.annot) pitch_inference_engine = PitchInferenceEngine() time_inference_engine = OnsetsInferenceEngine(cropobjects=cropobjects) logging.info('Running pitch inference.') pitches, pitch_names = pitch_inference_engine.infer_pitches( cropobjects, with_names=True) # durations = inference_engine.durations_beats # Logging #pitch_names = {objid: midi2pitch_name(midi_code) # for objid, midi_code in pitches.items()} # Export logging.info('Adding pitch information to <Data> attributes.') for c in cropobjects: if c.objid in pitches: midi_pitch_code = pitches[c.objid] pitch_step, pitch_octave = pitch_names[c.objid] # beats = durations[c.objid] # if len(beats) > 1: # logging.warn('Notehead {0}: multiple possible beats: {1}' # ''.format(c.uid, beats)) # b = beats[0] # else: # b = beats[0] if c.data is None: c.data = dict() c.data['midi_pitch_code'] = midi_pitch_code c.data['normalized_pitch_step'] = pitch_step c.data['pitch_octave'] = pitch_octave logging.info('Adding duration info to <Data> attributes.') durations = time_inference_engine.durations(cropobjects) logging.info('Total durations: {0}'.format(len(durations))) for c in cropobjects: if c.objid in durations: c.data['duration_beats'] = durations[c.objid] logging.info('Some durations: {0}'.format(sorted(durations.items())[:10])) logging.info('Adding onset info to <Data> attributes.') onsets = time_inference_engine.onsets(cropobjects) logging.info('Total onsets: {0}'.format(len(onsets))) for c in cropobjects: if c.objid in onsets: c.data['onset_beats'] = onsets[c.objid] if args.export is not None: with open(args.export, 'w') as hdl: hdl.write(export_cropobject_list(cropobjects)) hdl.write('\n') else: print(export_cropobject_list(cropobjects)) if args.midi is not None: midi_builder = MIDIBuilder() mf = midi_builder.build_midi(pitches, durations, onsets) with open(args.midi, 'wb') as hdl: mf.writeFile(hdl) _end_time = time.clock() logging.info('infer_pitches.py done in {0:.3f} s'.format(_end_time - _start_time))
def main(args): logging.info('Starting main...') _start_time = time.clock() ######################################################## # Load gt image. logging.info('Loading staffline image.') # - Initialize Dataset. This checks for the root. if args.staff_imfile is None: cvc_dataset = CVC_MUSCIMA(root=args.root) args.staff_imfile = cvc_dataset.imfile(page=args.number, writer=args.writer, distortion='ideal', mode='staff_only') # - Load the image. gt = (imread(args.staff_imfile, as_grey=True) * 255).astype('uint8') # - Cast as binary mask. gt[gt > 0] = 1 ######################################################## # Locate stafflines in gt image. logging.info('Getting staffline connected components.') # - Get connected components in gt image. cc, labels, bboxes = compute_connected_components(gt) # - Use vertical dimension of CCs to determine which ones belong together # to form stafflines. (Criterion: row overlap.) n_rows, n_cols = gt.shape intervals = [[] for _ in range(n_rows) ] # For each row: which CCs have pxs on that row? for label, (t, l, b, r) in list(bboxes.items()): if label == 0: continue # Ignore very short staffline segments that can easily be artifacts # and should not affect the vertical range of the staffline anyway. if (r - l) < 8: continue for row in range(t, b): intervals[row].append(label) logging.info('Grouping staffline connected components into stafflines.') staffline_components = [ ] # For each staffline, we collect the CCs that it is made of _in_staffline = False _current_staffline_components = [] for r_labels in intervals: if not _in_staffline: # Last row did not contain staffline components. if len(r_labels) == 0: # No staffline component on current row continue else: _in_staffline = True _current_staffline_components += r_labels else: # Last row contained staffline components. if len(r_labels) == 0: # Current staffline has no more rows. staffline_components.append(set(_current_staffline_components)) _current_staffline_components = [] _in_staffline = False continue else: # Current row contains staffline components: the current # staffline continues. _current_staffline_components += r_labels logging.info('No. of stafflines, with component groups: {0}' ''.format(len(staffline_components))) # Now: merge the staffline components into one bbox/mask. logging.info( 'Merging staffline components into staffline bboxes and masks.') staffline_bboxes = [] staffline_masks = [] for sc in sorted(staffline_components, key=lambda c: min([bboxes[cc][0] for cc in c])): # Sorted top-down st, sl, sb, sr = n_rows, n_cols, 0, 0 for component in sc: t, l, b, r = bboxes[component] st, sl, sb, sr = min(t, st), min(l, sl), max(b, sb), max(r, sr) _sm = gt[st:sb, sl:sr] staffline_bboxes.append((st, sl, sb, sr)) staffline_masks.append(_sm) # Check if n. of stafflines is divisible by 5 n_stafflines = len(staffline_bboxes) logging.info('\tTotal stafflines: {0}'.format(n_stafflines)) if n_stafflines % 5 != 0: import matplotlib.pyplot as plt stafllines_mask_image = numpy.zeros(gt.shape) for i, (_sb, _sm) in enumerate(zip(staffline_bboxes, staffline_masks)): t, l, b, r = _sb stafllines_mask_image[t:b, l:r] = min(255, (i * 333) % 255 + 40) plt.imshow(stafllines_mask_image, cmap='jet', interpolation='nearest') plt.show() raise ValueError('No. of stafflines is not divisible by 5!') logging.info('Creating staff bboxes and masks.') # - Go top-down and group the stafflines by five to get staves. # (The staffline bboxes are already sorted top-down.) staff_bboxes = [] staff_masks = [] for i in range(n_stafflines // 5): _sbb = staffline_bboxes[5 * i:5 * (i + 1)] _st = min([bb[0] for bb in _sbb]) _sl = min([bb[1] for bb in _sbb]) _sb = max([bb[2] for bb in _sbb]) _sr = max([bb[3] for bb in _sbb]) staff_bboxes.append((_st, _sl, _sb, _sr)) staff_masks.append(gt[_st:_sb, _sl:_sr]) logging.info('Total staffs: {0}'.format(len(staff_bboxes))) ################################################################## # (Optionally fill in missing pixels, based on full image.) logging.info('SKIP: fill in missing pixels based on full image.') # - Load full image # - Find gap regions # - Obtain gap region masks from full image # - Add gap region mask to staffline mask. # Create the CropObjects for stafflines and staffs: # - Load corresponding annotation, to which the stafflines and # staves should be added. (This is needed to correctly set docname # and objids.) if not args.annot: cropobjects = [] next_objid = 0 dataset_namespace = 'FCNOMR' docname = os.path.splitext(os.path.basename(args.staff_imfile))[0] else: if not os.path.isfile(args.annot): raise ValueError('Annotation file {0} does not exist!'.format( args.annot)) logging.info('Creating cropobjects...') cropobjects = parse_cropobject_list(args.annot) logging.info('Non-staffline cropobjects: {0}'.format(len(cropobjects))) next_objid = max([c.objid for c in cropobjects]) + 1 dataset_namespace = cropobjects[0].dataset docname = cropobjects[0].doc # - Create the staffline CropObjects staffline_cropobjects = [] for sl_bb, sl_m in zip(staffline_bboxes, staffline_masks): uid = CropObject.build_uid(dataset_namespace, docname, next_objid) t, l, b, r = sl_bb c = CropObject(objid=next_objid, clsname=STAFFLINE_CLSNAME, top=t, left=l, height=b - t, width=r - l, mask=sl_m, uid=uid) staffline_cropobjects.append(c) next_objid += 1 if not args.stafflines_only: # - Create the staff CropObjects staff_cropobjects = [] for s_bb, s_m in zip(staff_bboxes, staff_masks): uid = CropObject.build_uid(dataset_namespace, docname, next_objid) t, l, b, r = s_bb c = CropObject(objid=next_objid, clsname=STAFF_CLSNAME, top=t, left=l, height=b - t, width=r - l, mask=s_m, uid=uid) staff_cropobjects.append(c) next_objid += 1 # - Add the inlinks/outlinks for i, sc in enumerate(staff_cropobjects): sl_from = 5 * i sl_to = 5 * (i + 1) for sl in staffline_cropobjects[sl_from:sl_to]: sl.inlinks.append(sc.objid) sc.outlinks.append(sl.objid) # Add the staffspaces. staffspace_cropobjects = [] for i, staff in enumerate(staff_cropobjects): current_stafflines = [ sc for sc in staffline_cropobjects if sc.objid in staff.outlinks ] sorted_stafflines = sorted(current_stafflines, key=lambda x: x.top) current_staffspace_cropobjects = [] # Percussion single-line staves do not have staffspaces. if len(sorted_stafflines) == 1: continue # Internal staffspace for s1, s2 in zip(sorted_stafflines[:-1], sorted_stafflines[1:]): # s1 is the UPPER staffline, s2 is the LOWER staffline # Left and right limits: to simplify things, we take the column # *intersection* of (s1, s2). This gives the invariant that # the staffspace is limited from top and bottom in each of its columns. l = max(s1.left, s2.left) r = min(s1.right, s2.right) # Shift s1, s2 to the right by this much to have the cols. align # All of these are non-negative. dl1, dl2 = l - s1.left, l - s2.left dr1, dr2 = s1.right - r, s2.right - r # The stafflines are not necessarily straight, # so top is given for the *topmost bottom edge* of the top staffline + 1 # First create mask canvas = numpy.zeros((s2.bottom - s1.top, r - l), dtype='uint8') # Paste masks into canvas. # This assumes that the top of the bottom staffline is below # the top of the top staffline... and that the bottom # of the top staffline is above the bottom of the bottom # staffline. This may not hold in very weird situations, # but it's good for now. logging.debug(s1.bounding_box, s1.mask.shape) logging.debug(s2.bounding_box, s2.mask.shape) logging.debug(canvas.shape) logging.debug( 'l={0}, dl1={1}, dl2={2}, r={3}, dr1={4}, dr2={5}' ''.format(l, dl1, dl2, r, dr1, dr2)) #canvas[:s1.height, :] += s1.mask[:, dl1:s1.width-dr1] #canvas[-s2.height:, :] += s2.mask[:, dl2:s2.width-dr2] # We have to deal with staffline interruptions. # One way to do this # is watershed fill: put markers along the bottom and top # edge, use mask * 10000 as elevation s1_above, s1_below = staffline_surroundings_mask(s1) s2_above, s2_below = staffline_surroundings_mask(s2) # Get bounding boxes of the individual stafflines' masks # that intersect with the staffspace bounding box, in terms # of the staffline bounding box. s1_t, s1_l, s1_b, s1_r = 0, dl1, \ s1.height, s1.width - dr1 s1_h, s1_w = s1_b - s1_t, s1_r - s1_l s2_t, s2_l, s2_b, s2_r = canvas.shape[0] - s2.height, dl2, \ canvas.shape[0], s2.width - dr2 s2_h, s2_w = s2_b - s2_t, s2_r - s2_l logging.debug(s1_t, s1_l, s1_b, s1_r, (s1_h, s1_w)) # We now take the intersection of s1_below and s2_above. # If there is empty space in the middle, we fill it in. staffspace_mask = numpy.ones(canvas.shape) staffspace_mask[s1_t:s1_b, :] -= ( 1 - s1_below[:, dl1:s1.width - dr1]) staffspace_mask[s2_t:s2_b, :] -= ( 1 - s2_above[:, dl2:s2.width - dr2]) ss_top = s1.top ss_bottom = s2.bottom ss_left = l ss_right = r uid = CropObject.build_uid(dataset_namespace, docname, next_objid) staffspace = CropObject(next_objid, STAFFSPACE_CLSNAME, top=ss_top, left=ss_left, height=ss_bottom - ss_top, width=ss_right - ss_left, mask=staffspace_mask, uid=uid) staffspace.inlinks.append(staff.objid) staff.outlinks.append(staffspace.objid) current_staffspace_cropobjects.append(staffspace) next_objid += 1 # Add top and bottom staffspace. # These outer staffspaces will have the width # of their bottom neighbor, and height derived # from its mask columns. # This is quite approximate, but it should do. # Upper staffspace tsl = sorted_stafflines[0] tsl_heights = tsl.mask.sum(axis=0) tss = current_staffspace_cropobjects[0] tss_heights = tss.mask.sum(axis=0) uss_top = max(0, tss.top - max(tss_heights)) uss_left = tss.left uss_width = tss.width # We use 1.5, so that large noteheads # do not "hang out" of the staffspace. uss_height = int(tss.height / 1.2) # Shift because of height downscaling: uss_top += tss.height - uss_height uss_mask = tss.mask[:uss_height, :] * 1 uid = CropObject.build_uid(dataset_namespace, docname, next_objid) staffspace = CropObject(next_objid, STAFFSPACE_CLSNAME, top=uss_top, left=uss_left, height=uss_height, width=uss_width, mask=uss_mask, uid=uid) current_staffspace_cropobjects.append(staffspace) staff.outlinks.append(staffspace.objid) staffspace.inlinks.append(staff.objid) next_objid += 1 # Lower staffspace bss = current_staffspace_cropobjects[-1] bss_heights = bss.mask.sum(axis=0) bsl = sorted_stafflines[-1] bsl_heights = bsl.mask.sum(axis=0) lss_top = bss.bottom # + max(bsl_heights) lss_left = bss.left lss_width = bss.width lss_height = int(bss.height / 1.2) lss_mask = bss.mask[:lss_height, :] * 1 uid = CropObject.build_uid(dataset_namespace, docname, next_objid) staffspace = CropObject(next_objid, STAFFSPACE_CLSNAME, top=lss_top, left=lss_left, height=lss_height, width=lss_width, mask=lss_mask, uid=uid) current_staffspace_cropobjects.append(staffspace) staff.outlinks.append(staffspace.objid) staffspace.inlinks.append(staff.objid) next_objid += 1 # ################ End of dealing with upper/lower staffspace ###### # Add to current list staffspace_cropobjects += current_staffspace_cropobjects # - Join the lists together cropobjects_with_staffs = cropobjects \ + staffline_cropobjects \ + staffspace_cropobjects \ + staff_cropobjects else: cropobjects_with_staffs = cropobjects + staffline_cropobjects logging.info('Exporting the new cropobject list: {0} objects' ''.format(len(cropobjects_with_staffs))) # - Export the combined list. cropobject_string = export_cropobject_list(cropobjects_with_staffs) if args.export is not None: with open(args.export, 'w') as hdl: hdl.write(cropobject_string) else: print(cropobject_string) _end_time = time.clock() logging.info('add_staffline_symbols.py done in {0:.3f} s' ''.format(_end_time - _start_time))
def count_cropobjects(annot_file): return len(parse_cropobject_list(annot_file))