Exemple #1
0
def combine_seperate(lg_layout):
    """Given a line graph layout (List of (homography, line graph) pairs) this merges them all together into a single LineGraph. This version doesn't do anything clever."""
    args = []
    for hg, lg in lg_layout:
        args.append(hg)
        args.append(lg)

    ret = LineGraph()
    ret.from_many(*args)
    return ret
Exemple #2
0
def combine_seperate(lg_layout):
  """Given a line graph layout (List of (homography, line graph) pairs) this merges them all together into a single LineGraph. This version doesn't do anything clever."""
  args = []
  for hg, lg in lg_layout:
    args.append(hg)
    args.append(lg)
  
  ret = LineGraph()
  ret.from_many(*args)
  return ret
Exemple #3
0
def gen_bias(lg, hg):
    """Helper function - creates and returns a bias object for use when creating Glyphs. Basically weights each line with the amount of ink on it, so when a writter uses every other line it strongly biases towards letters being assigned to the lines they wrote on."""
    bias = defaultdict(float)

    # Transform the line graph to line space...
    ls_lg = LineGraph()
    ls_lg.from_many(lg)

    ihg = la.inv(hg)
    ls_lg.transform(ihg, True)

    # Add weight from all of the line segments...
    for ei in xrange(ls_lg.edge_count):
        edge = ls_lg.get_edge(ei)

        vf = ls_lg.get_vertex(edge[0])
        vt = ls_lg.get_vertex(edge[1])

        dx = vt[0] - vf[0]
        dy = vt[1] - vf[1]

        mass = (vf[5] + vt[5]) * numpy.sqrt(dx * dx + dy * dy)
        line = int(numpy.floor(0.5 * (vt[1] + vf[1])))

        bias[line] += mass

    # Normalise and return...
    maximum = max(bias.values())

    for key in bias.keys():
        bias[key] /= maximum

    return bias
Exemple #4
0
def gen_bias(lg, hg):
  """Helper function - creates and returns a bias object for use when creating Glyphs. Basically weights each line with the amount of ink on it, so when a writter uses every other line it strongly biases towards letters being assigned to the lines they wrote on."""
  bias = defaultdict(float)
  
  # Transform the line graph to line space...
  ls_lg = LineGraph()
  ls_lg.from_many(lg)
  
  ihg = la.inv(hg)
  ls_lg.transform(ihg, True)
  
  # Add weight from all of the line segments...
  for ei in xrange(ls_lg.edge_count):
    edge = ls_lg.get_edge(ei)
    
    vf = ls_lg.get_vertex(edge[0])
    vt = ls_lg.get_vertex(edge[1])
    
    dx = vt[0] - vf[0]
    dy = vt[1] - vf[1]
    
    mass = (vf[5] + vt[5]) * numpy.sqrt(dx*dx + dy*dy)
    line = int(numpy.floor(0.5 * (vt[1] + vf[1])))
    
    bias[line] += mass
  
  # Normalise and return...
  maximum = max(bias.values())
  
  for key in bias.keys():
    bias[key] /= maximum
  
  return bias
