def __call__(self,im_staffonly,alpha,n,p=0.5,random_seed=0): from gamera.core import RGBPixel,FloatPoint from gamera.toolkits.musicstaves.stafffinder import StafflineSkeleton seed(random_seed) im_full=self im_staffless=im_full.xor_image(im_staffonly) def_staffonly=im_staffonly.image_copy() [first_x, last_x, stafflines, thickness]=find_stafflines_int(im_staffonly) bnp=binomial(n,p) # walk along all pixels of all stafflines for y in stafflines: for x in range(first_x, last_x): # now do the russian roulette if random()<alpha: w=bnp.random() if w>0: def_staffonly.draw_filled_rect(FloatPoint(x-w/2,y-thickness),FloatPoint(x+w/2+w%2-1,y+thickness),RGBPixel(0,0,0)) def_full=im_staffless.or_image(def_staffonly) # construct skeletons (in fact they didn't change) staffline_skel=[] for y in stafflines: skel=StafflineSkeleton() skel.left_x=first_x # all stafflines are completely straight skel.y_list=(last_x-first_x+1)*[y] staffline_skel.append(skel) return [def_full,def_staffonly,staffline_skel]
def __call__(self, im_staffonly, ampx, period=1): from gamera.toolkits.musicstaves.stafffinder import StafflineSkeleton from gamera.plugins.deformation import wave im_full = self [first_x, last_x, stafflines, thickness] = find_stafflines_int(im_staffonly) periodwidth = int((last_x - first_x) * 2 / period) amp = int(ampx * periodwidth) # the wave-plugin does the main work wave_staffonly = im_staffonly.wave(amp, periodwidth, 0, 0, first_x) wave_full = im_full.wave(amp, periodwidth, 0, 0, first_x) # now we put together the new staffline skeletons staffline_skel = [] for i in stafflines: skel = StafflineSkeleton() skel.left_x = first_x skel.y_list = [] staffline_skel.append(skel) xfactor = 2 * pi / periodwidth yfactor = -amp * 0.5 yoffset = amp * 0.5 for x in range(last_x - first_x + 1): y_shift = int(yoffset + yfactor * sin(x * xfactor)) # y_shift applies to all stafflines at the same x for i in range(len(stafflines)): staffline_skel[i].y_list.append(stafflines[i] + y_shift) return [wave_full, wave_staffonly, staffline_skel]
def __call__(self, im_staffonly, ampx, period=1): from gamera.toolkits.musicstaves.stafffinder import StafflineSkeleton from gamera.plugins.deformation import wave im_full=self [first_x, last_x, stafflines, thickness] =find_stafflines_int(im_staffonly) periodwidth=int((last_x-first_x)*2/period) amp=int(ampx*periodwidth) # the wave-plugin does the main work wave_staffonly=im_staffonly.wave(amp,periodwidth,0,0,first_x) wave_full=im_full.wave(amp,periodwidth,0,0,first_x) # now we put together the new staffline skeletons staffline_skel=[] for i in stafflines: skel=StafflineSkeleton() skel.left_x=first_x skel.y_list=[] staffline_skel.append(skel) xfactor=2*pi/periodwidth yfactor=-amp*0.5 yoffset=amp*0.5 for x in range(last_x-first_x+1): y_shift=int(yoffset+yfactor*sin(x*xfactor)) # y_shift applies to all stafflines at the same x for i in range(len(stafflines)): staffline_skel[i].y_list.append(stafflines[i]+y_shift) return [wave_full,wave_staffonly,staffline_skel]
def __call__(self,im_staffonly, ratio): from gamera.core import Image,RGBPixel,Point from gamera.toolkits.musicstaves.stafffinder import StafflineSkeleton im_full=self im_staffless=im_full.xor_image(im_staffonly) [first_x, last_x, stafflines, thickness]=find_stafflines_int(im_staffonly) # ratio=staffline_height/staffspace_height thickness=(stafflines[1]-stafflines[0])/(1/ratio+1) im_newlines=Image(im_full.ul,im_full.size) # we just draw the lines ourselves for y in stafflines: im_newlines.draw_line(Point(first_x,y),Point(last_x,y),RGBPixel(255,255,255),thickness) # new full: lines OR symbols def_full=im_staffless.or_image(im_newlines) # new staffonly: lines AND NOT symbols def_staffonly=im_staffless.image_copy() def_staffonly.invert() def_staffonly.and_image(im_newlines,True) # staffless image doesn't change def_staffless=im_staffless.image_copy() # construct skeletons staffline_skel=[] for y in stafflines: skel=StafflineSkeleton() skel.left_x=first_x # all stafflines are completely straight skel.y_list=(last_x-first_x+1)*[y] staffline_skel.append(skel) return [def_full,def_staffonly,staffline_skel]
def __call__(self, im_staffonly, alpha, n, p=0.5, random_seed=0): from gamera.core import RGBPixel, FloatPoint from gamera.toolkits.musicstaves.stafffinder import StafflineSkeleton seed(random_seed) im_full = self im_staffless = im_full.xor_image(im_staffonly) def_staffonly = im_staffonly.image_copy() [first_x, last_x, stafflines, thickness] = find_stafflines_int(im_staffonly) bnp = binomial(n, p) # walk along all pixels of all stafflines for y in stafflines: for x in range(first_x, last_x): # now do the russian roulette if random() < alpha: w = bnp.random() if w > 0: def_staffonly.draw_filled_rect( FloatPoint(x - w / 2, y - thickness), FloatPoint(x + w / 2 + w % 2 - 1, y + thickness), RGBPixel(0, 0, 0)) def_full = im_staffless.or_image(def_staffonly) # construct skeletons (in fact they didn't change) staffline_skel = [] for y in stafflines: skel = StafflineSkeleton() skel.left_x = first_x # all stafflines are completely straight skel.y_list = (last_x - first_x + 1) * [y] staffline_skel.append(skel) return [def_full, def_staffonly, staffline_skel]
def __call__(self, im_staffonly, angle): from gamera.core import RGBPixel from gamera.toolkits.musicstaves.stafffinder import StafflineSkeleton im_full = self im_staffless = im_full.xor_image(im_staffonly) # let someone else do the work for us # we use -angle because rotate() uses the wrong direction rot_staffonly = im_staffonly.rotate(-angle, RGBPixel(0, 0, 0)) rot_full = im_full.rotate(-angle, RGBPixel(0, 0, 0)) def apply_transformation(mat, pt): return [ pt[0] * mat[0] + pt[1] * mat[1] + mat[2], pt[0] * mat[3] + pt[1] * mat[4] + mat[5] ] # find offset by which the image is moved while rotating it m = [ cos(angle * pi / 180), sin(angle * pi / 180), 0, -sin(angle * pi / 180), cos(angle * pi / 180), 0 ] pts_orig = [[0, 0], [im_full.width - 1, 0], [0, im_full.height - 1], [im_full.width - 1, im_full.height - 1]] pts = [apply_transformation(m, p) for p in pts_orig] m[2] = -min([p[0] for p in pts]) m[5] = -min([p[1] for p in pts]) # find the stafflines in the original image [first_x, last_x, stafflines, thickness] = find_stafflines_int(im_staffonly) # rotate/move them and wrap them inside a list of StafflineSkeleton objects staffline_skel = [] for y in stafflines: startpt = apply_transformation(m, [first_x, y]) endpt = apply_transformation(m, [last_x, y]) # swap point, if the rotation was so hard, that what was left is now right (ie. angle>90 or <-90) if startpt[0] > endpt[0]: tempswap = startpt startpt = endpt endpt = tempswap o = StafflineSkeleton() o.left_x = startpt[0] o.y_list = [startpt[1]] ty = startpt[1] dy = (endpt[1] - startpt[1]) / (endpt[0] - startpt[0]) for n in range(int(round(endpt[0] - startpt[0]))): ty = ty + dy o.y_list.append(ty) staffline_skel.append(o) return [rot_full, rot_staffonly, staffline_skel]
def __call__(self, im_staffonly, min, max, c=0.5, random_seed=0): from gamera.core import Image, RGBPixel from gamera.toolkits.musicstaves.stafffinder import StafflineSkeleton seed(random_seed) im_full = self im_staffless = im_full.xor_image(im_staffonly) [first_x, last_x, stafflines, thickness] = find_stafflines_int(im_staffonly) def_staffonly = Image(im_full.ul, im_full.size) # state machine for the thickness states = states_mh(max - min + 1, c) # draw the deformed stafflines in a blank image last_thickness = 0 even_shift = 0 for l in range(len(stafflines)): y = stafflines[l] for x in range(first_x, last_x + 1): thickness = states.next() + min y_st = y - thickness / 2 if thickness % 2 == 0: if thickness != last_thickness: if random() < 0.5: even_shift = 1 else: even_shift = 0 y_st = y_st + even_shift if thickness > 0: def_staffonly.draw_line((x, y_st), (x, y_st + thickness - 1), RGBPixel(255, 255, 255)) last_thickness = thickness # stafflines = stafflines AND NOT symbols im_staffless_invert = im_staffless.image_copy() im_staffless_invert.invert() def_staffonly.and_image(im_staffless_invert, True) # full = symbols OR stafflines def_full = im_staffless.image_copy() def_full.or_image(def_staffonly, True) # construct skeletons (in fact they didn't change) staffline_skel = [] for y in stafflines: skel = StafflineSkeleton() skel.left_x = first_x # all stafflines are completely straight skel.y_list = (last_x - first_x + 1) * [y] staffline_skel.append(skel) return [def_full, def_staffonly, staffline_skel]
def __call__(self, im_staffonly, p, n, k=2, connectivity=2, random_seed=0): from gamera.toolkits.musicstaves.stafffinder import StafflineSkeleton # the C++ plugin only returns the images [staffdef, nostaffdef] = _staffdeformation.white_speckles_parallel_noskel(self, im_staffonly, p, n, k, connectivity, random_seed) # create staffline skeletons [first_x, last_x, stafflines, thickness] =find_stafflines_int(im_staffonly) staffline_skel=[] for y in stafflines: skel=StafflineSkeleton() skel.left_x=first_x # all stafflines are completely straight skel.y_list=(last_x-first_x+1)*[y] staffline_skel.append(skel) return [staffdef, nostaffdef, staffline_skel]
def __call__(self): from gamera.toolkits.musicstaves.stafffinder import StafflineSkeleton [first_x, last_x, stafflines, thickness]=find_stafflines_int(self) # construct skeletons (in fact they didn't change) staffline_skel=[] for y in stafflines: skel=StafflineSkeleton() skel.left_x=first_x # all stafflines are completely straight skel.y_list=(last_x-first_x+1)*[y] staffline_skel.append(skel) return staffline_skel
def __call__(self): from gamera.toolkits.musicstaves.stafffinder import StafflineSkeleton [first_x, last_x, stafflines, thickness] = find_stafflines_int(self) # construct skeletons (in fact they didn't change) staffline_skel = [] for y in stafflines: skel = StafflineSkeleton() skel.left_x = first_x # all stafflines are completely straight skel.y_list = (last_x - first_x + 1) * [y] staffline_skel.append(skel) return staffline_skel
def __call__(self,im_staffonly,min,max,c=0.5,random_seed=0): from gamera.core import Image,RGBPixel from gamera.toolkits.musicstaves.stafffinder import StafflineSkeleton seed(random_seed) im_full=self im_staffless=im_full.xor_image(im_staffonly) [first_x, last_x, stafflines, thickness]=find_stafflines_int(im_staffonly) def_staffonly=Image(im_full.ul,im_full.size) # state machine for the thickness states=states_mh(max-min+1,c) # draw the deformed stafflines in a blank image last_thickness=0 even_shift=0 for l in range(len(stafflines)): y=stafflines[l] for x in range(first_x,last_x+1): thickness=states.next()+min y_st=y-thickness/2 if thickness%2==0: if thickness!=last_thickness: if random()<0.5: even_shift=1 else: even_shift=0 y_st=y_st+even_shift if thickness>0: def_staffonly.draw_line((x,y_st),(x,y_st+thickness-1),RGBPixel(255,255,255)) last_thickness=thickness # stafflines = stafflines AND NOT symbols im_staffless_invert=im_staffless.image_copy() im_staffless_invert.invert() def_staffonly.and_image(im_staffless_invert,True) # full = symbols OR stafflines def_full=im_staffless.image_copy() def_full.or_image(def_staffonly,True) # construct skeletons (in fact they didn't change) staffline_skel=[] for y in stafflines: skel=StafflineSkeleton() skel.left_x=first_x # all stafflines are completely straight skel.y_list=(last_x-first_x+1)*[y] staffline_skel.append(skel) return [def_full,def_staffonly,staffline_skel]
def __call__(self, im_staffonly, angle): from gamera.core import RGBPixel from gamera.toolkits.musicstaves.stafffinder import StafflineSkeleton im_full=self im_staffless=im_full.xor_image(im_staffonly) # let someone else do the work for us # we use -angle because rotate() uses the wrong direction rot_staffonly=im_staffonly.rotate(-angle,RGBPixel(0,0,0)) rot_full=im_full.rotate(-angle,RGBPixel(0,0,0)) def apply_transformation(mat,pt): return [pt[0]*mat[0]+pt[1]*mat[1]+mat[2],pt[0]*mat[3]+pt[1]*mat[4]+mat[5]] # find offset by which the image is moved while rotating it m=[ cos(angle*pi/180),sin(angle*pi/180),0, -sin(angle*pi/180),cos(angle*pi/180),0] pts_orig=[[0,0], [im_full.width-1,0], [0,im_full.height-1], [im_full.width-1,im_full.height-1]] pts=[apply_transformation(m,p) for p in pts_orig] m[2]=-min([p[0] for p in pts]) m[5]=-min([p[1] for p in pts]) # find the stafflines in the original image [first_x, last_x, stafflines, thickness]=find_stafflines_int(im_staffonly) # rotate/move them and wrap them inside a list of StafflineSkeleton objects staffline_skel=[] for y in stafflines: startpt=apply_transformation(m,[first_x,y]) endpt=apply_transformation(m,[last_x,y]) # swap point, if the rotation was so hard, that what was left is now right (ie. angle>90 or <-90) if startpt[0]>endpt[0]: tempswap=startpt startpt=endpt endpt=tempswap o=StafflineSkeleton() o.left_x=startpt[0] o.y_list=[startpt[1]] ty=startpt[1] dy=(endpt[1]-startpt[1])/(endpt[0]-startpt[0]) for n in range(int(round(endpt[0]-startpt[0]))): ty=ty+dy o.y_list.append(ty) staffline_skel.append(o) return [rot_full,rot_staffonly,staffline_skel]
def find_staves(self, with_trimming=True, with_deletion=True, \ with_staff_fixing=True, \ enable_strong_staff_pixels=False): """Method for finding the staff lines. Signature: ``find_staves(with_trimming=True, with_deletion=True, with_staff_fixing=True, enable_strong_staff_pixels=False)`` with - *with_trimming*: Trims staff sets where white space or ornamentations are found. - *with_deletion*: If true, the image will be processed once and will create an image comprised of only found staves and then the code is run again. More accurate for images with a lot of lyrics or ornamentation. - *with_staff_fixing*: Uses the slopes of staff sets to fix staff lines that differ wildly from the slope at specific intervals. - *enable_strong_staff_pixels*: Experimental method that reduces the weights of vertical runs that are the exact width of staffline_height and are exactly staffspace_height away from the closest black pixel. This method fills the *self.linelist* attribute for further processing. """ # Get the skeleton list skeleton_list = self.image.get_stable_path_staff_skeletons( \ with_trimming=with_trimming, \ with_staff_fixing=with_staff_fixing, \ enable_strong_staff_pixels=enable_strong_staff_pixels, \ staffline_height=self.staffline_height, \ staffspace_height=self.staffspace_height) # copy over to stafflist self.linelist = [] print len(skeleton_list) for g in skeleton_list: newstaff = [] for s in g: skel = StafflineSkeleton() skel.left_x = s[0] skel.y_list = s[1] newstaff.append(skel) self.linelist.append(newstaff)
def __call__(self, im_staffonly, p, n, k=2, connectivity=2, random_seed=0): from gamera.toolkits.musicstaves.stafffinder import StafflineSkeleton # the C++ plugin only returns the images [staffdef, nostaffdef] = _staffdeformation.white_speckles_parallel_noskel( self, im_staffonly, p, n, k, connectivity, random_seed) # create staffline skeletons [first_x, last_x, stafflines, thickness] = find_stafflines_int(im_staffonly) staffline_skel = [] for y in stafflines: skel = StafflineSkeleton() skel.left_x = first_x # all stafflines are completely straight skel.y_list = (last_x - first_x + 1) * [y] staffline_skel.append(skel) return [staffdef, nostaffdef, staffline_skel]
def __call__(self, im_staffonly, eta, a0, a, b0, b, k=2, random_seed=0): from gamera.toolkits.musicstaves.stafffinder import StafflineSkeleton # the C++ plugin only returns a single image # random_seed should guarantee compatible results # additionally we subtract staffless image from nostaffdef # so that not too many symbol pixels are redefined as staff pixels staffdef = _staffdeformation.degrade_kanungo_single_image(self, eta, a0, a, b0, b, k, random_seed) nostaffdef = _staffdeformation.degrade_kanungo_single_image(im_staffonly, eta, a0, a, b0, b, k, random_seed) im_nostaff = self.subtract_images(im_staffonly) nostaffdef = nostaffdef.subtract_images(im_nostaff) # create staffline skeletons [first_x, last_x, stafflines, thickness] =find_stafflines_int(im_staffonly) staffline_skel=[] for y in stafflines: skel=StafflineSkeleton() skel.left_x=first_x # all stafflines are completely straight skel.y_list=(last_x-first_x+1)*[y] staffline_skel.append(skel) return [staffdef, nostaffdef, staffline_skel]
def __call__(self, im_staffonly, maxdiff, c=0.5, random_seed=0): from gamera.core import Image, RGBPixel from gamera.toolkits.musicstaves.stafffinder import StafflineSkeleton seed(random_seed) im_full = self im_staffless = im_full.xor_image(im_staffonly) [first_x, last_x, stafflines, thickness] = find_stafflines_int(im_staffonly) def_staffonly = Image(im_full.ul, im_full.size) states = states_mh(2 * maxdiff + 1, c) m = (thickness - 1) / 2 if (thickness and 1) == 1: p = m else: p = m + 1 staffline_skel = [] for l in range(len(stafflines)): y = stafflines[l] - maxdiff skel = StafflineSkeleton() skel.left_x = first_x skel.y_list = (last_x - first_x + 1) * [y] for x in range(first_x, last_x + 1): y_l = y + states.next() skel.y_list[x - first_x] = y_l def_staffonly.draw_line((x, y_l - m), (x, y_l + p), RGBPixel(255, 255, 255)) staffline_skel.append(skel) im_staffless_invert = im_staffless.image_copy() im_staffless_invert.invert() def_staffonly.and_image(im_staffless_invert, True) def_staffless = im_staffless.image_copy() def_full = im_staffless.image_copy() def_full.or_image(def_staffonly, True) return [def_full, def_staffonly, staffline_skel]
def __call__(self, im_staffonly, eta, a0, a, b0, b, k=2, random_seed=0): from gamera.toolkits.musicstaves.stafffinder import StafflineSkeleton # the C++ plugin only returns a single image # random_seed should guarantee compatible results # additionally we subtract staffless image from nostaffdef # so that not too many symbol pixels are redefined as staff pixels staffdef = _staffdeformation.degrade_kanungo_single_image( self, eta, a0, a, b0, b, k, random_seed) nostaffdef = _staffdeformation.degrade_kanungo_single_image( im_staffonly, eta, a0, a, b0, b, k, random_seed) im_nostaff = self.subtract_images(im_staffonly) nostaffdef = nostaffdef.subtract_images(im_nostaff) # create staffline skeletons [first_x, last_x, stafflines, thickness] = find_stafflines_int(im_staffonly) staffline_skel = [] for y in stafflines: skel = StafflineSkeleton() skel.left_x = first_x # all stafflines are completely straight skel.y_list = (last_x - first_x + 1) * [y] staffline_skel.append(skel) return [staffdef, nostaffdef, staffline_skel]
def __call__(self,im_staffonly,maxdiff,c=0.5,random_seed=0): from gamera.core import Image,RGBPixel from gamera.toolkits.musicstaves.stafffinder import StafflineSkeleton seed(random_seed) im_full=self im_staffless=im_full.xor_image(im_staffonly) [first_x, last_x, stafflines, thickness]=find_stafflines_int(im_staffonly) def_staffonly=Image(im_full.ul,im_full.size) states=states_mh(2*maxdiff+1,c) m=(thickness-1)/2 if (thickness and 1)==1: p=m else: p=m+1 staffline_skel=[] for l in range(len(stafflines)): y=stafflines[l]-maxdiff skel=StafflineSkeleton() skel.left_x=first_x skel.y_list=(last_x-first_x+1)*[y] for x in range(first_x,last_x+1): y_l=y+states.next() skel.y_list[x-first_x]=y_l def_staffonly.draw_line((x,y_l-m),(x,y_l+p),RGBPixel(255,255,255)) staffline_skel.append(skel) im_staffless_invert=im_staffless.image_copy() im_staffless_invert.invert() def_staffonly.and_image(im_staffless_invert,True) def_staffless=im_staffless.image_copy() def_full=im_staffless.image_copy() def_full.or_image(def_staffonly,True) return [def_full,def_staffonly,staffline_skel]
def __call__(self, im_staffonly, ratio): from gamera.core import Image, RGBPixel, Point from gamera.toolkits.musicstaves.stafffinder import StafflineSkeleton im_full = self im_staffless = im_full.xor_image(im_staffonly) [first_x, last_x, stafflines, thickness] = find_stafflines_int(im_staffonly) # ratio=staffline_height/staffspace_height thickness = (stafflines[1] - stafflines[0]) / (1 / ratio + 1) im_newlines = Image(im_full.ul, im_full.size) # we just draw the lines ourselves for y in stafflines: im_newlines.draw_line(Point(first_x, y), Point(last_x, y), RGBPixel(255, 255, 255), thickness) # new full: lines OR symbols def_full = im_staffless.or_image(im_newlines) # new staffonly: lines AND NOT symbols def_staffonly = im_staffless.image_copy() def_staffonly.invert() def_staffonly.and_image(im_newlines, True) # staffless image doesn't change def_staffless = im_staffless.image_copy() # construct skeletons staffline_skel = [] for y in stafflines: skel = StafflineSkeleton() skel.left_x = first_x # all stafflines are completely straight skel.y_list = (last_x - first_x + 1) * [y] staffline_skel.append(skel) return [def_full, def_staffonly, staffline_skel]
def find_staves(self, num_lines=0, window=2, blackness = 60, \ tolerance = 25, align_edges=True, join_interrupted=True, debug = 0): """Method for finding the staff lines. Signature: ``find_staves(num_lines=0, window=1, blackness=60, tolerance=25, align_edges=True, join_interrupted=True, debug=0)`` with - *num_lines*: Number of lines per staff. If unknown, set to 0. - *window* and *blackness*: Parameters for the extraction of long horizontal runs. Only pixels are extracted that have an average blackness greater than *blackness* within a window of the width *window* \* *staff_space_height*. - *tolerance*: The tolerance that is used while connecting line segments that belong to the same staff. They may have a vertical distance of *staff_line_height* + *staff_space_height* with a deviation of *tolerance* in percent. - *align_edges*: When `True`, staff line edges in a staff are aligned up to the left most and right most found staff line within this staff. - *debug*: 0 = Be quiet. 1 = Show Progress messages. 2 = print images with prefix 'dalitzdebug' to current directory This method fills the *self.linelist* attribute for further processing. """ #-------------------------------------------------------- # # Step 1: Get staff skeleton list # if debug > 0: print print "Getting staff skeletons..." # Get the skeleton list skeleton_list = self.image.get_staff_skeleton_list( \ self.staffline_height, \ window * self.staffspace_height, \ blackness) too_short_skeletons = [ line for line in skeleton_list \ if len(line[1]) < self.staffspace_height * 2 ] if len(too_short_skeletons) > 0: if debug > 0: print " %i skeletons are too short. Removing." \ % (len(too_short_skeletons)) # Remove very short segments for s in too_short_skeletons: skeleton_list.remove(s) # Create graph segments = [] for line in skeleton_list: n = StaffSegment() n.row_start = line[1][0] n.col_start = line[0] n.row_end = line[1][-1] n.col_end = n.col_start + len(line[1]) - 1 n.skeleton = line segments.append(n) #-------------------------------------------------------- # # Step 2: Create vertical connections # # A connection is done between two segments that # overlap horizontally if the vertical distance # is staff_line_height + staff_space_height # (+- tolerance) percent # if debug > 0: print "Creating vertical connections..." connections = [] tol = float(tolerance) / 100.0 min_dist = (self.staffline_height + self.staffspace_height) \ * (1.0 - tol) max_dist = (self.staffline_height + self.staffspace_height) \ * (1.0 + tol) for seg1 in segments: for seg2 in segments: # No self-connections, segments must overlap if seg1 != seg2 and seg1.overlaps(seg2): # Calculate vertical distance in # the middle of the overlapping parts mid = (max(seg1.col_start, seg2.col_start) + \ min(seg1.col_end, seg2.col_end)) / 2 row1 = seg1.skeleton[1][mid - seg1.col_start] row2 = seg2.skeleton[1][mid - seg2.col_start] dist = row2 - row1 if dist >= min_dist and dist <= max_dist: # seg2 belongs to a staff # line below seg1 if seg1.down_links.count(seg2) == 0: seg1.down_links.append(seg2) if seg2.up_links.count(seg1) == 0: seg2.up_links.append(seg1) # Add connection for debugging conn = StaffConn() conn.col = mid conn.row_start = min(row1, row2) conn.row_end = max(row1, row2) connections.append(conn) elif dist <= -min_dist and dist >= -max_dist: # seg2 belongs to a staff # line on top of seg1 if (seg2.down_links.count(seg1) == 0): seg2.down_links.append(seg1) if (seg1.up_links.count(seg2) == 0): seg1.up_links.append(seg2) if debug > 0: print " %i connections created." % (len(connections)) #-------------------------------------------------------- # # Step 3a: Remove Segments without links # tmp = [] for seg in segments: if len(seg.down_links) > 0 or len(seg.up_links) > 0: tmp.append(seg) segments = tmp del tmp #-------------------------------------------------------- # # Step 3b: Label CC's and line numbers # # Do a breadth-first search on the segments # and give a unique label to every segment that # belongs to a certain group of connected segments. # Increase the line number with every downward # connection and decrease it with every upward # connection. if debug > 0: print "Grouping segments to staffs..." label = -1 groups = [] for segment in segments: # Segment already labeled: Process next one if segment.label != None: continue seg = segment label = label + 1 # Increase label group = StaffGroup() # Label this segment seg.label = label seg.line = 0 group.extend(seg) # Label neighbors neighbors = [] for n in seg.up_links: if n.label == None: n.label = label # Up-link: Decrease line number n.line = seg.line - 1 group.extend(n) neighbors.append(n) elif n.label != label: raise RuntimeError, "Labelling error!" for n in seg.down_links: if n.label == None: n.label = label # Down-link: Increase line number n.line = seg.line + 1 group.extend(n) neighbors.append(n) elif n.label != label: raise RuntimeError, "Labelling error!" # Process neighbors while len(neighbors) > 0: new_neighbors = [] for seg in neighbors: for n in seg.up_links: if n.label == None: n.label = label # Up-Link: Decrease line number n.line = seg.line - 1 group.extend(n) new_neighbors.append(n) elif n.label != label: raise RuntimeError, "Labelling error!" for n in seg.down_links: if n.label == None: n.label = label # Down-Link: Increase line numver n.line = seg.line + 1 group.extend(n) new_neighbors.append(n) elif n.label != label: raise RuntimeError, "Labelling error!" neighbors = new_neighbors groups.append(group) if debug > 0: print " Found %i staffs." % (len(groups)) #-------------------------------------------------------- # # Step 4: Melt overlapping segments of a staff line # # If two segments of the same staff line overlap, # they have to be melted to one, so that the later # interpolation can assume non-overlapping segments. # # In the overlapped parts, the decision of which # part to use is made by laying a least square fit over # the non-overlapping parts. The overlapping part, that # fits better to the resulting straight line is used # to substitute the overlapping range. if debug > 0: print "Melting overlapping line segments..." melt_skeletons = [] melt_count = 0 for g in groups: for l in range(g.min_line, g.max_line + 1): melted = True while (melted): melted = False for seg1 in g.segments: if seg1.line != l: continue for seg2 in g.segments: if seg2.line != l or seg1 == seg2: continue if seg1.overlaps(seg2): melt_skeletons.append(seg1.melt(seg2)) g.segments.remove(seg2) melted = True melt_count = melt_count + 1 # Jump out of the # inner 'for' break # Jump out of the outer 'for' if melted: break if debug > 0 and melt_count > 0: print " %d segments melted." % (melt_count) #-------------------------------------------------------- # # Step 5a: Removal of staffs with too few lines # # when the number of lines is not given, it is # estimated as the most frequent num_lines among the wide groups # # Get maximum staff line width of all staffs max_group_width = 0 for g in groups: width = g.col_end - g.col_start + 1 if width > max_group_width: max_group_width = width # estimate num_lines if num_lines > 0: estimated_num_lines = num_lines else: num_hist = {} for g in groups: if g.col_end - g.col_start + 1 > max_group_width / 2: n = g.max_line - g.min_line + 1 if num_hist.has_key(n): num_hist[n] += 1 else: num_hist[n] = 1 max_count = 0 for (n,c) in num_hist.iteritems(): if c > max_count: estimated_num_lines = n max_count = c print "num_lines estimated as ", estimated_num_lines # remove staffs with fewer lines if debug > 0: print "Removing staffs with fewer lines than", estimated_num_lines, "..." rem_groups = [g for g in groups \ if g.max_line - g.min_line + 1 < estimated_num_lines] for g in rem_groups: groups.remove(g) if debug > 0 and len(rem_groups) > 0: print " %i removed, %i staffs left." \ % (len(rem_groups), len(groups)) #-------------------------------------------------------- # # Step 5b: Remove additional lines above and below staffs # # If the number of staff lines in a staff is known, # the top or bottom line (the narrower one) is removed # until the correct number of lines is reached. # # If it is not known, every line is removed, that is # much narrower than the maximum line length in this # staff. # if debug > 0: print "Removing additional staff lines..." lines_removed = 0 # In every staff group for g in groups: lengths = [] max_length = 0 # Calculate range of every staff line for line in range(g.min_line, g.max_line + 1): length_sum = 0 for s in [seg for seg in g.segments if seg.line == line]: length_sum = length_sum + s.col_end - s.col_start + 1 lengths.append((line, length_sum)) if length_sum > max_length: max_length = length_sum # If the real number of staff lines is given... if num_lines > 0: # While there are to much lines... while g.max_line - g.min_line + 1 > num_lines: if lengths[0][1] < lengths[-1][1]: # Upper line shortest g.remove_line(lengths[0][0]) lengths.pop(0) # Remove first line info else: # Bottom line shortest g.remove_line(lengths[-1][0]) lengths.pop() # Remove last line info lines_removed = lines_removed + 1 else: # Real number of lines not given # Simply remove lines that are too short for line, length in lengths: if (length < max_length * 0.8): g.remove_line(line) lines_removed = lines_removed + 1 # TODO: Check if groups have been seperated! if debug > 0 and lines_removed > 0: print " Removed %d lines." % (lines_removed) #-------------------------------------------------------- # # Step 6a: Remove groups that overlap with wider groups # if debug > 0: print "Removing embedded staffs..." # sort groups line for line from left to right def _cmp_y(g,h): if g.row_start < h.row_start: return -1 else: return 1 groups.sort(_cmp_y) ngroups = len(groups) breakdist = 2 * self.staffspace_height for i in range(ngroups): # find items in same line candidates = [] for j in range(i+1,ngroups): if groups[i].row_end - breakdist < groups[j].row_start: break candidates.append([j,groups[j]]) # pick the leftmost as next in order if len(candidates) > 0: minj = i; min_col_start = groups[i].col_start for c in candidates: if c[1].col_start < min_col_start: minj = c[0]; min_col_start = c[1].col_start if minj > i: g = groups[i] groups[i] = groups[minj] groups[minj] = g #print "groups sorted:" #for g in groups: # print "rows = ", g.row_start, "-", g.row_end, "col_start =",g.col_start, "lines =", g.max_line - g.min_line + 1 # when consecutive groups overlap, keep only the widest rem_groups = [] i = 0; j = 0 while i < len(groups) and j < len(groups) - 1: j += 1 g = groups[i]; h = groups[j] if g.col_end >= h.col_start and \ ((h.row_start < g.row_start and g.row_start < h.row_end) or \ (h.row_start < g.row_end and g.row_end < h.row_end)): if (g.col_end - g.col_start) < (h.col_end - h.col_start): rem_groups.append(g) i = j else: rem_groups.append(h) else: i += 1 for g in rem_groups: if g in groups: groups.remove(g) if debug > 0 and len(rem_groups) > 0: print " %i removed, %i staffs left." \ % (len(rem_groups), len(groups)) #-------------------------------------------------------- # # Step 6b: Join groups belonging to the same staff system # (only executed when join_interrupted is set) # if join_interrupted: if debug > 0: print "Join interrupted staves..." # check whether consecutive groups follow each other # and how they could be linked # condition: distance < 2*staffspace_height rem_groups = [] for i, g1 in enumerate(groups): if g1 in rem_groups: continue for j in range(i + 1, len(groups)): g2 = groups[j] # join only if vertically overlapping if max(g1.row_start, g2.row_start) > min(g1.row_end, g2.row_end): break # join groups with the same line count only if g2.max_line - g2.min_line != g1.max_line - g1.min_line: break if g2.col_start <= g1.col_end: break if g2.col_start - g1.col_end >= 2*self.staffspace_height: break # now do the join thing g1.join(g2) rem_groups.append(g2) for g in rem_groups: groups.remove(g) if debug > 0: print " %i group(s) joined." % len(rem_groups) #-------------------------------------------------------- # # Step 7: Removal of narrow staffs # if debug > 0: print "Removing invalid staffs..." rem_groups = [g for g in groups \ if g.col_end - g.col_start + 1 < max_group_width / 2] for g in rem_groups: groups.remove(g) if debug > 0 and len(rem_groups) > 0: print " %i removed, %i staffs left." \ % (len(rem_groups), len(groups)) #-------------------------------------------------------- # # Step 8: Interpolate broken staff lines # # If there is more than one segment left for a staff # line: Order and connect them. # if debug > 0: print "Connecting broken lines..." conn_skels = [] conn_count = 0 for g in groups: for line in range(g.min_line, g.max_line + 1): # Count segments in this line line_segs = [] for s in g.segments: if s.line == line: line_segs.append(s) # If more than one segment per line: Connect them! if len(line_segs) > 1: conn_count = conn_count + len(line_segs) # Sort segments by start column line_segs.sort(lambda x, y: int(x.col_start - y.col_start)) s1 = line_segs.pop(0) # Leftmost segment for s in line_segs: conn_skel = s1.connect(s) if conn_skel: conn_skels.append(conn_skel) g.segments.remove(s) if debug > 0 and conn_count > 0: print " %i connected" % (conn_count) #-------------------------------------------------------- # # Visualization # if (debug > 1): rgb = Image(self.image, RGB) print print "Drawing group backgrounds..." for g in groups: color = RGBPixel(150 + (31 * g.label) % 106, \ 150 + (111 * (g.label + 1)) % 106, \ 150 + (201 * (g.label + 2)) % 106) rgb.draw_filled_rect((g.col_start, g.row_start), \ (g.col_end, g.row_end), \ color) print "Drawing original image..." rgb.highlight(self.image, RGBPixel(0, 0, 0)) print "Highlighting staff line candidates..." staff_skeleton = self.image.skeleton_list_to_image(skeleton_list) rgb.highlight(staff_skeleton, RGBPixel(255, 150, 0)) # orange staff_skeleton_short = self.image.skeleton_list_to_image(\ too_short_skeletons) rgb.highlight(staff_skeleton_short, RGBPixel(255, 0, 150)) # orange black_runs = self.image.extract_filled_horizontal_black_runs(\ windowwidth = self.staffspace_height * window, \ blackness = blackness) black_runs = black_runs.to_rgb() black_runs.highlight(staff_skeleton, RGBPixel(0, 255, 0)) black_runs.save_PNG("dalitzdebug_blackruns.png") print "Highlighting group segments..." group_skeletons = [] melted_skeletons = [] conn_skeletons = [] for g in groups: for seg in g.segments: group_skeletons.append(seg.skeleton) group_image = self.image.skeleton_list_to_image(group_skeletons) rgb.highlight(group_image, RGBPixel(0, 255, 0)) # green print "Highlighting melted sections..." melt_image = self.image.skeleton_list_to_image(melt_skeletons) rgb.highlight(melt_image, RGBPixel(0, 255, 255)) # cyan print "Highlighting connections..." conn_image = self.image.skeleton_list_to_image(conn_skels) rgb.highlight(conn_image, RGBPixel(255, 255, 0)) # yellow print "Drawing segment markers..." for g in groups: for seg in g.segments: color = RGBPixel(100 + ((71 * seg.line) % 156), 0, 0) rgb.draw_marker((seg.col_start, seg.row_start), \ self.staffline_height * 2, \ 3, color) rgb.draw_marker((seg.col_end, seg.row_end), \ self.staffline_height * 2, \ 3, color) print "Drawing links..." # All connections for c in connections: rgb.draw_line((c.col, c.row_start), (c.col, c.row_end), \ RGBPixel(0, 0, 255)) # blue # Connections of group segments for g in groups: for seg in g.segments: for link in seg.down_links: mid = (max(seg.col_start, link.col_start) + \ min(seg.col_end, link.col_end)) / 2 row1 = seg.skeleton[1][mid - seg.col_start] row2 = link.skeleton[1][mid - link.col_start] rgb.draw_line((mid, row1), (mid, row2), \ RGBPixel(255, 0, 200)) # pink print "Writing file..." rgb.save_PNG("dalitzdebug_out.png") #-------------------------------------------------------- # # Copy over the results into self.linelist # self.linelist = [] for g in groups: newstaff = [] #for line in range(g.min_line, g.max_line + 1): for s in g.segments: skel = StafflineSkeleton() skel.left_x = s.skeleton[0] skel.y_list = s.skeleton[1] newstaff.append(skel) # sort by y-position newstaff.sort(lambda s1,s2: int(s1.y_list[0] - s2.y_list[0])) self.linelist.append(newstaff) #-------------------------------------------------------- # # Adjust edge points to the left/right most point with each staff # if align_edges: if debug > 0: print "Align edge points" for staff in self.linelist: # find left/right most edge point lefti = 0; left = self.image.ncols righti = 0; right = 0 for i,skel in enumerate(staff): if skel.left_x < left: lefti = i; left = skel.left_x if skel.left_x + len(skel.y_list) - 1 > right: righti = i; right = skel.left_x + len(skel.y_list) - 1 leftref = staff[lefti].y_list rightref = staff[righti].y_list # extrapolate left edge points for skel in staff: if skel.left_x > left: if skel.left_x - left < len(leftref): dy = skel.y_list[0] - leftref[skel.left_x - left] else: dy = self.staffspace_height x = skel.left_x - 1 while (x >= left): if x-left < len(leftref): skel.y_list.insert(0, leftref[x-left] + dy) else: skel.y_list.insert(0, skel.y_list[0]) x -= 1 skel.left_x = left # extrapolate right edge points for skel in staff: if skel.left_x + len(skel.y_list) - 1 < right: dy = skel.y_list[-1] - rightref[len(skel.y_list)] x = skel.left_x + len(skel.y_list) while (x <= right): skel.y_list.append(rightref[x-left] + dy) x += 1 if debug > 0: print "Ready." print
def __call__(self, im_staffonly, n_gap, p_gap, n_shift, random_seed=0, add_noshift=False): from gamera.core import RGBPixel, Point, FloatPoint, Rect from gamera.toolkits.musicstaves.stafffinder import StafflineSkeleton seed(random_seed) bnp_gap = binomial(n_gap, p_gap) bnp_shift = binomial(n_shift, 0.5) im_full = self.image_copy() im_staffless = im_full.xor_image(im_staffonly) im_staffonly = im_staffonly.image_copy() il_shifts = [im_staffless, im_staffonly, im_full] il_breaks = il_shifts if add_noshift: ns_full = im_full.image_copy() ns_staffonly = im_staffonly.image_copy() il_breaks = [ im_staffless, im_staffonly, im_full, ns_full, ns_staffonly ] #find stafflines [first_x, last_x, stafflines, thickness] = find_stafflines_int(im_staffonly) #find breaks (where are the symbols?) stafflinedist = stafflines[1] - stafflines[0] staffline_skel = [] for sl in range(len(stafflines)): if sl == 0 or stafflines[sl] - stafflines[sl - 1] > 3 * stafflinedist: #first staffline of a system first_line = stafflines[sl] lines_per_system = 0 lines_per_system = lines_per_system + 1 if sl == len(stafflines) - 1 or stafflines[ sl + 1] - stafflines[sl] > 3 * stafflinedist: #last staffline of a system last_line = stafflines[sl] # a hoizontal projection of the symbols helps us to find the x positions of the symbols. syst = im_staffless.subimage( Point(0, max((0, first_line - 3 * stafflinedist))), Point( im_staffless.width, min((last_line + 3 * stafflinedist, im_full.lr_y - 1)))) symbolcols = syst.projection_cols() # collect the breaks, i.e. the mid of the white spaces between two symbols breaks = [] whiterun = False for col in range(len(symbolcols)): if (not whiterun) and symbolcols[col] == 0: whiterun = True runbegin = col else: if whiterun and symbolcols[col] > 0: whiterun = False br = [(runbegin + col) / 2, col - runbegin] if br[0] >= first_x: breaks.append(br) # replace the first break (before the first symbol) with a break at the beginning of the stafflines breaks[0] = [first_x, 1] # and append a break after the last symbol if whiterun: breaks.append([(last_x + runbegin) / 2, last_x - runbegin]) else: breaks.append([last_x, 1]) # draw white lines at the breaks new_breaks = [] for br in breaks: w = bnp_gap.random() # draw line if it fits only if w < br[1]: for im in il_breaks: im.draw_line( FloatPoint(br[0], first_line - stafflinedist), FloatPoint(br[0], last_line + stafflinedist), RGBPixel(0, 0, 0), w) new_breaks.append(br) breaks = new_breaks skeleton = [] # do the vertical shift by making a temporary copy (orig_type), white them out (draw_filled_rect) and "or" them again in with a new y-position for t in range(len(breaks) - 1): vertical_shift = bnp_shift.random() - n_shift / 2 typerect = Rect( Point(breaks[t][0], max((first_line - 3 * stafflinedist, 0))), Point( breaks[t + 1][0], min((last_line + 3 * stafflinedist, im_full.lr_y)))) moveto = Rect( Point(typerect.ul_x, typerect.ul_y + vertical_shift), typerect.size) if moveto.ul_y < 0: typerect.ul_y = typerect.ul_y - moveto.ul_y moveto.ul_y = 0 if moveto.lr_y > im_full.lr_y: typerect.lr_y = typerect.lr_y - (moveto.lr_y - im_full.lr_y) moveto.lr_y = im_full.lr_y for im in il_shifts: orig_type = im.subimage(typerect) orig_type = orig_type.image_copy() im.draw_filled_rect(typerect, RGBPixel(0, 0, 0)) im.subimage(moveto).or_image(orig_type, True) # collect data for later construction of the skeletons for line in range(lines_per_system): if len(skeleton) <= line: skeleton.append([]) st_line = stafflines[sl - lines_per_system + 1 + line] y = st_line + vertical_shift skeleton[line].append(Point(typerect.ul_x, y)) skeleton[line].append(Point(typerect.ur_x, y)) # now construct the skeletons for sk in skeleton: x = sk[0].x y = sk[0].y left_x = x y_list = [] i = 1 while i < len(sk): y_list.append(y) if x >= sk[i].x: y = sk[i].y i = i + 1 x = x + 1 o = StafflineSkeleton() o.left_x = left_x o.y_list = y_list staffline_skel.append(o) if add_noshift: return [ im_full, im_staffonly, staffline_skel, ns_full, ns_staffonly ] else: return [im_full, im_staffonly, staffline_skel]
def find_staves(self, num_lines=0, window=3, blackness = 60, \ tolerance = 25, align_edges=True, join_interrupted=True, debug = 0): """Method for finding the staff lines. Signature: ``find_staves(num_lines=0, window=3, blackness=60, tolerance=25, align_edges=True, join_interrupted=True, debug=0)`` with - *num_lines*: Number of lines per staff. If unknown, set to 0. - *window* and *blackness*: Parameters for the extraction of long horizontal runs. Only pixels are extracted that have an average blackness greater than *blackness* within a window of the width *window* \* *staff_space_height*. - *tolerance*: The tolerance that is used while connecting line segments that belong to the same staff. They may have a vertical distance of *staff_line_height* + *staff_space_height* with a deviation of *tolerance* in percent. - *align_edges*: When `True`, staff line edges in a staff are aligned up to the left most and right most found staff line within this staff. - *debug*: 0 = Be quiet. 1 = Show Progress messages. 2 = print images with prefix 'dalitzdebug' to current directory This method fills the *self.linelist* attribute for further processing. """ #-------------------------------------------------------- # # Step 1: Get staff skeleton list # if debug > 0: print print "Getting staff skeletons..." # Get the skeleton list skeleton_list = self.image.get_staff_skeleton_list( \ self.staffline_height, \ window * self.staffspace_height, \ blackness) too_short_skeletons = [ line for line in skeleton_list \ if len(line[1]) < self.staffspace_height * 2 ] if len(too_short_skeletons) > 0: if debug > 0: print " %i skeletons are too short. Removing." \ % (len(too_short_skeletons)) # Remove very short segments for s in too_short_skeletons: skeleton_list.remove(s) # Create graph segments = [] for line in skeleton_list: n = StaffSegment() n.row_start = line[1][0] n.col_start = line[0] n.row_end = line[1][-1] n.col_end = n.col_start + len(line[1]) - 1 n.skeleton = line segments.append(n) #-------------------------------------------------------- # # Step 2: Create vertical connections # # A connection is done between two segments that # overlap horizontally if the vertical distance # is staff_line_height + staff_space_height # (+- tolerance) percent # if debug > 0: print "Creating vertical connections..." connections = [] tol = float(tolerance) / 100.0 min_dist = (self.staffline_height + self.staffspace_height) \ * (1.0 - tol) max_dist = (self.staffline_height + self.staffspace_height) \ * (1.0 + tol) for seg1 in segments: for seg2 in segments: # No self-connections, segments must overlap if seg1 != seg2 and seg1.overlaps(seg2): # Calculate vertical distance in # the middle of the overlapping parts mid = (max(seg1.col_start, seg2.col_start) + \ min(seg1.col_end, seg2.col_end)) / 2 row1 = seg1.skeleton[1][mid - seg1.col_start] row2 = seg2.skeleton[1][mid - seg2.col_start] dist = row2 - row1 if dist >= min_dist and dist <= max_dist: # seg2 belongs to a staff # line below seg1 if seg1.down_links.count(seg2) == 0: seg1.down_links.append(seg2) if seg2.up_links.count(seg1) == 0: seg2.up_links.append(seg1) # Add connection for debugging conn = StaffConn() conn.col = mid conn.row_start = min(row1, row2) conn.row_end = max(row1, row2) connections.append(conn) elif dist <= -min_dist and dist >= -max_dist: # seg2 belongs to a staff # line on top of seg1 if (seg2.down_links.count(seg1) == 0): seg2.down_links.append(seg1) if (seg1.up_links.count(seg2) == 0): seg1.up_links.append(seg2) if debug > 0: print " %i connections created." % (len(connections)) #-------------------------------------------------------- # # Step 3a: Remove Segments without links # tmp = [] for seg in segments: if len(seg.down_links) > 0 or len(seg.up_links) > 0: tmp.append(seg) segments = tmp del tmp #-------------------------------------------------------- # # Step 3b: Label CC's and line numbers # # Do a breadth-first search on the segments # and give a unique label to every segment that # belongs to a certain group of connected segments. # Increase the line number with every downward # connection and decrease it with every upward # connection. if debug > 0: print "Grouping segments to staffs..." label = -1 groups = [] for segment in segments: # Segment already labeled: Process next one if segment.label != None: continue seg = segment label = label + 1 # Increase label group = StaffGroup() # Label this segment seg.label = label seg.line = 0 group.extend(seg) # Label neighbors neighbors = [] for n in seg.up_links: if n.label == None: n.label = label # Up-link: Decrease line number n.line = seg.line - 1 group.extend(n) neighbors.append(n) elif n.label != label: raise RuntimeError, "Labelling error!" for n in seg.down_links: if n.label == None: n.label = label # Down-link: Increase line number n.line = seg.line + 1 group.extend(n) neighbors.append(n) elif n.label != label: raise RuntimeError, "Labelling error!" # Process neighbors while len(neighbors) > 0: new_neighbors = [] for seg in neighbors: for n in seg.up_links: if n.label == None: n.label = label # Up-Link: Decrease line number n.line = seg.line - 1 group.extend(n) new_neighbors.append(n) elif n.label != label: raise RuntimeError, "Labelling error!" for n in seg.down_links: if n.label == None: n.label = label # Down-Link: Increase line numver n.line = seg.line + 1 group.extend(n) new_neighbors.append(n) elif n.label != label: raise RuntimeError, "Labelling error!" neighbors = new_neighbors groups.append(group) if debug > 0: print " Found %i staffs." % (len(groups)) #-------------------------------------------------------- # # Step 4: Melt overlapping segments of a staff line # # If two segments of the same staff line overlap, # they have to be melted to one, so that the later # interpolation can assume non-overlapping segments. # # In the overlapped parts, the decision of which # part to use is made by laying a least square fit over # the non-overlapping parts. The overlapping part, that # fits better to the resulting straight line is used # to substitute the overlapping range. if debug > 0: print "Melting overlapping line segments..." melt_skeletons = [] melt_count = 0 for g in groups: for l in range(g.min_line, g.max_line + 1): melted = True while (melted): melted = False for seg1 in g.segments: if seg1.line != l: continue for seg2 in g.segments: if seg2.line != l or seg1 == seg2: continue if seg1.overlaps(seg2): melt_skeletons.append(seg1.melt(seg2)) g.segments.remove(seg2) melted = True melt_count = melt_count + 1 # Jump out of the # inner 'for' break # Jump out of the outer 'for' if melted: break if debug > 0 and melt_count > 0: print " %d segments melted." % (melt_count) #-------------------------------------------------------- # # Step 5a: Removal of staffs with too few lines # # when the number of lines is not given, it is # estimated as the most frequent num_lines among the wide groups # # Get maximum staff line width of all staffs max_group_width = 0 for g in groups: width = g.col_end - g.col_start + 1 if width > max_group_width: max_group_width = width # estimate num_lines if num_lines > 0: estimated_num_lines = num_lines else: num_hist = {} for g in groups: if g.col_end - g.col_start + 1 > max_group_width / 2: n = g.max_line - g.min_line + 1 if num_hist.has_key(n): num_hist[n] += 1 else: num_hist[n] = 1 max_count = 0 estimated_num_lines = 0 for (n, c) in num_hist.iteritems(): if c > max_count: estimated_num_lines = n max_count = c print "num_lines estimated as ", estimated_num_lines # remove staffs with fewer lines if debug > 0: print "Removing staffs with fewer lines than", estimated_num_lines, "..." rem_groups = [g for g in groups \ if g.max_line - g.min_line + 1 < estimated_num_lines] for g in rem_groups: groups.remove(g) if debug > 0 and len(rem_groups) > 0: print " %i removed, %i staffs left." \ % (len(rem_groups), len(groups)) #-------------------------------------------------------- # # Step 5b: Remove additional lines above and below staffs # # If the number of staff lines in a staff is known, # the top or bottom line (the narrower one) is removed # until the correct number of lines is reached. # # If it is not known, every line is removed, that is # much narrower than the maximum line length in this # staff. # if debug > 0: print "Removing additional staff lines..." lines_removed = 0 # In every staff group for g in groups: lengths = [] max_length = 0 # Calculate range of every staff line for line in range(g.min_line, g.max_line + 1): length_sum = 0 for s in [seg for seg in g.segments if seg.line == line]: length_sum = length_sum + s.col_end - s.col_start + 1 lengths.append((line, length_sum)) if length_sum > max_length: max_length = length_sum # If the real number of staff lines is given... if num_lines > 0: # While there are to much lines... while g.max_line - g.min_line + 1 > num_lines: if lengths[0][1] < lengths[-1][1]: # Upper line shortest g.remove_line(lengths[0][0]) lengths.pop(0) # Remove first line info else: # Bottom line shortest g.remove_line(lengths[-1][0]) lengths.pop() # Remove last line info lines_removed = lines_removed + 1 else: # Real number of lines not given # Simply remove lines that are too short for line, length in lengths: if (length < max_length * 0.8): g.remove_line(line) lines_removed = lines_removed + 1 # TODO: Check if groups have been seperated! if debug > 0 and lines_removed > 0: print " Removed %d lines." % (lines_removed) #-------------------------------------------------------- # # Step 6a: Remove groups that overlap with wider groups # if debug > 0: print "Removing embedded staffs..." # sort groups line for line from left to right def _cmp_y(g, h): if g.row_start < h.row_start: return -1 else: return 1 groups.sort(_cmp_y) ngroups = len(groups) breakdist = 2 * self.staffspace_height for i in range(ngroups): # find items in same line candidates = [] for j in range(i + 1, ngroups): if groups[i].row_end - breakdist < groups[j].row_start: break candidates.append([j, groups[j]]) # pick the leftmost as next in order if len(candidates) > 0: minj = i min_col_start = groups[i].col_start for c in candidates: if c[1].col_start < min_col_start: minj = c[0] min_col_start = c[1].col_start if minj > i: g = groups[i] groups[i] = groups[minj] groups[minj] = g #print "groups sorted:" #for g in groups: # print "rows = ", g.row_start, "-", g.row_end, "col_start =",g.col_start, "lines =", g.max_line - g.min_line + 1 # when consecutive groups overlap, keep only the widest rem_groups = [] i = 0 j = 0 while i < len(groups) and j < len(groups) - 1: j += 1 g = groups[i] h = groups[j] if g.col_end >= h.col_start and \ ((h.row_start < g.row_start and g.row_start < h.row_end) or \ (h.row_start < g.row_end and g.row_end < h.row_end)): if (g.col_end - g.col_start) < (h.col_end - h.col_start): rem_groups.append(g) i = j else: rem_groups.append(h) else: i += 1 for g in rem_groups: if g in groups: groups.remove(g) if debug > 0 and len(rem_groups) > 0: print " %i removed, %i staffs left." \ % (len(rem_groups), len(groups)) #-------------------------------------------------------- # # Step 6b: Join groups belonging to the same staff system # (only executed when join_interrupted is set) # if join_interrupted: if debug > 0: print "Join interrupted staves..." # check whether consecutive groups follow each other # and how they could be linked # condition: distance < 2*staffspace_height rem_groups = [] for i, g1 in enumerate(groups): if g1 in rem_groups: continue for j in range(i + 1, len(groups)): g2 = groups[j] # join only if vertically overlapping if max(g1.row_start, g2.row_start) > min( g1.row_end, g2.row_end): break # join groups with the same line count only if g2.max_line - g2.min_line != g1.max_line - g1.min_line: break if g2.col_start <= g1.col_end: break if g2.col_start - g1.col_end >= 2 * self.staffspace_height: break # now do the join thing g1.join(g2) rem_groups.append(g2) for g in rem_groups: groups.remove(g) if debug > 0: print " %i group(s) joined." % len(rem_groups) #-------------------------------------------------------- # # Step 7: Removal of narrow staffs # #if debug > 0: # print "Removing invalid staffs..." #rem_groups = [g for g in groups \ # if g.col_end - g.col_start + 1 < max_group_width / 2] #for g in rem_groups: groups.remove(g) #if debug > 0 and len(rem_groups) > 0: # print " %i removed, %i staffs left." \ # % (len(rem_groups), len(groups)) #-------------------------------------------------------- # # Step 8: Interpolate broken staff lines # # If there is more than one segment left for a staff # line: Order and connect them. # if debug > 0: print "Connecting broken lines..." conn_skels = [] conn_count = 0 for g in groups: for line in range(g.min_line, g.max_line + 1): # Count segments in this line line_segs = [] for s in g.segments: if s.line == line: line_segs.append(s) # If more than one segment per line: Connect them! if len(line_segs) > 1: conn_count = conn_count + len(line_segs) # Sort segments by start column line_segs.sort(lambda x, y: int(x.col_start - y.col_start)) s1 = line_segs.pop(0) # Leftmost segment for s in line_segs: conn_skel = s1.connect(s) if conn_skel: conn_skels.append(conn_skel) g.segments.remove(s) if debug > 0 and conn_count > 0: print " %i connected" % (conn_count) #-------------------------------------------------------- # # Visualization # if (debug > 1): rgb = Image(self.image, RGB) print print "Drawing group backgrounds..." for g in groups: color = RGBPixel(150 + (31 * g.label) % 106, \ 150 + (111 * (g.label + 1)) % 106, \ 150 + (201 * (g.label + 2)) % 106) rgb.draw_filled_rect((g.col_start, g.row_start), \ (g.col_end, g.row_end), \ color) print "Drawing original image..." rgb.highlight(self.image, RGBPixel(0, 0, 0)) print "Highlighting staff line candidates..." staff_skeleton = self.image.skeleton_list_to_image(skeleton_list) rgb.highlight(staff_skeleton, RGBPixel(255, 150, 0)) # orange staff_skeleton_short = self.image.skeleton_list_to_image(\ too_short_skeletons) rgb.highlight(staff_skeleton_short, RGBPixel(255, 0, 150)) # orange black_runs = self.image.extract_filled_horizontal_black_runs(\ windowwidth = self.staffspace_height * window, \ blackness = blackness) black_runs = black_runs.to_rgb() black_runs.highlight(staff_skeleton, RGBPixel(0, 255, 0)) black_runs.save_PNG("dalitzdebug_blackruns.png") print "Highlighting group segments..." group_skeletons = [] melted_skeletons = [] conn_skeletons = [] for g in groups: for seg in g.segments: group_skeletons.append(seg.skeleton) group_image = self.image.skeleton_list_to_image(group_skeletons) rgb.highlight(group_image, RGBPixel(0, 255, 0)) # green print "Highlighting melted sections..." melt_image = self.image.skeleton_list_to_image(melt_skeletons) rgb.highlight(melt_image, RGBPixel(0, 255, 255)) # cyan print "Highlighting connections..." conn_image = self.image.skeleton_list_to_image(conn_skels) rgb.highlight(conn_image, RGBPixel(255, 255, 0)) # yellow print "Drawing segment markers..." for g in groups: for seg in g.segments: color = RGBPixel(100 + ((71 * seg.line) % 156), 0, 0) rgb.draw_marker((seg.col_start, seg.row_start), \ self.staffline_height * 2, \ 3, color) rgb.draw_marker((seg.col_end, seg.row_end), \ self.staffline_height * 2, \ 3, color) print "Drawing links..." # All connections for c in connections: rgb.draw_line((c.col, c.row_start), (c.col, c.row_end), \ RGBPixel(0, 0, 255)) # blue # Connections of group segments for g in groups: for seg in g.segments: for link in seg.down_links: mid = (max(seg.col_start, link.col_start) + \ min(seg.col_end, link.col_end)) / 2 row1 = seg.skeleton[1][mid - seg.col_start] row2 = link.skeleton[1][mid - link.col_start] rgb.draw_line((mid, row1), (mid, row2), \ RGBPixel(255, 0, 200)) # pink print "Writing file..." rgb.save_PNG("dalitzdebug_out.png") #-------------------------------------------------------- # # Copy over the results into self.linelist # self.linelist = [] for g in groups: newstaff = [] #for line in range(g.min_line, g.max_line + 1): for s in g.segments: skel = StafflineSkeleton() skel.left_x = s.skeleton[0] skel.y_list = s.skeleton[1] newstaff.append(skel) # sort by y-position newstaff.sort(lambda s1, s2: int(s1.y_list[0] - s2.y_list[0])) self.linelist.append(newstaff) #-------------------------------------------------------- # # Adjust edge points to the left/right most point with each staff # if align_edges: if debug > 0: print "Align edge points" for staff in self.linelist: # find left/right most edge point lefti = 0 left = self.image.ncols righti = 0 right = 0 for i, skel in enumerate(staff): if skel.left_x < left: lefti = i left = skel.left_x if skel.left_x + len(skel.y_list) - 1 > right: righti = i right = skel.left_x + len(skel.y_list) - 1 leftref = staff[lefti].y_list rightref = staff[righti].y_list # extrapolate left edge points for skel in staff: if skel.left_x > left: if skel.left_x - left < len(leftref): dy = skel.y_list[0] - leftref[skel.left_x - left] else: dy = self.staffspace_height x = skel.left_x - 1 while (x >= left): if x - left < len(leftref): skel.y_list.insert(0, leftref[x - left] + dy) else: skel.y_list.insert(0, skel.y_list[0]) x -= 1 skel.left_x = left # extrapolate right edge points for skel in staff: if skel.left_x + len(skel.y_list) - 1 < right: dy = skel.y_list[-1] - rightref[len(skel.y_list)] x = skel.left_x + len(skel.y_list) while (x <= right): skel.y_list.append(rightref[x - left] + dy) x += 1 if debug > 0: print "Ready." print
def __call__(self,im_staffonly, n_gap, p_gap, n_shift, random_seed=0, add_noshift=False ): from gamera.core import RGBPixel,Point,FloatPoint,Rect from gamera.toolkits.musicstaves.stafffinder import StafflineSkeleton seed(random_seed) bnp_gap=binomial(n_gap,p_gap) bnp_shift=binomial(n_shift,0.5) im_full=self.image_copy() im_staffless=im_full.xor_image(im_staffonly) im_staffonly=im_staffonly.image_copy() il_shifts=[im_staffless,im_staffonly,im_full] il_breaks=il_shifts if add_noshift: ns_full=im_full.image_copy() ns_staffonly=im_staffonly.image_copy() il_breaks=[im_staffless,im_staffonly,im_full,ns_full,ns_staffonly] #find stafflines [first_x, last_x, stafflines, thickness]=find_stafflines_int(im_staffonly) #find breaks (where are the symbols?) stafflinedist=stafflines[1]-stafflines[0] staffline_skel=[] for sl in range(len(stafflines)): if sl==0 or stafflines[sl]-stafflines[sl-1]>3*stafflinedist: #first staffline of a system first_line=stafflines[sl] lines_per_system=0 lines_per_system=lines_per_system+1 if sl==len(stafflines)-1 or stafflines[sl+1]-stafflines[sl]>3*stafflinedist: #last staffline of a system last_line=stafflines[sl] # a hoizontal projection of the symbols helps us to find the x positions of the symbols. syst=im_staffless.subimage(Point(0,max((0,first_line-3*stafflinedist))),Point(im_staffless.width,min((last_line+3*stafflinedist,im_full.lr_y-1)))) symbolcols=syst.projection_cols() # collect the breaks, i.e. the mid of the white spaces between two symbols breaks=[] whiterun=False for col in range(len(symbolcols)): if (not whiterun) and symbolcols[col]==0: whiterun=True runbegin=col else: if whiterun and symbolcols[col]>0: whiterun=False br=[(runbegin+col)/2,col-runbegin] if br[0]>=first_x: breaks.append(br) # replace the first break (before the first symbol) with a break at the beginning of the stafflines breaks[0]=[first_x,1] # and append a break after the last symbol if whiterun: breaks.append([(last_x+runbegin)/2,last_x-runbegin]) else: breaks.append([last_x,1]) # draw white lines at the breaks new_breaks=[] for br in breaks: w=bnp_gap.random() # draw line if it fits only if w<br[1]: for im in il_breaks: im.draw_line(FloatPoint(br[0],first_line-stafflinedist), FloatPoint(br[0],last_line+stafflinedist), RGBPixel(0,0,0), w) new_breaks.append(br) breaks=new_breaks skeleton=[] # do the vertical shift by making a temporary copy (orig_type), white them out (draw_filled_rect) and "or" them again in with a new y-position for t in range(len(breaks)-1): vertical_shift=bnp_shift.random()-n_shift/2 typerect=Rect(Point(breaks[t][0],max((first_line-3*stafflinedist,0))), Point(breaks[t+1][0],min((last_line+3*stafflinedist,im_full.lr_y)))) moveto=Rect(Point(typerect.ul_x, typerect.ul_y+vertical_shift), typerect.size) if moveto.ul_y<0: typerect.ul_y=typerect.ul_y-moveto.ul_y moveto.ul_y=0 if moveto.lr_y>im_full.lr_y: typerect.lr_y=typerect.lr_y-(moveto.lr_y-im_full.lr_y) moveto.lr_y=im_full.lr_y for im in il_shifts: orig_type=im.subimage(typerect) orig_type=orig_type.image_copy() im.draw_filled_rect(typerect,RGBPixel(0,0,0)) im.subimage(moveto).or_image(orig_type,True) # collect data for later construction of the skeletons for line in range(lines_per_system): if len(skeleton)<=line: skeleton.append([]) st_line=stafflines[sl-lines_per_system+1+line] y=st_line+vertical_shift skeleton[line].append(Point(typerect.ul_x,y)) skeleton[line].append(Point(typerect.ur_x,y)) # now construct the skeletons for sk in skeleton: x=sk[0].x y=sk[0].y left_x=x y_list=[] i=1 while i<len(sk): y_list.append(y) if x>=sk[i].x: y=sk[i].y i=i+1 x=x+1 o=StafflineSkeleton() o.left_x=left_x o.y_list=y_list staffline_skel.append(o) if add_noshift: return [im_full,im_staffonly,staffline_skel,ns_full,ns_staffonly] else: return [im_full,im_staffonly,staffline_skel]