(self, direction, method, **kwargs)
return clusters_w_vals
Find line clusters for detected lines in direction <direction> using clustering method <method> (from clustering
        module). <kwargs> will be passed to <method>.
        Optionally remove clusters with high standard deviation values (<remove_cluster_sections_stddev_thresh>).
        Optionally remove clusters with empty sections, meaning that there are only a few text boxes inside these
        sections.
        <remove_empty_cluster_sections_use_texts>: list of text boxes for empty cluster section removal
        <remove_empty_cluster_sections_n_texts_ratio>: ratio for min. texts threshold
        <remove_empty_cluster_sections_scaling>: text box coord. system scaling in relation to image coord. system
Find line clusters for detected lines in direction <direction> using clustering method <method> (from clustering
        module). <kwargs> will be passed to <method>.
        Optionally remove clusters with high standard deviation values (<remove_cluster_sections_stddev_thresh>).
        Optionally remove clusters with empty sections, meaning that there are only a few text boxes inside these
        sections.
        <remove_empty_cluster_sections_use_texts>: list of text boxes for empty cluster section removal
        <remove_empty_cluster_sections_n_texts_ratio>: ratio for min. texts threshold
        <remove_empty_cluster_sections_scaling>: text box coord. system scaling in relation to image coord. system
Find line clusters for detected lines in direction <direction > using clustering method <method > ( from clustering module ) . <kwargs > will be passed to <method > . Optionally remove clusters with high standard deviation values ( <remove_cluster_sections_stddev_thresh > ) . Optionally remove clusters with empty sections meaning that there are only a few text boxes inside these sections . <remove_empty_cluster_sections_use_texts > : list of text boxes for empty cluster section removal <remove_empty_cluster_sections_n_texts_ratio > : ratio for min . texts threshold <remove_empty_cluster_sections_scaling > : text box coord . system scaling in relation to image coord . system
def find_clusters(self, direction, method, **kwargs):
        """
        Find line clusters for detected lines in direction <direction> using clustering method <method> (from clustering
        module). <kwargs> will be passed to <method>.
        Optionally remove clusters with high standard deviation values (<remove_cluster_sections_stddev_thresh>).
        Optionally remove clusters with empty sections, meaning that there are only a few text boxes inside these
        sections.
        <remove_empty_cluster_sections_use_texts>: list of text boxes for empty cluster section removal
        <remove_empty_cluster_sections_n_texts_ratio>: ratio for min. texts threshold
        <remove_empty_cluster_sections_scaling>: text box coord. system scaling in relation to image coord. system
        """
        remove_empty_cluster_sections_use_texts = kwargs.pop('remove_empty_cluster_sections_use_texts', None)
        if remove_empty_cluster_sections_use_texts is not None:
            remove_empty_cluster_sections_n_texts_ratio = kwargs.pop('remove_empty_cluster_sections_n_texts_ratio')
            remove_empty_cluster_sections_scaling = kwargs.pop('remove_empty_cluster_sections_scaling')
            remove_empty_cluster_sections_clust_center_fn = kwargs.pop('remove_empty_cluster_sections_clust_center_fn',
                                                                       np.median)
        
        remove_cluster_sections_stddev_thresh = kwargs.pop('remove_cluster_sections_stddev_thresh', None)
        
        if not self.lines_hough:
            raise ValueError("no lines in lines_hough. did you call detect_lines before?")
        
        if direction not in (DIRECTION_HORIZONTAL, DIRECTION_VERTICAL):
            raise ValueError("invalid value for 'direction': '%s'" % direction)
        
        if not callable(method):
            raise ValueError("'method' must be callable")
        
        lines_in_dir = [l for l in self.lines_hough if l[3] == direction]
        
        if len(lines_in_dir) == 0:  # no lines in that direction
            return []
        
        lines_ab = self.ab_lines_from_hough_lines(lines_in_dir)
                
        coord_idx = 0 if direction == DIRECTION_VERTICAL else 1
        positions = np.array([(l[0][coord_idx] + l[1][coord_idx]) / 2 for l in lines_ab])
        
        clusters = method(positions, **kwargs)
        
        if type(clusters) != list:
            raise ValueError("'method' returned invalid clusters (must be list)")
        
        if len(clusters) > 0 and type(clusters[0]) != np.ndarray:
            raise ValueError("'method' returned invalid cluster elements (must be list of numpy.ndarray objects)")
        
        clusters_w_vals = zip_clusters_and_values(clusters, positions)
        
        if remove_cluster_sections_stddev_thresh is not None:
            clusters_w_vals = [(ind, vals) for ind, vals in clusters_w_vals
                               if np.std(vals) < remove_cluster_sections_stddev_thresh]
        
        if remove_empty_cluster_sections_use_texts is not None:
            if direction == DIRECTION_HORIZONTAL:
                t_attr = 'top', 'bottom'
            else:
                t_attr = 'left', 'right'
                
            clusters_w_vals_and_centers = [(ind, vals, remove_empty_cluster_sections_clust_center_fn(vals) / remove_empty_cluster_sections_scaling)
                                           for ind, vals in clusters_w_vals]
            clusters_w_vals_and_centers = sorted(clusters_w_vals_and_centers, key=lambda x: x[2])
            clusters_w_vals_and_n_texts = []
            prev_center = -1
            for ind, vals, center in clusters_w_vals_and_centers:
                texts = [t for t in remove_empty_cluster_sections_use_texts
                         if prev_center < t[t_attr[0]] <= center or prev_center < t[t_attr[1]] <= center]
                clusters_w_vals_and_n_texts.append((ind, vals, len(texts)))
                prev_center = center
            assert len(clusters_w_vals_and_n_texts) == len(clusters_w_vals)
                
            max_n_texts = np.median([tup[2] for tup in clusters_w_vals_and_n_texts])
            n_texts_thresh = round(max_n_texts * remove_empty_cluster_sections_n_texts_ratio)
            clusters_w_vals_filtered = []
            prev_clust = None
            for ind, vals, n_texts in clusters_w_vals_and_n_texts:
                if n_texts >= n_texts_thresh:
                    if not clusters_w_vals_filtered and prev_clust is not None:
                        clusters_w_vals_filtered.append(prev_clust)
                    
                    clusters_w_vals_filtered.append((ind, vals))
                prev_clust = (ind, vals)
            assert(len(clusters_w_vals_filtered) <= len(clusters_w_vals))
            clusters_w_vals = clusters_w_vals_filtered
        
        return clusters_w_vals