Exemple #5
0
 def add(self, fn):
   """Given a filename for a linegraph file this loads it, extracts all chunks and stores them in the db."""
   if fn in self.fnl: return 0
   self.fnl.append(fn)
   self.kdtree = None
   
   # Load the LineGraph from the given filename...
   data = ply2.read(fn)
   
   lg = LineGraph()
   lg.from_dict(data)
   
   texture = os.path.normpath(os.path.join(os.path.dirname(fn), data['meta']['image']))
   
   # Calculate the radius scaler and distance for this line graph, by calculating the median radius...
   rads = map(lambda i: lg.get_vertex(i)[5], xrange(lg.vertex_count))
   rads.sort()
   median_radius = rads[len(rads)//2]
   radius_mult = 1.0 / median_radius
   
   dist = self.dist * median_radius
   
   # Chop it up into chains, extract chunks and store them in the database...
   ret = 0
   
   for raw_chain in lg.chains():
     for chain in filter(lambda c: len(c)>1, [raw_chain, raw_chain[::-1]]):
       head = 0
       tail = 0
       length = 0.0
       
       while True:
         # Move tail so its long enough, or has reached the end...
         while length<dist and tail+1<len(chain):
           tail += 1
           v1 = lg.get_vertex(chain[tail-1])
           v2 = lg.get_vertex(chain[tail])
           length += numpy.sqrt((v1[0]-v2[0])**2 + (v1[1]-v2[1])**2)
         
         # Create the chunk...
         chunk = LineGraph()
         chunk.from_vertices(lg, chain[head:tail+1])
         
         # Tag it...
         chunk.add_tag(0, 0.1, 'file:%s'%fn)
         chunk.add_tag(0, 0.2, 'texture:%s'%texture)
         
         # Store it...
         self.chunks.append((chunk, median_radius))
         ret += 1
         
         # If tail is at the end exit the loop...
         if tail+1 >= len(chain): break
         
         # Move head along for the next chunk...
         to_move = dist * self.factor
         while to_move>0.0 and head+2<len(chain):
           head += 1
           v1 = lg.get_vertex(chain[head-1])
           v2 = lg.get_vertex(chain[head])
           offset = numpy.sqrt((v1[0]-v2[0])**2 + (v1[1]-v2[1])**2)
           length -= offset
           to_move -= offset
           
   return ret
Exemple #6
0
  def convert(self, lg, choices = 1, adv_match = False, textures = TextureCache(), memory = 0):
    """Given a line graph this chops it into chunks, matches each chunk to the database of chunks and returns a new line graph with these chunks instead of the original. Output will involve heavy overlap requiring clever blending. choices is the number of options it select from the db - it grabs this many closest to the requirements and then randomly selects from them. If adv_match is True then instead of random selection from the choices it does a more advanced match, and select the best match in terms of colour distance from already-rendered chunks. This option is reasonably expensive. memory is how many recently use chunks to remember, to avoid repetition."""
    if memory > (choices - 1):
      memory = choices - 1

    # If we have no data just return the input...
    if self.empty(): return lg
    
    # Check if the indexing structure is valid - if not create it...
    if self.kdtree==None:
      data = numpy.array(map(lambda p: self.feature_vect(p[0], p[1]), self.chunks), dtype=numpy.float)
      self.kdtree = scipy.spatial.cKDTree(data, 4)
      
    # Calculate the radius scaler and distance for this line graph, by calculating the median radius...
    rads = map(lambda i: lg.get_vertex(i)[5], xrange(lg.vertex_count))
    rads.sort()
    median_radius = rads[len(rads)//2]
    radius_mult = 1.0 / median_radius
    
    dist = self.dist * median_radius
    
    # Create the list into which we dump all the chunks that will make up the return...
    chunks = []
    temp = LineGraph()
    
    # List of recently used chunks, to avoid obvious patterns...
    recent = []
    
    # If advanced match we need a Composite of the image thus far, to compare against...
    if adv_match:
      canvas = Composite()
      min_x, max_x, min_y, max_y = lg.get_bounds()
      canvas.set_size(int(max_x+8), int(max_y+8))
    
    # Iterate the line graph, choping it into chunks and matching a chunk to each chop...
    for chain in lg.chains():
      head = 0
      tail = 0
      length = 0.0
        
      while True:
        # Move tail so its long enough, or has reached the end...
        while length<dist and tail+1<len(chain):
          tail += 1
          v1 = lg.get_vertex(chain[tail-1])
          v2 = lg.get_vertex(chain[tail])
          length += numpy.sqrt((v1[0]-v2[0])**2 + (v1[1]-v2[1])**2)

        # Extract a feature vector for this chunk...
        temp.from_vertices(lg, chain[head:tail+1])
        fv = self.feature_vect(temp, median_radius)
        
        # Select a chunk from the database...
        if choices==1:
          selected = self.kdtree.query(fv)[1]
          orig_chunk = self.chunks[selected]
        else:
          options = list(self.kdtree.query(fv, choices)[1])
          options = filter(lambda v: v not in recent, options)
          if not adv_match:
            selected = random.choice(options)
            orig_chunk = self.chunks[selected]
          else:
            cost = 1e64 * numpy.ones(len(options))
            
            for i, option in enumerate(options):
              fn = filter(lambda t: t[0].startswith('texture:'), self.chunks[option][0].get_tags())
              if len(fn)!=0:
                fn = fn[0][0][len('texture:'):]
                tex = textures[fn]
                
                chunk = LineGraph()
                chunk.from_many(self.chunks[option][0])
                chunk.morph_to(lg, chain[head:tail+1])
              
                part = canvas.draw_line_graph(chunk)
                cost[i] = canvas.cost_texture_nearest(tex, part)
            
            selected = options[numpy.argmin(cost)]
            orig_chunk = self.chunks[selected]
        
        # Update recent list...
        recent.append(selected)
        if len(recent)>memory:
          recent.pop(0)

        # Distort it to match the source line graph...
        chunk = LineGraph()
        chunk.from_many(orig_chunk[0])
        chunk.morph_to(lg, chain[head:tail+1])
        
        # Record it for output...
        chunks.append(chunk)
        
        # If advanced matching is on write it out to canvas, so future choices will take it into account...
        if adv_match:
          fn = filter(lambda t: t[0].startswith('texture:'), chunk.get_tags())
          if len(fn)!=0:
            fn = fn[0][0][len('texture:'):]
            tex = textures[fn]

            part = canvas.draw_line_graph(chunk)
            canvas.paint_texture_nearest(tex, part)
         
        # If tail is at the end exit the loop...
        if tail+1 >= len(chain): break
          
        # Move head along for the next chunk...
        to_move = dist * self.factor
        while to_move>0.0 and head+2<len(chain):
          head += 1
          v1 = lg.get_vertex(chain[head-1])
          v2 = lg.get_vertex(chain[head])
          offset = numpy.sqrt((v1[0]-v2[0])**2 + (v1[1]-v2[1])**2)
          length -= offset
          to_move -= offset

    # Return the final line graph...
    ret = LineGraph()
    ret.from_many(*chunks)
    return ret
Exemple #7
0
    def add(self, fn):
        """Given a filename for a linegraph file this loads it, extracts all chunks and stores them in the db."""
        if fn in self.fnl: return 0
        self.fnl.append(fn)
        self.kdtree = None

        # Load the LineGraph from the given filename...
        data = ply2.read(fn)

        lg = LineGraph()
        lg.from_dict(data)

        texture = os.path.normpath(
            os.path.join(os.path.dirname(fn), data['meta']['image']))

        # Calculate the radius scaler and distance for this line graph, by calculating the median radius...
        rads = map(lambda i: lg.get_vertex(i)[5], xrange(lg.vertex_count))
        rads.sort()
        median_radius = rads[len(rads) // 2]
        radius_mult = 1.0 / median_radius

        dist = self.dist * median_radius

        # Chop it up into chains, extract chunks and store them in the database...
        ret = 0

        for raw_chain in lg.chains():
            for chain in filter(lambda c: len(c) > 1,
                                [raw_chain, raw_chain[::-1]]):
                head = 0
                tail = 0
                length = 0.0

                while True:
                    # Move tail so its long enough, or has reached the end...
                    while length < dist and tail + 1 < len(chain):
                        tail += 1
                        v1 = lg.get_vertex(chain[tail - 1])
                        v2 = lg.get_vertex(chain[tail])
                        length += numpy.sqrt((v1[0] - v2[0])**2 +
                                             (v1[1] - v2[1])**2)

                    # Create the chunk...
                    chunk = LineGraph()
                    chunk.from_vertices(lg, chain[head:tail + 1])

                    # Tag it...
                    chunk.add_tag(0, 0.1, 'file:%s' % fn)
                    chunk.add_tag(0, 0.2, 'texture:%s' % texture)

                    # Store it...
                    self.chunks.append((chunk, median_radius))
                    ret += 1

                    # If tail is at the end exit the loop...
                    if tail + 1 >= len(chain): break

                    # Move head along for the next chunk...
                    to_move = dist * self.factor
                    while to_move > 0.0 and head + 2 < len(chain):
                        head += 1
                        v1 = lg.get_vertex(chain[head - 1])
                        v2 = lg.get_vertex(chain[head])
                        offset = numpy.sqrt((v1[0] - v2[0])**2 +
                                            (v1[1] - v2[1])**2)
                        length -= offset
                        to_move -= offset

        return ret
Exemple #8
0
    def convert(self,
                lg,
                choices=1,
                adv_match=False,
                textures=TextureCache(),
                memory=0):
        """Given a line graph this chops it into chunks, matches each chunk to the database of chunks and returns a new line graph with these chunks instead of the original. Output will involve heavy overlap requiring clever blending. choices is the number of options it select from the db - it grabs this many closest to the requirements and then randomly selects from them. If adv_match is True then instead of random selection from the choices it does a more advanced match, and select the best match in terms of colour distance from already-rendered chunks. This option is reasonably expensive. memory is how many recently use chunks to remember, to avoid repetition."""
        if memory > (choices - 1):
            memory = choices - 1

        # If we have no data just return the input...
        if self.empty(): return lg

        # Check if the indexing structure is valid - if not create it...
        if self.kdtree == None:
            data = numpy.array(map(lambda p: self.feature_vect(p[0], p[1]),
                                   self.chunks),
                               dtype=numpy.float)
            self.kdtree = scipy.spatial.cKDTree(data, 4)

        # Calculate the radius scaler and distance for this line graph, by calculating the median radius...
        rads = map(lambda i: lg.get_vertex(i)[5], xrange(lg.vertex_count))
        rads.sort()
        median_radius = rads[len(rads) // 2]
        radius_mult = 1.0 / median_radius

        dist = self.dist * median_radius

        # Create the list into which we dump all the chunks that will make up the return...
        chunks = []
        temp = LineGraph()

        # List of recently used chunks, to avoid obvious patterns...
        recent = []

        # If advanced match we need a Composite of the image thus far, to compare against...
        if adv_match:
            canvas = Composite()
            min_x, max_x, min_y, max_y = lg.get_bounds()
            canvas.set_size(int(max_x + 8), int(max_y + 8))

        # Iterate the line graph, choping it into chunks and matching a chunk to each chop...
        for chain in lg.chains():
            head = 0
            tail = 0
            length = 0.0

            while True:
                # Move tail so its long enough, or has reached the end...
                while length < dist and tail + 1 < len(chain):
                    tail += 1
                    v1 = lg.get_vertex(chain[tail - 1])
                    v2 = lg.get_vertex(chain[tail])
                    length += numpy.sqrt((v1[0] - v2[0])**2 +
                                         (v1[1] - v2[1])**2)

                # Extract a feature vector for this chunk...
                temp.from_vertices(lg, chain[head:tail + 1])
                fv = self.feature_vect(temp, median_radius)

                # Select a chunk from the database...
                if choices == 1:
                    selected = self.kdtree.query(fv)[1]
                    orig_chunk = self.chunks[selected]
                else:
                    options = list(self.kdtree.query(fv, choices)[1])
                    options = filter(lambda v: v not in recent, options)
                    if not adv_match:
                        selected = random.choice(options)
                        orig_chunk = self.chunks[selected]
                    else:
                        cost = 1e64 * numpy.ones(len(options))

                        for i, option in enumerate(options):
                            fn = filter(lambda t: t[0].startswith('texture:'),
                                        self.chunks[option][0].get_tags())
                            if len(fn) != 0:
                                fn = fn[0][0][len('texture:'):]
                                tex = textures[fn]

                                chunk = LineGraph()
                                chunk.from_many(self.chunks[option][0])
                                chunk.morph_to(lg, chain[head:tail + 1])

                                part = canvas.draw_line_graph(chunk)
                                cost[i] = canvas.cost_texture_nearest(
                                    tex, part)

                        selected = options[numpy.argmin(cost)]
                        orig_chunk = self.chunks[selected]

                # Update recent list...
                recent.append(selected)
                if len(recent) > memory:
                    recent.pop(0)

                # Distort it to match the source line graph...
                chunk = LineGraph()
                chunk.from_many(orig_chunk[0])
                chunk.morph_to(lg, chain[head:tail + 1])

                # Record it for output...
                chunks.append(chunk)

                # If advanced matching is on write it out to canvas, so future choices will take it into account...
                if adv_match:
                    fn = filter(lambda t: t[0].startswith('texture:'),
                                chunk.get_tags())
                    if len(fn) != 0:
                        fn = fn[0][0][len('texture:'):]
                        tex = textures[fn]

                        part = canvas.draw_line_graph(chunk)
                        canvas.paint_texture_nearest(tex, part)

                # If tail is at the end exit the loop...
                if tail + 1 >= len(chain): break

                # Move head along for the next chunk...
                to_move = dist * self.factor
                while to_move > 0.0 and head + 2 < len(chain):
                    head += 1
                    v1 = lg.get_vertex(chain[head - 1])
                    v2 = lg.get_vertex(chain[head])
                    offset = numpy.sqrt((v1[0] - v2[0])**2 +
                                        (v1[1] - v2[1])**2)
                    length -= offset
                    to_move -= offset

        # Return the final line graph...
        ret = LineGraph()
        ret.from_many(*chunks)
        return ret
Exemple #9
0
    def __init__(self, lg, seg, hg, extra=0.4, bias=None):
        """Given a segmented LineGraph and segment number this extracts it, transforms it into the standard coordinate system and stores the homography used to get there. (hg transforms from line space, where there is a line for each y=integer, to the space of the original pixels.) Also records its position on its assigned line and line number so it can be ordered suitably. Does not store connectivity information - that is done later. extra is used for infering the line position, and is extra falloff to have either side of a line voting for it - a smoothing term basically. bias is an optional dictionary indexed by line number that gives a weight to assign to being assigned to that line - used to utilise the fact that data collection asks the writter to use every-other line, which helps avoid misassigned dropped j's for instance."""
        if lg == None: return

        # Extract the line graph...
        self.lg = LineGraph()
        self.adjacent = self.lg.from_segment(lg, seg)
        self.seg = seg

        # Tranform it to line space...
        ihg = la.inv(hg)
        self.lg.transform(ihg, True)

        # Check if which line its on is tagged - exists as an override for annoying glyphs...
        line = None
        for tag in self.lg.get_tags():
            if tag[0] == 'line':
                # We have a tag of line - its position specifies the line the glyph is on...
                point = self.lg.get_point(tag[1], tag[2])
                line = int(numpy.floor(point[1]))
                break

        # Record which line it is on and its position along the line...
        # (Works by assuming that the line is the one below the space where most of the mass of the glyph is. Takes it range to be that within the space, so crazy tails are cut off.)
        min_x, max_x, min_y, max_y = self.lg.get_bounds()
        self.source = (0.5 * (min_x + max_x), 0.5 * (min_y + max_y))

        if line == None:
            best_mass = 0.0
            self.left_x = min_x
            self.right_x = max_x
            line = 0

            start = int(numpy.trunc(min_y))
            for pl in xrange(start, int(numpy.ceil(max_y))):
                mass = 0.0
                low_y = float(pl) - extra
                high_y = float(pl + 1) + extra

                left_x = None
                right_x = None

                for es in self.lg.within(min_x, max_x, low_y, high_y):
                    for ei in xrange(*es.indices(self.lg.edge_count)):
                        edge = self.lg.get_edge(ei)
                        vf = self.lg.get_vertex(edge[0])
                        vt = self.lg.get_vertex(edge[1])

                        if vf[1] > low_y and vf[1] < high_y and vt[
                                1] > low_y and vt[1] < high_y:
                            dx = vt[0] - vf[0]
                            dy = vt[1] - vf[1]
                            mass += (vf[5] + vt[5]) * numpy.sqrt(dx * dx +
                                                                 dy * dy)

                            if left_x == None: left_x = min(vf[0], vt[0])
                            else: left_x = min(vf[0], vt[0], left_x)

                            if right_x == None: right_x = max(vf[0], vt[0])
                            else: right_x = max(vf[0], vt[0], right_x)

                mass *= 1.0 / (1.0 + pl - start
                               )  # Bias to choosing higher, for tails.

                if bias != None:
                    mass *= bias[pl]

                if mass > best_mass:
                    best_mass = mass
                    self.left_x = left_x
                    self.right_x = right_x
                    line = pl

        # Transform it so it is positioned to be sitting on line 1 of y, store the total homography that we have applied...
        self.offset_x = -min_x
        self.offset_y = -line

        hg = numpy.eye(3, dtype=numpy.float32)
        hg[0, 2] = self.offset_x
        hg[1, 2] = self.offset_y

        self.left_x += self.offset_x
        self.right_x += self.offset_x

        self.lg.transform(hg)

        self.transform = numpy.dot(hg, ihg)

        # Set as empty its before and after glyphs - None if there is no adjacency, or a tuple if there is: (glyph, list of connecting (link glyph, shared vertex in this, shared vertex in glyph, vertex in link glyph on this side, vertex in link glyph on glyph side), empty if none.)...
        self.left = None
        self.right = None

        # Extract the character this glyph represents...
        tags = self.lg.get_tags()
        codes = [
            t[0] for t in tags if len(filter(lambda c: c != '_', t[0])) == 1
        ]
        self.key = codes[0] if len(codes) != 0 else None

        self.code = -id(self)

        # Cache stuff...
        self.mass = None
        self.center = None
        self.feat = None
        self.v_offset = None
Exemple #10
0
class Glyph:
  """Represents a glyph, that has been transformed into a suitable coordinate system; includes connectivity information."""
  def __init__(self, lg, seg, hg, extra = 0.4, bias = None):
    """Given a segmented LineGraph and segment number this extracts it, transforms it into the standard coordinate system and stores the homography used to get there. (hg transforms from line space, where there is a line for each y=integer, to the space of the original pixels.) Also records its position on its assigned line and line number so it can be ordered suitably. Does not store connectivity information - that is done later. extra is used for infering the line position, and is extra falloff to have either side of a line voting for it - a smoothing term basically. bias is an optional dictionary indexed by line number that gives a weight to assign to being assigned to that line - used to utilise the fact that data collection asks the writter to use every-other line, which helps avoid misassigned dropped j's for instance."""
    if lg==None: return

    # Extract the line graph...
    self.lg = LineGraph()
    self.adjacent = self.lg.from_segment(lg, seg)
    self.seg = seg
    
    # Tranform it to line space...
    ihg = la.inv(hg)
    self.lg.transform(ihg, True)
    
    # Check if which line its on is tagged - exists as an override for annoying glyphs...
    line = None
    for tag in self.lg.get_tags():
      if tag[0]=='line':
        # We have a tag of line - its position specifies the line the glyph is on...
        point = self.lg.get_point(tag[1], tag[2])
        line = int(numpy.floor(point[1]))
        break
    
    # Record which line it is on and its position along the line...
    # (Works by assuming that the line is the one below the space where most of the mass of the glyph is. Takes it range to be that within the space, so crazy tails are cut off.)
    min_x, max_x, min_y, max_y = self.lg.get_bounds()
    self.source = (0.5 * (min_x + max_x), 0.5 * (min_y + max_y))
    
    if line==None:   
      best_mass = 0.0
      self.left_x = min_x
      self.right_x = max_x
      line = 0
    
      start = int(numpy.trunc(min_y))
      for pl in xrange(start, int(numpy.ceil(max_y))):
        mass = 0.0
        low_y = float(pl) - extra
        high_y = float(pl+1) + extra
      
        left_x = None
        right_x = None
      
        for es in self.lg.within(min_x, max_x, low_y, high_y):
          for ei in xrange(*es.indices(self.lg.edge_count)):
            edge = self.lg.get_edge(ei)
            vf = self.lg.get_vertex(edge[0])
            vt = self.lg.get_vertex(edge[1])
          
            if vf[1]>low_y and vf[1]<high_y and vt[1]>low_y and vt[1]<high_y:
              dx = vt[0] - vf[0]
              dy = vt[1] - vf[1]
              mass += (vf[5] + vt[5]) * numpy.sqrt(dx*dx + dy*dy)
            
              if left_x==None: left_x = min(vf[0], vt[0])
              else: left_x = min(vf[0], vt[0], left_x)
            
              if right_x==None: right_x = max(vf[0], vt[0])
              else: right_x = max(vf[0], vt[0], right_x)
      
        mass *= 1.0/(1.0+pl - start) # Bias to choosing higher, for tails.
        
        if bias!=None:
          mass *= bias[pl]
      
        if mass>best_mass:
          best_mass = mass
          self.left_x = left_x
          self.right_x = right_x
          line = pl
    
    # Transform it so it is positioned to be sitting on line 1 of y, store the total homography that we have applied...
    self.offset_x = -min_x
    self.offset_y = -line
    
    hg = numpy.eye(3, dtype=numpy.float32)
    hg[0,2] = self.offset_x
    hg[1,2] = self.offset_y
    
    self.left_x += self.offset_x
    self.right_x += self.offset_x
    
    self.lg.transform(hg)
    
    self.transform = numpy.dot(hg, ihg)
    
    # Set as empty its before and after glyphs - None if there is no adjacency, or a tuple if there is: (glyph, list of connecting (link glyph, shared vertex in this, shared vertex in glyph, vertex in link glyph on this side, vertex in link glyph on glyph side), empty if none.)...
    self.left = None
    self.right = None
    
    # Extract the character this glyph represents...
    tags = self.lg.get_tags()
    codes = [t[0] for t in tags if len(filter(lambda c: c!='_', t[0]))==1]
    self.key = codes[0] if len(codes)!=0 else None
    
    self.code = -id(self)
    
    # Cache stuff...
    self.mass = None
    self.center = None
    self.feat = None
    self.v_offset = None


  def clone(self):
    """Returns a clone of this Glyph."""
    ret = Glyph(None, None, None)
    
    ret.lg = self.lg
    ret.adjacent = self.adjacent
    ret.seg = self.seg
    
    ret.source = self.source
    
    ret.left_x = self.left_x
    ret.right_x = self.right_x
    
    ret.offset_x = self.offset_x
    ret.offset_y = self.offset_y
    ret.transform = self.transform
    
    ret.left = self.left
    ret.right = self.right
    ret.key = self.key
    
    ret.code = self.code
    
    ret.mass = None if self.mass==None else self.mass.copy()
    ret.center = None if self.center==None else self.center.copy()
    ret.feat = None if self.feat==None else map(lambda a: a.copy(), self.feat)
    ret.v_offset = self.v_offset
    
    return ret
    
    
  def get_linegraph(self):
    return self.lg
  
  
  def orig_left_x(self):
    return self.left_x - self.offset_x
  
  def orig_right_x(self):
    return self.right_x - self.offset_x

  
  def get_mass(self):
    """Returns a vector of [average density, average radius] - used for matching adjacent glyphs."""
    if self.mass==None:
      self.mass = numpy.zeros(2, dtype=numpy.float32)
      weight = 0.0
      for i in xrange(self.lg.vertex_count):
        info = self.lg.get_vertex(i)
        
        weight += 1.0
        self.mass += (numpy.array([info[6], info[5]]) - self.mass) / weight
    
    return self.mass


  def get_center(self):
    """Returns the 'center' of the glyph - its density weighted in an attempt to make it robust to crazy tails."""
    if self.center==None:
      self.center = numpy.zeros(2, dtype=numpy.float32)
      weight = 0.0
    
      for i in xrange(self.lg.vertex_count):
        info = self.lg.get_vertex(i)
        w = info[5] * info[5] * info[6] # Radius squared * density - proportional to quantity of ink, assuming (correctly as rest of system currently works) even sampling.
        if w>1e-6:
          weight += w
          mult = w / weight
          self.center[0] += (info[0] - self.center[0]) * mult
          self.center[1] += (info[1] - self.center[1]) * mult
    
    return self.center
  
  
  def get_voffset(self):
    """Calculates and returns the vertical offset to apply to the glyph that corrects for any systematic bias in its flow calculation."""
    
    if self.v_offset==None:
      self.v_offset = 0.0
      weight = 0.0
      
      truth = self.get_center()[1]
      
      # Calculate the estimated offsets from the left side and update the estimate, correctly factoring in the variance of the offset...
      if self.left!=None:
        diff, sd = costs.glyph_pair_offset(self.left[0], self, 0.2, True)
        estimate = self.left[0].get_center()[1] + diff
        offset = truth - estimate
        
        est_weight = 1.0 / (sd**2.0)
        weight += est_weight
        self.v_offset += (offset - self.v_offset) * est_weight / weight
    
      # Again from the right side...
      if self.right!=None:
        diff, sd = costs.glyph_pair_offset(self, self.right[0], 0.2, True)
        estimate = self.right[0].get_center()[1] - diff
        offset = truth - estimate
        
        est_weight = 1.0 / (sd**2.0)
        weight += est_weight
        self.v_offset += (offset - self.v_offset) * est_weight / weight
  
    return self.v_offset


  def most_left(self):
    """Returns the coordinate of the furthest left vertex in the glyph."""
    
    info = self.lg.get_vertex(0)
    best_x = info[0]
    best_y = info[1]
    
    for i in xrange(1,self.lg.vertex_count):
      info = self.lg.get_vertex(0)
      if info[0]<best_x:
        best_x = info[0]
        best_y = info[1]
    
    return (best_x, best_y)

  def most_right(self):
    """Returns the coordinate of the furthest right vertex in the glyph."""
    
    info = self.lg.get_vertex(0)
    best_x = info[0]
    best_y = info[1]
    
    for i in xrange(1,self.lg.vertex_count):
      info = self.lg.get_vertex(0)
      if info[0]>best_x:
        best_x = info[0]
        best_y = info[1]
    
    return (best_x, best_y)
  
  
  def get_feat(self):
    """Calculates and returns a feature for the glyph, or, more accuratly two features, representing (left, right), so some tricks can be done to make their use side dependent (For learning a function for matching to adjacent glyphs.)."""
    if self.feat==None:
      # First build a culumative distribution over the x axis range of the glyph...
      min_x, max_x, min_y, max_y = self.lg.get_bounds()
      culm = numpy.ones(32, dtype=numpy.float32)
      culm *= 1e-2
      
      min_x -= 1e-3
      max_x += 1e-3
      
      for i in xrange(self.lg.vertex_count):
        info = self.lg.get_vertex(i)
        w = info[5] * info[5] * info[6]
        t = (info[0] - min_x) / (max_x - min_x)
        
        t *= (culm.shape[0]-1)
        low = int(t)
        high = low + 1
        t -= low
        culm[low] += (1.0 - t) * w
        culm[high] += t * w
      
      culm /= culm.sum()
      culm = numpy.cumsum(culm)
      
      # Now extract all the per sample features...
      feat_param = {'dir_travel':0.1, 'travel_max':1.0, 'travel_bins':6, 'travel_ratio':0.8, 'pos_bins':3, 'pos_ratio':0.9, 'radius_bins':1, 'density_bins':3}
      fv = self.lg.features(**feat_param)
      
      # Combine them into the two halves, by weighting by the culumative; include density and radius as well...
      left = numpy.zeros(fv.shape[1]+2, dtype=numpy.float32)
      right = numpy.zeros(fv.shape[1]+2, dtype=numpy.float32)
      
      left_total = 0.0
      right_total = 0.0
      
      for i in xrange(self.lg.vertex_count):
        info = self.lg.get_vertex(i)
        w = info[5] * info[5] * info[6]
        t = (info[0] - min_x) / (max_x - min_x)
        
        t *= (culm.shape[0]-1)
        low = int(t)
        high = low + 1
        t -= low
        right_w = (1.0-t) * culm[low] + t * culm[high]
        left_w = 1.0 - right_w
        
        left[0] += left_w * info[5]
        right[0] += right_w * info[5]
        
        left[1] += left_w * info[6]
        right[1] += right_w * info[6]
        
        left[2:] += w * left_w * fv[i,:]
        right[2:] += w * right_w * fv[i,:]
        
        left_total += left_w
        right_total += right_w
      
      left[:2] /= left_total
      right[:2] /= right_total
      left[2:] /= max(left[2:].sum(), 1e-6)
      right[2:] /= max(right[2:].sum(), 1e-6)
      
      self.feat = (left, right)
    
    return self.feat


  def __str__(self):
    l = self.left[0].key if self.left!=None else 'None'
    r = self.right[0].key if self.right!=None else 'None'
    return 'Glyph %i: key = %s (%s|%s)' % (self.code, self.key, l, r)
Exemple #11
0
  def add(self, fn):
    """Given the filename for a LineGraph file this loads it in and splits it into Glyphs, which it dumps into the db. Does nothing if the file is already loaded. returns the number of glyphs added."""
    
    if fn in self.fnl: return 0
    self.fnl.append(fn)
    
    # Load the LineGraph from the given filename, and get the homography...
    f = open(fn, 'r')
    data = ply2.read(f)
    f.close()
    
    lg = LineGraph()
    lg.from_dict(data)
    lg.segment()
    
    hg = data['element']['homography']['v'].reshape((3,3))
    
    texture = os.path.normpath(os.path.join(os.path.dirname(fn), data['meta']['image']))
    
    
    # Create a line bias object to be used in the next step...
    bias = gen_bias(lg, hg)
    
    # First pass - create each glyph object...
    glyphs = []
    for s in xrange(lg.segments):
      g = Glyph(lg, s, hg, bias = bias)
      glyphs.append(g if '_' not in map(lambda t: t[0], g.lg.get_tags()) else None)


    # Second pass - fill in the connectivity information supported by adjacency...
    link_glyphs = []
    for seg, glyph in enumerate(glyphs):
      if glyph==None: continue
      if glyph.key==None: continue
      
      # Brute force search to find a left partner...
      if glyph.left==None:
        best = None
        for g in glyphs:
          # Check it satisfies the conditions...
          if g==None: continue
          if g.key==None: continue;
          if id(g)==id(glyph): continue
          if g.offset_y!=glyph.offset_y: continue
          if (g.right_x - g.offset_x) > (glyph.right_x - glyph.offset_x): continue
          
          # Check its better than the current best...
          if best==None or (best.right_x - best.offset_x) < (g.right_x - g.offset_x):
            best = g
        
        if best!=None:
          glyph.left = (best, [])
      
      # Brute force search to find a right partner...
      if glyph.right==None:
        best = None
        for g in glyphs:
          # Check it satisfies the conditions...
          if g==None: continue
          if g.key==None: continue;
          if id(g)==id(glyph): continue
          if g.offset_y!=glyph.offset_y: continue
          if (g.left_x - g.offset_x) < (glyph.left_x - glyph.offset_x): continue
          
          # Check its better than the current best...
          if best==None or (best.left_x - best.offset_x) > (g.left_x - g.offset_x):
            best = g
        
        if best!=None:
          glyph.right = (best, [])


      # Now we have the best matches find common glyphs to link them, and record them...
      for other, out in [g for g in [glyph.left, glyph.right] if g!=None]:
        shared_seg = set([a[1] for a in glyph.adjacent]) & set([a[1] for a in other.adjacent])
        
        for seg in shared_seg:
          g = glyphs[seg]
          if g==None: continue
          
          # We have a linking glyph - extract the information...
          glyph_vert = [a[0] for a in glyph.adjacent if a[1]==seg]
          other_vert = [a[0] for a in other.adjacent if a[1]==seg]
          
          link_glyph = [a[0] for a in g.adjacent if a[1]==glyph.seg]
          link_other = [a[0] for a in g.adjacent if a[1]==other.seg]
          
          # Check if we have a multi-link scenario - if so choose links...
          if len(glyph_vert)>1 or len(other_vert)>1:
            gv_y = map(lambda v: glyph.lg.get_vertex(v)[1], glyph_vert)
            ov_y = map(lambda v: other.lg.get_vertex(v)[1], other_vert)
            
            if (max(gv_y) - min(ov_y)) > (max(ov_y) - min(gv_y)):
              glyph_vert = glyph_vert[numpy.argmax(gv_y)]
              other_vert = other_vert[numpy.argmin(ov_y)]
            else:
              glyph_vert = glyph_vert[numpy.argmin(gv_y)]
              other_vert = other_vert[numpy.argmax(ov_y)]
            
            lg_y = map(lambda v: g.lg.get_vertex(v)[1], link_glyph)
            lo_y = map(lambda v: g.lg.get_vertex(v)[1], link_other)

            if (max(lg_y) - min(lo_y)) > (max(lo_y) - min(lg_y)):
              link_glyph = link_glyph[numpy.argmax(lg_y)]
              link_other = link_other[numpy.argmin(lo_y)]
            else:
              link_glyph = link_glyph[numpy.argmin(lg_y)]
              link_other = link_other[numpy.argmax(lo_y)]

          else:
            # Simple scenario...
            glyph_vert = glyph_vert[0]
            other_vert = other_vert[0]
            
            link_glyph = link_glyph[0]
            link_other = link_other[0]
          
          # Recreate the link as a simple path - its the only way to be safe!..
          try:
            g = g.clone()
            nlg = LineGraph()
            link_glyph, link_other = nlg.from_path(g.lg, link_glyph, link_other)
            g.lg = nlg
            link_glyphs.append(g)
          except:
            continue # Line is broken in the centre - don't use it.
          
          # Add the tuple to the storage...
          out.append((g, glyph_vert, other_vert, link_glyph, link_other))


    # Third pass - enforce consistancy...
    for glyph in glyphs:
      if glyph==None: continue
      
      if glyph.left!=None:
        if glyph.left[0].right==None or id(glyph.left[0].right[0])!=id(glyph):
          # Inconsistancy - break it...
          glyph.left[0].right = None
          glyph.left = None
          
      if glyph.right!=None:
        if glyph.right[0].left==None or id(glyph.right[0].left[0])!=id(glyph):
          # Inconsistancy - break it...
          glyph.right[0].left = None
          glyph.right = None
    
    
    # Forth pass - add filename tags and add to db (Count and return the glyph count)...
    count = 0
    
    for glyph in (glyphs+link_glyphs):
      if glyph==None: continue
      
      glyph.lg.add_tag(0, 0.1, 'file:%s'%fn)
      glyph.lg.add_tag(0, 0.2, 'texture:%s'%texture)
      
      glyph.lg.add_tag(0, 0.3, 'link:left:%s'%('n' if glyph.left==None else ('g' if len(glyph.left[1])==0 else 'l')))
      glyph.lg.add_tag(0, 0.4, 'link:right:%s'%('n' if glyph.right==None else ('g' if len(glyph.right[1])==0 else 'l')))
      
      glyph.lg.add_tag(0, 0.5, 'source:x:%f' % glyph.source[0])
      glyph.lg.add_tag(0, 0.6, 'source:y:%f' % glyph.source[1])
      
      if glyph.key!=None:
        count += 1
        if glyph.key in self.db: self.db[glyph.key].append(glyph)
        else: self.db[glyph.key] = [glyph]
      
      glyph.code = len(self.by_code)
      glyph.lg.add_tag(0, 0.5, 'code:%i'%glyph.code)
      self.by_code.append(glyph)
    
    return count
Exemple #12
0
def render(lg,
           border=8,
           textures=TextureCache(),
           cleverness=0,
           radius_growth=3.0,
           stretch_weight=0.5,
           edge_weight=0.5,
           smooth_weight=2.0,
           alpha_weight=1.0,
           unary_mult=1.0,
           overlap_weight=0.0,
           use_linear=True):
    """Given a line_graph this will render it, returning a numpy array that represents an image (As the first element in a tuple - second element is how many graph cut problems it solved.). It will transform the entire linegraph to obtain a suitable border. The cleverness parameter indicates how it merges the many bits - 0 means last layer (stupid), 1 means averaging; 2 selecting a border using max flow; 3 using graph cuts to take into account weight as well."""

    # Setup the compositor...
    comp = Composite()
    min_x, max_x, min_y, max_y = lg.get_bounds()

    do_transform = False
    offset_x = 0.0
    offset_y = 0.0

    if min_x < border:
        do_transform = True
        offset_x = border - min_x

    if min_y < border:
        do_transform = True
        offset_y = border - min_y

    if do_transform:
        hg = numpy.eye(3, dtype=numpy.float32)
        hg[0, 2] = offset_x
        hg[1, 2] = offset_y

        lg.transform(hg)

        max_x += offset_x
        max_y += offset_y

    comp.set_size(int(max_x + border), int(max_y + border))

    # Break the lg into segments, as each can have its own image - draw & paint each in turn...
    lg.segment()
    duplicate_sets = dict()

    for s in xrange(lg.segments):

        slg = LineGraph()
        slg.from_segment(lg, s)
        part = comp.draw_line_graph(slg, radius_growth, stretch_weight)

        done = False
        fn = filter(lambda t: t[0].startswith('texture:'), slg.get_tags())
        if len(fn) != 0: fn = fn[0][0][len('texture:'):]
        else: fn = None

        for pair in filter(lambda t: t[0].startswith('duplicate:'),
                           slg.get_tags()):
            key = pair[0][len('duplicate:'):]
            if key in duplicate_sets: duplicate_sets[key].append(part)
            else: duplicate_sets[key] = [part]

        tex = textures[fn]

        if tex is not None:
            if use_linear:
                comp.paint_texture_linear(tex, part)
            else:
                comp.paint_texture_nearest(tex, part)
            done = True

        if not done:
            comp.paint_test_pattern(part)

    # Bias towards pixels that are opaque...
    comp.inc_weight_alpha(alpha_weight)

    # Arrange for duplicate pairs to have complete overlap, by adding transparent pixels, so graph cuts doesn't create a feather effect...
    if overlap_weight > 1e-6:
        for values in duplicate_sets.itervalues():
            for i, part1 in enumerate(values):
                for part2 in values[i:]:
                    comp.draw_pair(part1, part2, overlap_weight)

    # If requested use maxflow to find optimal cuts, to avoid any real blending...
    count = 0
    if cleverness == 2:
        count = comp.maxflow_select(edge_weight, smooth_weight, maxflow)
    elif cleverness == 3:
        count = comp.graphcut_select(edge_weight, smooth_weight, unary_mult,
                                     maxflow)

    if cleverness == 0:
        render = comp.render_last()
    else:
        render = comp.render_average()

    # Return the rendered image (If cleverness==0 this will actually do some averaging, otherwise it will just create an image)...
    return render, count
Exemple #13
0
def stitch_connect(glyph_layout, soft=True, half=False, pair_base=0):
    """Converts a glyph layout to a linegraph layout. This stitches together the glyphs when it has sufficient information to do so."""
    ret = []

    # First copy over the actual glyphs...
    for pair in glyph_layout:
        if pair is not None:
            hg, glyph = pair
            ret.append((hg, glyph.lg))

    # Now loop through and identify all pairs that can be stitched together, and stitch them...
    pair_code = 0
    for i in xrange(len(glyph_layout) - 1):
        # Can't stitch spaces...
        if glyph_layout[i] is not None and glyph_layout[i + 1] is not None:
            l_hg, l_glyph = glyph_layout[i]
            r_hg, r_glyph = glyph_layout[i + 1]

            matches = costs.match_links(l_glyph, r_glyph)

            # Iterate and do each pairing in turn...
            for ml, mr in matches:
                # Calculate the homographies to put the two line graphs into position...
                lc_hg = numpy.dot(
                    l_hg, numpy.dot(l_glyph.transform,
                                    la.inv(ml[0].transform)))
                rc_hg = numpy.dot(
                    r_hg, numpy.dot(r_glyph.transform,
                                    la.inv(mr[0].transform)))

                # Copy the links, applying the homographies...
                lc = LineGraph()
                lc.from_many(lc_hg, ml[0].lg)

                rc = LineGraph()
                rc.from_many(rc_hg, mr[0].lg)

                # Extract the merge points...
                blend = [(ml[3], 0.0, mr[4]), (ml[4], 1.0, mr[3])]

                # Do the blending...
                lc.blend(rc, blend, soft)

                # Record via tagging that the two parts are the same entity...
                pair = 'duplicate:%i,%i' % (pair_base, pair_code)
                lc.add_tag(0, 0.5, pair)
                rc.add_tag(0, 0.5, pair)
                pair_code += 1

                # Store the pair of line graphs in the return, with identity homographies...
                ret.append((numpy.eye(3), lc))
                if not half: ret.append((numpy.eye(3), rc))

    return ret
Exemple #14
0
def render(lg, border = 8, textures = TextureCache(), cleverness = 0, radius_growth = 3.0, stretch_weight = 0.5, edge_weight = 0.5, smooth_weight = 2.0, alpha_weight = 1.0, unary_mult = 1.0, overlap_weight = 0.0, use_linear = True):
  """Given a line_graph this will render it, returning a numpy array that represents an image (As the first element in a tuple - second element is how many graph cut problems it solved.). It will transform the entire linegraph to obtain a suitable border. The cleverness parameter indicates how it merges the many bits - 0 means last layer (stupid), 1 means averaging; 2 selecting a border using max flow; 3 using graph cuts to take into account weight as well."""

  # Setup the compositor...
  comp = Composite()
  min_x, max_x, min_y, max_y = lg.get_bounds()
  
  do_transform = False
  offset_x = 0.0
  offset_y = 0.0
  
  if min_x<border:
    do_transform = True
    offset_x = border-min_x
    
  if min_y<border:
    do_transform = True
    offset_y = border-min_y
  
  if do_transform:
    hg = numpy.eye(3, dtype=numpy.float32)
    hg[0,2] = offset_x
    hg[1,2] = offset_y
    
    lg.transform(hg)
    
    max_x += offset_x
    max_y += offset_y
  
  comp.set_size(int(max_x+border), int(max_y+border))


  # Break the lg into segments, as each can have its own image - draw & paint each in turn...
  lg.segment()
  duplicate_sets = dict()

  for s in xrange(lg.segments):

    slg = LineGraph()
    slg.from_segment(lg, s)
    part = comp.draw_line_graph(slg, radius_growth, stretch_weight)
    
    done = False
    fn = filter(lambda t: t[0].startswith('texture:'), slg.get_tags())
    if len(fn)!=0: fn = fn[0][0][len('texture:'):]
    else: fn = None
    
    for pair in filter(lambda t: t[0].startswith('duplicate:'), slg.get_tags()):
      key = pair[0][len('duplicate:'):]
      if key in duplicate_sets: duplicate_sets[key].append(part)
      else: duplicate_sets[key] = [part]
    
    tex = textures[fn]
    
    if tex!=None:
      if use_linear:
        comp.paint_texture_linear(tex, part)
      else:
        comp.paint_texture_nearest(tex, part)
      done = True
    
    if not done:
      comp.paint_test_pattern(part)

  
  # Bias towards pixels that are opaque...
  comp.inc_weight_alpha(alpha_weight)
  
  # Arrange for duplicate pairs to have complete overlap, by adding transparent pixels, so graph cuts doesn't create a feather effect...
  if overlap_weight>1e-6:
    for values in duplicate_sets.itervalues():
      for i, part1 in enumerate(values):
        for part2 in values[i:]:
          comp.draw_pair(part1, part2, overlap_weight)
  
  # If requested use maxflow to find optimal cuts, to avoid any real blending...
  count = 0
  if cleverness==2:
    count = comp.maxflow_select(edge_weight, smooth_weight, maxflow)
  elif cleverness==3:
    count = comp.graphcut_select(edge_weight, smooth_weight, unary_mult, maxflow)
  
  if cleverness==0:
    render = comp.render_last()
  else:
    render = comp.render_average()

  # Return the rendered image (If cleverness==0 this will actually do some averaging, otherwise it will just create an image)...
  return render, count
Exemple #15
0
    def add(self, fn):
        """Given the filename for a LineGraph file this loads it in and splits it into Glyphs, which it dumps into the db. Does nothing if the file is already loaded. returns the number of glyphs added."""

        if fn in self.fnl: return 0
        self.fnl.append(fn)

        # Load the LineGraph from the given filename, and get the homography...
        f = open(fn, 'r')
        data = ply2.read(f)
        f.close()

        lg = LineGraph()
        lg.from_dict(data)
        lg.segment()

        hg = data['element']['homography']['v'].reshape((3, 3))

        texture = os.path.normpath(
            os.path.join(os.path.dirname(fn), data['meta']['image']))

        # Create a line bias object to be used in the next step...
        bias = gen_bias(lg, hg)

        # First pass - create each glyph object...
        glyphs = []
        for s in xrange(lg.segments):
            g = Glyph(lg, s, hg, bias=bias)
            glyphs.append(g if '_' not in
                          map(lambda t: t[0], g.lg.get_tags()) else None)

        # Second pass - fill in the connectivity information supported by adjacency...
        link_glyphs = []
        for seg, glyph in enumerate(glyphs):
            if glyph == None: continue
            if glyph.key == None: continue

            # Brute force search to find a left partner...
            if glyph.left == None:
                best = None
                for g in glyphs:
                    # Check it satisfies the conditions...
                    if g == None: continue
                    if g.key == None: continue
                    if id(g) == id(glyph): continue
                    if g.offset_y != glyph.offset_y: continue
                    if (g.right_x - g.offset_x) > (glyph.right_x -
                                                   glyph.offset_x):
                        continue

                    # Check its better than the current best...
                    if best == None or (best.right_x - best.offset_x) < (
                            g.right_x - g.offset_x):
                        best = g

                if best != None:
                    glyph.left = (best, [])

            # Brute force search to find a right partner...
            if glyph.right == None:
                best = None
                for g in glyphs:
                    # Check it satisfies the conditions...
                    if g == None: continue
                    if g.key == None: continue
                    if id(g) == id(glyph): continue
                    if g.offset_y != glyph.offset_y: continue
                    if (g.left_x - g.offset_x) < (glyph.left_x -
                                                  glyph.offset_x):
                        continue

                    # Check its better than the current best...
                    if best == None or (best.left_x - best.offset_x) > (
                            g.left_x - g.offset_x):
                        best = g

                if best != None:
                    glyph.right = (best, [])

            # Now we have the best matches find common glyphs to link them, and record them...
            for other, out in [
                    g for g in [glyph.left, glyph.right] if g != None
            ]:
                shared_seg = set([a[1] for a in glyph.adjacent]) & set(
                    [a[1] for a in other.adjacent])

                for seg in shared_seg:
                    g = glyphs[seg]
                    if g == None: continue

                    # We have a linking glyph - extract the information...
                    glyph_vert = [a[0] for a in glyph.adjacent if a[1] == seg]
                    other_vert = [a[0] for a in other.adjacent if a[1] == seg]

                    link_glyph = [
                        a[0] for a in g.adjacent if a[1] == glyph.seg
                    ]
                    link_other = [
                        a[0] for a in g.adjacent if a[1] == other.seg
                    ]

                    # Check if we have a multi-link scenario - if so choose links...
                    if len(glyph_vert) > 1 or len(other_vert) > 1:
                        gv_y = map(lambda v: glyph.lg.get_vertex(v)[1],
                                   glyph_vert)
                        ov_y = map(lambda v: other.lg.get_vertex(v)[1],
                                   other_vert)

                        if (max(gv_y) - min(ov_y)) > (max(ov_y) - min(gv_y)):
                            glyph_vert = glyph_vert[numpy.argmax(gv_y)]
                            other_vert = other_vert[numpy.argmin(ov_y)]
                        else:
                            glyph_vert = glyph_vert[numpy.argmin(gv_y)]
                            other_vert = other_vert[numpy.argmax(ov_y)]

                        lg_y = map(lambda v: g.lg.get_vertex(v)[1], link_glyph)
                        lo_y = map(lambda v: g.lg.get_vertex(v)[1], link_other)

                        if (max(lg_y) - min(lo_y)) > (max(lo_y) - min(lg_y)):
                            link_glyph = link_glyph[numpy.argmax(lg_y)]
                            link_other = link_other[numpy.argmin(lo_y)]
                        else:
                            link_glyph = link_glyph[numpy.argmin(lg_y)]
                            link_other = link_other[numpy.argmax(lo_y)]

                    else:
                        # Simple scenario...
                        glyph_vert = glyph_vert[0]
                        other_vert = other_vert[0]

                        link_glyph = link_glyph[0]
                        link_other = link_other[0]

                    # Recreate the link as a simple path - its the only way to be safe!..
                    try:
                        g = g.clone()
                        nlg = LineGraph()
                        link_glyph, link_other = nlg.from_path(
                            g.lg, link_glyph, link_other)
                        g.lg = nlg
                        link_glyphs.append(g)
                    except:
                        continue  # Line is broken in the centre - don't use it.

                    # Add the tuple to the storage...
                    out.append(
                        (g, glyph_vert, other_vert, link_glyph, link_other))

        # Third pass - enforce consistancy...
        for glyph in glyphs:
            if glyph == None: continue

            if glyph.left != None:
                if glyph.left[0].right == None or id(
                        glyph.left[0].right[0]) != id(glyph):
                    # Inconsistancy - break it...
                    glyph.left[0].right = None
                    glyph.left = None

            if glyph.right != None:
                if glyph.right[0].left == None or id(
                        glyph.right[0].left[0]) != id(glyph):
                    # Inconsistancy - break it...
                    glyph.right[0].left = None
                    glyph.right = None

        # Forth pass - add filename tags and add to db (Count and return the glyph count)...
        count = 0

        for glyph in (glyphs + link_glyphs):
            if glyph == None: continue

            glyph.lg.add_tag(0, 0.1, 'file:%s' % fn)
            glyph.lg.add_tag(0, 0.2, 'texture:%s' % texture)

            glyph.lg.add_tag(
                0, 0.3,
                'link:left:%s' % ('n' if glyph.left == None else
                                  ('g' if len(glyph.left[1]) == 0 else 'l')))
            glyph.lg.add_tag(
                0, 0.4,
                'link:right:%s' % ('n' if glyph.right == None else
                                   ('g' if len(glyph.right[1]) == 0 else 'l')))

            glyph.lg.add_tag(0, 0.5, 'source:x:%f' % glyph.source[0])
            glyph.lg.add_tag(0, 0.6, 'source:y:%f' % glyph.source[1])

            if glyph.key != None:
                count += 1
                if glyph.key in self.db: self.db[glyph.key].append(glyph)
                else: self.db[glyph.key] = [glyph]

            glyph.code = len(self.by_code)
            glyph.lg.add_tag(0, 0.5, 'code:%i' % glyph.code)
            self.by_code.append(glyph)

        return count
Exemple #16
0
  def __init__(self, lg, seg, hg, extra = 0.4, bias = None):
    """Given a segmented LineGraph and segment number this extracts it, transforms it into the standard coordinate system and stores the homography used to get there. (hg transforms from line space, where there is a line for each y=integer, to the space of the original pixels.) Also records its position on its assigned line and line number so it can be ordered suitably. Does not store connectivity information - that is done later. extra is used for infering the line position, and is extra falloff to have either side of a line voting for it - a smoothing term basically. bias is an optional dictionary indexed by line number that gives a weight to assign to being assigned to that line - used to utilise the fact that data collection asks the writter to use every-other line, which helps avoid misassigned dropped j's for instance."""
    if lg==None: return

    # Extract the line graph...
    self.lg = LineGraph()
    self.adjacent = self.lg.from_segment(lg, seg)
    self.seg = seg
    
    # Tranform it to line space...
    ihg = la.inv(hg)
    self.lg.transform(ihg, True)
    
    # Check if which line its on is tagged - exists as an override for annoying glyphs...
    line = None
    for tag in self.lg.get_tags():
      if tag[0]=='line':
        # We have a tag of line - its position specifies the line the glyph is on...
        point = self.lg.get_point(tag[1], tag[2])
        line = int(numpy.floor(point[1]))
        break
    
    # Record which line it is on and its position along the line...
    # (Works by assuming that the line is the one below the space where most of the mass of the glyph is. Takes it range to be that within the space, so crazy tails are cut off.)
    min_x, max_x, min_y, max_y = self.lg.get_bounds()
    self.source = (0.5 * (min_x + max_x), 0.5 * (min_y + max_y))
    
    if line==None:   
      best_mass = 0.0
      self.left_x = min_x
      self.right_x = max_x
      line = 0
    
      start = int(numpy.trunc(min_y))
      for pl in xrange(start, int(numpy.ceil(max_y))):
        mass = 0.0
        low_y = float(pl) - extra
        high_y = float(pl+1) + extra
      
        left_x = None
        right_x = None
      
        for es in self.lg.within(min_x, max_x, low_y, high_y):
          for ei in xrange(*es.indices(self.lg.edge_count)):
            edge = self.lg.get_edge(ei)
            vf = self.lg.get_vertex(edge[0])
            vt = self.lg.get_vertex(edge[1])
          
            if vf[1]>low_y and vf[1]<high_y and vt[1]>low_y and vt[1]<high_y:
              dx = vt[0] - vf[0]
              dy = vt[1] - vf[1]
              mass += (vf[5] + vt[5]) * numpy.sqrt(dx*dx + dy*dy)
            
              if left_x==None: left_x = min(vf[0], vt[0])
              else: left_x = min(vf[0], vt[0], left_x)
            
              if right_x==None: right_x = max(vf[0], vt[0])
              else: right_x = max(vf[0], vt[0], right_x)
      
        mass *= 1.0/(1.0+pl - start) # Bias to choosing higher, for tails.
        
        if bias!=None:
          mass *= bias[pl]
      
        if mass>best_mass:
          best_mass = mass
          self.left_x = left_x
          self.right_x = right_x
          line = pl
    
    # Transform it so it is positioned to be sitting on line 1 of y, store the total homography that we have applied...
    self.offset_x = -min_x
    self.offset_y = -line
    
    hg = numpy.eye(3, dtype=numpy.float32)
    hg[0,2] = self.offset_x
    hg[1,2] = self.offset_y
    
    self.left_x += self.offset_x
    self.right_x += self.offset_x
    
    self.lg.transform(hg)
    
    self.transform = numpy.dot(hg, ihg)
    
    # Set as empty its before and after glyphs - None if there is no adjacency, or a tuple if there is: (glyph, list of connecting (link glyph, shared vertex in this, shared vertex in glyph, vertex in link glyph on this side, vertex in link glyph on glyph side), empty if none.)...
    self.left = None
    self.right = None
    
    # Extract the character this glyph represents...
    tags = self.lg.get_tags()
    codes = [t[0] for t in tags if len(filter(lambda c: c!='_', t[0]))==1]
    self.key = codes[0] if len(codes)!=0 else None
    
    self.code = -id(self)
    
    # Cache stuff...
    self.mass = None
    self.center = None
    self.feat = None
    self.v_offset = None
Exemple #17
0
class Glyph:
    """Represents a glyph, that has been transformed into a suitable coordinate system; includes connectivity information."""
    def __init__(self, lg, seg, hg, extra=0.4, bias=None):
        """Given a segmented LineGraph and segment number this extracts it, transforms it into the standard coordinate system and stores the homography used to get there. (hg transforms from line space, where there is a line for each y=integer, to the space of the original pixels.) Also records its position on its assigned line and line number so it can be ordered suitably. Does not store connectivity information - that is done later. extra is used for infering the line position, and is extra falloff to have either side of a line voting for it - a smoothing term basically. bias is an optional dictionary indexed by line number that gives a weight to assign to being assigned to that line - used to utilise the fact that data collection asks the writter to use every-other line, which helps avoid misassigned dropped j's for instance."""
        if lg == None: return

        # Extract the line graph...
        self.lg = LineGraph()
        self.adjacent = self.lg.from_segment(lg, seg)
        self.seg = seg

        # Tranform it to line space...
        ihg = la.inv(hg)
        self.lg.transform(ihg, True)

        # Check if which line its on is tagged - exists as an override for annoying glyphs...
        line = None
        for tag in self.lg.get_tags():
            if tag[0] == 'line':
                # We have a tag of line - its position specifies the line the glyph is on...
                point = self.lg.get_point(tag[1], tag[2])
                line = int(numpy.floor(point[1]))
                break

        # Record which line it is on and its position along the line...
        # (Works by assuming that the line is the one below the space where most of the mass of the glyph is. Takes it range to be that within the space, so crazy tails are cut off.)
        min_x, max_x, min_y, max_y = self.lg.get_bounds()
        self.source = (0.5 * (min_x + max_x), 0.5 * (min_y + max_y))

        if line == None:
            best_mass = 0.0
            self.left_x = min_x
            self.right_x = max_x
            line = 0

            start = int(numpy.trunc(min_y))
            for pl in xrange(start, int(numpy.ceil(max_y))):
                mass = 0.0
                low_y = float(pl) - extra
                high_y = float(pl + 1) + extra

                left_x = None
                right_x = None

                for es in self.lg.within(min_x, max_x, low_y, high_y):
                    for ei in xrange(*es.indices(self.lg.edge_count)):
                        edge = self.lg.get_edge(ei)
                        vf = self.lg.get_vertex(edge[0])
                        vt = self.lg.get_vertex(edge[1])

                        if vf[1] > low_y and vf[1] < high_y and vt[
                                1] > low_y and vt[1] < high_y:
                            dx = vt[0] - vf[0]
                            dy = vt[1] - vf[1]
                            mass += (vf[5] + vt[5]) * numpy.sqrt(dx * dx +
                                                                 dy * dy)

                            if left_x == None: left_x = min(vf[0], vt[0])
                            else: left_x = min(vf[0], vt[0], left_x)

                            if right_x == None: right_x = max(vf[0], vt[0])
                            else: right_x = max(vf[0], vt[0], right_x)

                mass *= 1.0 / (1.0 + pl - start
                               )  # Bias to choosing higher, for tails.

                if bias != None:
                    mass *= bias[pl]

                if mass > best_mass:
                    best_mass = mass
                    self.left_x = left_x
                    self.right_x = right_x
                    line = pl

        # Transform it so it is positioned to be sitting on line 1 of y, store the total homography that we have applied...
        self.offset_x = -min_x
        self.offset_y = -line

        hg = numpy.eye(3, dtype=numpy.float32)
        hg[0, 2] = self.offset_x
        hg[1, 2] = self.offset_y

        self.left_x += self.offset_x
        self.right_x += self.offset_x

        self.lg.transform(hg)

        self.transform = numpy.dot(hg, ihg)

        # Set as empty its before and after glyphs - None if there is no adjacency, or a tuple if there is: (glyph, list of connecting (link glyph, shared vertex in this, shared vertex in glyph, vertex in link glyph on this side, vertex in link glyph on glyph side), empty if none.)...
        self.left = None
        self.right = None

        # Extract the character this glyph represents...
        tags = self.lg.get_tags()
        codes = [
            t[0] for t in tags if len(filter(lambda c: c != '_', t[0])) == 1
        ]
        self.key = codes[0] if len(codes) != 0 else None

        self.code = -id(self)

        # Cache stuff...
        self.mass = None
        self.center = None
        self.feat = None
        self.v_offset = None

    def clone(self):
        """Returns a clone of this Glyph."""
        ret = Glyph(None, None, None)

        ret.lg = self.lg
        ret.adjacent = self.adjacent
        ret.seg = self.seg

        ret.source = self.source

        ret.left_x = self.left_x
        ret.right_x = self.right_x

        ret.offset_x = self.offset_x
        ret.offset_y = self.offset_y
        ret.transform = self.transform

        ret.left = self.left
        ret.right = self.right
        ret.key = self.key

        ret.code = self.code

        ret.mass = None if self.mass == None else self.mass.copy()
        ret.center = None if self.center == None else self.center.copy()
        ret.feat = None if self.feat == None else map(lambda a: a.copy(),
                                                      self.feat)
        ret.v_offset = self.v_offset

        return ret

    def get_linegraph(self):
        return self.lg

    def orig_left_x(self):
        return self.left_x - self.offset_x

    def orig_right_x(self):
        return self.right_x - self.offset_x

    def get_mass(self):
        """Returns a vector of [average density, average radius] - used for matching adjacent glyphs."""
        if self.mass == None:
            self.mass = numpy.zeros(2, dtype=numpy.float32)
            weight = 0.0
            for i in xrange(self.lg.vertex_count):
                info = self.lg.get_vertex(i)

                weight += 1.0
                self.mass += (numpy.array([info[6], info[5]]) -
                              self.mass) / weight

        return self.mass

    def get_center(self):
        """Returns the 'center' of the glyph - its density weighted in an attempt to make it robust to crazy tails."""
        if self.center == None:
            self.center = numpy.zeros(2, dtype=numpy.float32)
            weight = 0.0

            for i in xrange(self.lg.vertex_count):
                info = self.lg.get_vertex(i)
                w = info[5] * info[5] * info[
                    6]  # Radius squared * density - proportional to quantity of ink, assuming (correctly as rest of system currently works) even sampling.
                if w > 1e-6:
                    weight += w
                    mult = w / weight
                    self.center[0] += (info[0] - self.center[0]) * mult
                    self.center[1] += (info[1] - self.center[1]) * mult

        return self.center

    def get_voffset(self):
        """Calculates and returns the vertical offset to apply to the glyph that corrects for any systematic bias in its flow calculation."""

        if self.v_offset == None:
            self.v_offset = 0.0
            weight = 0.0

            truth = self.get_center()[1]

            # Calculate the estimated offsets from the left side and update the estimate, correctly factoring in the variance of the offset...
            if self.left != None:
                diff, sd = costs.glyph_pair_offset(self.left[0], self, 0.2,
                                                   True)
                estimate = self.left[0].get_center()[1] + diff
                offset = truth - estimate

                est_weight = 1.0 / (sd**2.0)
                weight += est_weight
                self.v_offset += (offset - self.v_offset) * est_weight / weight

            # Again from the right side...
            if self.right != None:
                diff, sd = costs.glyph_pair_offset(self, self.right[0], 0.2,
                                                   True)
                estimate = self.right[0].get_center()[1] - diff
                offset = truth - estimate

                est_weight = 1.0 / (sd**2.0)
                weight += est_weight
                self.v_offset += (offset - self.v_offset) * est_weight / weight

        return self.v_offset

    def most_left(self):
        """Returns the coordinate of the furthest left vertex in the glyph."""

        info = self.lg.get_vertex(0)
        best_x = info[0]
        best_y = info[1]

        for i in xrange(1, self.lg.vertex_count):
            info = self.lg.get_vertex(0)
            if info[0] < best_x:
                best_x = info[0]
                best_y = info[1]

        return (best_x, best_y)

    def most_right(self):
        """Returns the coordinate of the furthest right vertex in the glyph."""

        info = self.lg.get_vertex(0)
        best_x = info[0]
        best_y = info[1]

        for i in xrange(1, self.lg.vertex_count):
            info = self.lg.get_vertex(0)
            if info[0] > best_x:
                best_x = info[0]
                best_y = info[1]

        return (best_x, best_y)

    def get_feat(self):
        """Calculates and returns a feature for the glyph, or, more accuratly two features, representing (left, right), so some tricks can be done to make their use side dependent (For learning a function for matching to adjacent glyphs.)."""
        if self.feat == None:
            # First build a culumative distribution over the x axis range of the glyph...
            min_x, max_x, min_y, max_y = self.lg.get_bounds()
            culm = numpy.ones(32, dtype=numpy.float32)
            culm *= 1e-2

            min_x -= 1e-3
            max_x += 1e-3

            for i in xrange(self.lg.vertex_count):
                info = self.lg.get_vertex(i)
                w = info[5] * info[5] * info[6]
                t = (info[0] - min_x) / (max_x - min_x)

                t *= (culm.shape[0] - 1)
                low = int(t)
                high = low + 1
                t -= low
                culm[low] += (1.0 - t) * w
                culm[high] += t * w

            culm /= culm.sum()
            culm = numpy.cumsum(culm)

            # Now extract all the per sample features...
            feat_param = {
                'dir_travel': 0.1,
                'travel_max': 1.0,
                'travel_bins': 6,
                'travel_ratio': 0.8,
                'pos_bins': 3,
                'pos_ratio': 0.9,
                'radius_bins': 1,
                'density_bins': 3
            }
            fv = self.lg.features(**feat_param)

            # Combine them into the two halves, by weighting by the culumative; include density and radius as well...
            left = numpy.zeros(fv.shape[1] + 2, dtype=numpy.float32)
            right = numpy.zeros(fv.shape[1] + 2, dtype=numpy.float32)

            left_total = 0.0
            right_total = 0.0

            for i in xrange(self.lg.vertex_count):
                info = self.lg.get_vertex(i)
                w = info[5] * info[5] * info[6]
                t = (info[0] - min_x) / (max_x - min_x)

                t *= (culm.shape[0] - 1)
                low = int(t)
                high = low + 1
                t -= low
                right_w = (1.0 - t) * culm[low] + t * culm[high]
                left_w = 1.0 - right_w

                left[0] += left_w * info[5]
                right[0] += right_w * info[5]

                left[1] += left_w * info[6]
                right[1] += right_w * info[6]

                left[2:] += w * left_w * fv[i, :]
                right[2:] += w * right_w * fv[i, :]

                left_total += left_w
                right_total += right_w

            left[:2] /= left_total
            right[:2] /= right_total
            left[2:] /= max(left[2:].sum(), 1e-6)
            right[2:] /= max(right[2:].sum(), 1e-6)

            self.feat = (left, right)

        return self.feat

    def __str__(self):
        l = self.left[0].key if self.left != None else 'None'
        r = self.right[0].key if self.right != None else 'None'
        return 'Glyph %i: key = %s (%s|%s)' % (self.code, self.key, l, r)
Exemple #18
0


# Load and process each in turn, to create a big database of feature/class...
train = []

for fn_num, fn in enumerate(lg_fn):
  print 'Processing %s: (%i of %i)' % (fn, fn_num+1, len(lg_fn))
  
  # Load line graph...
  print '    Loading...'
  f = open(fn, 'r')
  data = ply2.read(f)
  f.close()
    
  lg = LineGraph()
  lg.from_dict(data)
  lg.segment()
  
  # Extract features...
  print '    Extracting features...'
  fv = lg.features(**feat_param)
  
  # Extract labels, to match the features...
  segs = lg.get_segs()

  def seg_to_label(seg):
    tags = lg.get_tags(seg)
    if len(tags)==0: return 0 # Its a ligament
  
    for tag in tags:
Exemple #19
0
print 'Found %i line graphs' % len(lg_fn)

# Load and process each in turn, to create a big database of feature/class...
train = []

for fn_num, fn in enumerate(lg_fn):
    print 'Processing %s: (%i of %i)' % (fn, fn_num + 1, len(lg_fn))

    # Load line graph...
    print '    Loading...'
    f = open(fn, 'r')
    data = ply2.read(f)
    f.close()

    lg = LineGraph()
    lg.from_dict(data)
    lg.segment()

    # Extract features...
    print '    Extracting features...'
    fv = lg.features(**feat_param)

    # Extract labels, to match the features...
    segs = lg.get_segs()

    def seg_to_label(seg):
        tags = lg.get_tags(seg)
        if len(tags) == 0: return 0  # Its a ligament

        for tag in tags:
Exemple #20
0
def stitch_connect(glyph_layout, soft = True, half = False, pair_base = 0):
  """Converts a glyph layout to a linegraph layout. This stitches together the glyphs when it has sufficient information to do so."""
  ret = []
  
  # First copy over the actual glyphs...
  for pair in glyph_layout:
    if pair!=None:
      hg, glyph = pair
      ret.append((hg, glyph.lg))
      
  # Now loop through and identify all pairs that can be stitched together, and stitch them...
  pair_code = 0
  for i in xrange(len(glyph_layout)-1):
    # Can't stitch spaces...
    if glyph_layout[i]!=None and glyph_layout[i+1]!=None:
      l_hg, l_glyph = glyph_layout[i]
      r_hg, r_glyph = glyph_layout[i+1]
      
      matches = costs.match_links(l_glyph, r_glyph)
        
      # Iterate and do each pairing in turn...
      for ml, mr in matches:
        # Calculate the homographies to put the two line graphs into position...
        lc_hg = numpy.dot(l_hg, numpy.dot(l_glyph.transform, la.inv(ml[0].transform)))
        rc_hg = numpy.dot(r_hg, numpy.dot(r_glyph.transform, la.inv(mr[0].transform)))

        # Copy the links, applying the homographies...
        lc = LineGraph()
        lc.from_many(lc_hg, ml[0].lg)
        
        rc = LineGraph()
        rc.from_many(rc_hg, mr[0].lg)
          
        # Extract the merge points...
        blend = [(ml[3], 0.0, mr[4]), (ml[4], 1.0, mr[3])]
          
        # Do the blending...
        lc.blend(rc, blend, soft)
          
        # Record via tagging that the two parts are the same entity...
        pair = 'duplicate:%i,%i' % (pair_base, pair_code)
        lc.add_tag(0, 0.5, pair)
        rc.add_tag(0, 0.5, pair)
        pair_code += 1
        
        # Store the pair of line graphs in the return, with identity homographies...
        ret.append((numpy.eye(3), lc))
        if not half: ret.append((numpy.eye(3), rc))
  
  return ret