def find_clusters ( self , direction , method , * * kwargs ) : remove_empty_cluster_sections_use_texts = kwargs . pop ( 'remove_empty_cluster_sections_use_texts' , None ) if remove_empty_cluster_sections_use_texts is not None : remove_empty_cluster_sections_n_texts_ratio = kwargs . pop ( 'remove_empty_cluster_sections_n_texts_ratio' ) remove_empty_cluster_sections_scaling = kwargs . pop ( 'remove_empty_cluster_sections_scaling' ) remove_empty_cluster_sections_clust_center_fn = kwargs . pop ( 'remove_empty_cluster_sections_clust_center_fn' , np . median ) remove_cluster_sections_stddev_thresh = kwargs . pop ( 'remove_cluster_sections_stddev_thresh' , None ) if not self . lines_hough : raise ValueError ( "no lines in lines_hough. did you call detect_lines before?" ) if direction not in ( DIRECTION_HORIZONTAL , DIRECTION_VERTICAL ) : raise ValueError ( "invalid value for 'direction': '%s'" % direction ) if not callable ( method ) : raise ValueError ( "'method' must be callable" ) lines_in_dir = [ l for l in self . lines_hough if l [ 3 ] == direction ] if len ( lines_in_dir ) == 0 : # no lines in that direction return [ ] lines_ab = self . ab_lines_from_hough_lines ( lines_in_dir ) coord_idx = 0 if direction == DIRECTION_VERTICAL else 1 positions = np . array ( [ ( l [ 0 ] [ coord_idx ] + l [ 1 ] [ coord_idx ] ) / 2 for l in lines_ab ] ) clusters = method ( positions , * * kwargs ) if type ( clusters ) != list : raise ValueError ( "'method' returned invalid clusters (must be list)" ) if len ( clusters ) > 0 and type ( clusters [ 0 ] ) != np . ndarray : raise ValueError ( "'method' returned invalid cluster elements (must be list of numpy.ndarray objects)" ) clusters_w_vals = zip_clusters_and_values ( clusters , positions ) if remove_cluster_sections_stddev_thresh is not None : clusters_w_vals = [ ( ind , vals ) for ind , vals in clusters_w_vals if np . std ( vals ) < remove_cluster_sections_stddev_thresh ] if remove_empty_cluster_sections_use_texts is not None : if direction == DIRECTION_HORIZONTAL : t_attr = 'top' , 'bottom' else : t_attr = 'left' , 'right' clusters_w_vals_and_centers = [ ( ind , vals , remove_empty_cluster_sections_clust_center_fn ( vals ) / remove_empty_cluster_sections_scaling ) for ind , vals in clusters_w_vals ] clusters_w_vals_and_centers = sorted ( clusters_w_vals_and_centers , key = lambda x : x [ 2 ] ) clusters_w_vals_and_n_texts = [ ] prev_center = - 1 for ind , vals , center in clusters_w_vals_and_centers : texts = [ t for t in remove_empty_cluster_sections_use_texts if prev_center < t [ t_attr [ 0 ] ] <= center or prev_center < t [ t_attr [ 1 ] ] <= center ] clusters_w_vals_and_n_texts . append ( ( ind , vals , len ( texts ) ) ) prev_center = center assert len ( clusters_w_vals_and_n_texts ) == len ( clusters_w_vals ) max_n_texts = np . median ( [ tup [ 2 ] for tup in clusters_w_vals_and_n_texts ] ) n_texts_thresh = round ( max_n_texts * remove_empty_cluster_sections_n_texts_ratio ) clusters_w_vals_filtered = [ ] prev_clust = None for ind , vals , n_texts in clusters_w_vals_and_n_texts : if n_texts >= n_texts_thresh : if not clusters_w_vals_filtered and prev_clust is not None : clusters_w_vals_filtered . append ( prev_clust ) clusters_w_vals_filtered . append ( ( ind , vals ) ) prev_clust = ( ind , vals ) assert ( len ( clusters_w_vals_filtered ) <= len ( clusters_w_vals ) ) clusters_w_vals = clusters_w_vals_filtered return clusters_w_vals