(clusters, n_required_clusters, find_center_clusters_method, **kwargs)
From a dict containing clusters per page, find the cluster centers and apply some adjustments to them
    (filter bad values, interpolate missing values).
    
    Return the adjusted cluster centers in a dict with page number -> cluster center mapping.
    
    If parameter <return_center_clusters_diffsums> is True, additionally return a dict with summed differences between
    found centers and "model" centers as quality measure.
    <n_required_clusters> is the number of cluster centers (i.e. number of columns or lines) to be found.
    <find_center_clusters_method> is the clustering method to cluster aligned ("normalized") centers (<kwargs> will
    be passed to this function).
    <image_scaling> is an optional parameter: dict with page number -> <scaling> mapping with which the
    final centers for each page are calculated by <center> / <scaling>.
    <arr_matching_same_size_use_model_arr_diff_thresh> is an optional parameter. During cluster array matching,
    this parameter defines the array difference threshold, upon which the averaged "model array" (e.g. model clusters)
    is used instead of the detected cluster array, because the detected clusters do not fit to the model clusters.
From a dict containing clusters per page, find the cluster centers and apply some adjustments to them
    (filter bad values, interpolate missing values).
    
    Return the adjusted cluster centers in a dict with page number -> cluster center mapping.
    
    If parameter <return_center_clusters_diffsums> is True, additionally return a dict with summed differences between
    found centers and "model" centers as quality measure.
    <n_required_clusters> is the number of cluster centers (i.e. number of columns or lines) to be found.
    <find_center_clusters_method> is the clustering method to cluster aligned ("normalized") centers (<kwargs> will
    be passed to this function).
    <image_scaling> is an optional parameter: dict with page number -> <scaling> mapping with which the
    final centers for each page are calculated by <center> / <scaling>.
    <arr_matching_same_size_use_model_arr_diff_thresh> is an optional parameter. During cluster array matching,
    this parameter defines the array difference threshold, upon which the averaged "model array" (e.g. model clusters)
    is used instead of the detected cluster array, because the detected clusters do not fit to the model clusters.
From a dict containing clusters per page find the cluster centers and apply some adjustments to them ( filter bad values interpolate missing values ) . Return the adjusted cluster centers in a dict with page number - > cluster center mapping . If parameter <return_center_clusters_diffsums > is True additionally return a dict with summed differences between found centers and model centers as quality measure . <n_required_clusters > is the number of cluster centers ( i . e . number of columns or lines ) to be found . <find_center_clusters_method > is the clustering method to cluster aligned ( normalized ) centers ( <kwargs > will be passed to this function ) . <image_scaling > is an optional parameter : dict with page number - > <scaling > mapping with which the final centers for each page are calculated by <center > / <scaling > . <arr_matching_same_size_use_model_arr_diff_thresh > is an optional parameter . During cluster array matching this parameter defines the array difference threshold upon which the averaged model array ( e . g . model clusters ) is used instead of the detected cluster array because the detected clusters do not fit to the model clusters .
def get_adjusted_cluster_centers(clusters, n_required_clusters, find_center_clusters_method, **kwargs):
    """
    From a dict containing clusters per page, find the cluster centers and apply some adjustments to them
    (filter bad values, interpolate missing values).
    
    Return the adjusted cluster centers in a dict with page number -> cluster center mapping.
    
    If parameter <return_center_clusters_diffsums> is True, additionally return a dict with summed differences between
    found centers and "model" centers as quality measure.
    <n_required_clusters> is the number of cluster centers (i.e. number of columns or lines) to be found.
    <find_center_clusters_method> is the clustering method to cluster aligned ("normalized") centers (<kwargs> will
    be passed to this function).
    <image_scaling> is an optional parameter: dict with page number -> <scaling> mapping with which the
    final centers for each page are calculated by <center> / <scaling>.
    <arr_matching_same_size_use_model_arr_diff_thresh> is an optional parameter. During cluster array matching,
    this parameter defines the array difference threshold, upon which the averaged "model array" (e.g. model clusters)
    is used instead of the detected cluster array, because the detected clusters do not fit to the model clusters.
    """
    return_center_clusters_diffsums = kwargs.pop('return_center_clusters_diffsums', False)
    image_scaling = kwargs.pop('image_scaling', None)
    same_size_use_model_arr_diff_thresh = kwargs.pop('arr_matching_same_size_use_model_arr_diff_thresh', None)
    
    # 1. Align the cluster centers so that they all start with 0 and create a flat list that contains all centers
    all_clusters_centers = {}
    for p_num, clusters_w_vals in clusters.items():
        all_clusters_centers[p_num] = calc_cluster_centers_1d(clusters_w_vals)
    
    centers_norm = []
    for p_num, centers in all_clusters_centers.items():
        centers = np.array(centers)
        centers_norm.extend(centers - centers[0])
    
    centers_norm = np.array(centers_norm)

    # 2. Clustering second pass: Cluster aligned ("normalized") centers and filter them
    centers_norm_clusters_ind = find_center_clusters_method(centers_norm, **kwargs)
    centers_norm_clusters = zip_clusters_and_values(centers_norm_clusters_ind, centers_norm)

    if len(centers_norm_clusters) < n_required_clusters:
        logger.warning('Number of found clusters is smaller than number of required clusters `n_required_clusters`. '
                       'Try to decrease break distance or adapt other clustering parameters.')
        center_norm_medians = np.array(sorted([np.median(vals) for _, vals in centers_norm_clusters]))
    else:
        # Filter clusters: take only clusters with at least <min_n_values> inside. Decrease this value on each iteration.
        center_norm_medians = []
        min_n_startval = max(map(len, centers_norm_clusters_ind))
        for min_n_values in range(min_n_startval, 0, -1):
            clust_ids_to_remove = []
            for i, (_, vals) in enumerate(centers_norm_clusters):
                val_median = np.median(vals)
                if len(vals) >= min_n_values and val_median not in center_norm_medians:
                    center_norm_medians.append(val_median)
                    clust_ids_to_remove.append(i)

                if len(center_norm_medians) == n_required_clusters:
                    break
            else:
                centers_norm_clusters = [c for i, c in enumerate(centers_norm_clusters) if i not in clust_ids_to_remove]
                continue
            break

        assert len(center_norm_medians) == n_required_clusters
    
        center_norm_medians = np.array(sorted(center_norm_medians))

    # 3. Adjust the cluster centers by finding the best matching array to <center_norm_medians> if sizes differ
    adjusted_centers = {}
    diffsums = {} if return_center_clusters_diffsums else None
    for p_num, centers in all_clusters_centers.items():
        centers = np.array(centers)
        corrected_centers, diffsum = find_best_matching_array(centers, center_norm_medians,
                                                              same_size_use_model_arr_diff_thresh=same_size_use_model_arr_diff_thresh)
        #print(p_num, diffsum)
        #print(list((center_norm_medians + centers[0]) / image_scaling[p_num]))
        #print(list((centers_norm + centers[0]) / image_scaling[p_num]))
        #print()
        
        if image_scaling is not None:
            scaling_for_page = image_scaling[p_num]
            corrected_centers /= scaling_for_page
        
        adjusted_centers[p_num] = corrected_centers
        if return_center_clusters_diffsums:
            diffsums[p_num] = diffsum
    
    if return_center_clusters_diffsums:
        return adjusted_centers, diffsums
    else:
        return adjusted_centers
def get_adjusted_cluster_centers ( clusters , n_required_clusters , find_center_clusters_method , * * kwargs ) : return_center_clusters_diffsums = kwargs . pop ( 'return_center_clusters_diffsums' , False ) image_scaling = kwargs . pop ( 'image_scaling' , None ) same_size_use_model_arr_diff_thresh = kwargs . pop ( 'arr_matching_same_size_use_model_arr_diff_thresh' , None ) # 1. Align the cluster centers so that they all start with 0 and create a flat list that contains all centers all_clusters_centers = { } for p_num , clusters_w_vals in clusters . items ( ) : all_clusters_centers [ p_num ] = calc_cluster_centers_1d ( clusters_w_vals ) centers_norm = [ ] for p_num , centers in all_clusters_centers . items ( ) : centers = np . array ( centers ) centers_norm . extend ( centers - centers [ 0 ] ) centers_norm = np . array ( centers_norm ) # 2. Clustering second pass: Cluster aligned ("normalized") centers and filter them centers_norm_clusters_ind = find_center_clusters_method ( centers_norm , * * kwargs ) centers_norm_clusters = zip_clusters_and_values ( centers_norm_clusters_ind , centers_norm ) if len ( centers_norm_clusters ) < n_required_clusters : logger . warning ( 'Number of found clusters is smaller than number of required clusters `n_required_clusters`. ' 'Try to decrease break distance or adapt other clustering parameters.' ) center_norm_medians = np . array ( sorted ( [ np . median ( vals ) for _ , vals in centers_norm_clusters ] ) ) else : # Filter clusters: take only clusters with at least <min_n_values> inside. Decrease this value on each iteration. center_norm_medians = [ ] min_n_startval = max ( map ( len , centers_norm_clusters_ind ) ) for min_n_values in range ( min_n_startval , 0 , - 1 ) : clust_ids_to_remove = [ ] for i , ( _ , vals ) in enumerate ( centers_norm_clusters ) : val_median = np . median ( vals ) if len ( vals ) >= min_n_values and val_median not in center_norm_medians : center_norm_medians . append ( val_median ) clust_ids_to_remove . append ( i ) if len ( center_norm_medians ) == n_required_clusters : break else : centers_norm_clusters = [ c for i , c in enumerate ( centers_norm_clusters ) if i not in clust_ids_to_remove ] continue break assert len ( center_norm_medians ) == n_required_clusters center_norm_medians = np . array ( sorted ( center_norm_medians ) ) # 3. Adjust the cluster centers by finding the best matching array to <center_norm_medians> if sizes differ adjusted_centers = { } diffsums = { } if return_center_clusters_diffsums else None for p_num , centers in all_clusters_centers . items ( ) : centers = np . array ( centers ) corrected_centers , diffsum = find_best_matching_array ( centers , center_norm_medians , same_size_use_model_arr_diff_thresh = same_size_use_model_arr_diff_thresh ) #print(p_num, diffsum) #print(list((center_norm_medians + centers[0]) / image_scaling[p_num])) #print(list((centers_norm + centers[0]) / image_scaling[p_num])) #print() if image_scaling is not None : scaling_for_page = image_scaling [ p_num ] corrected_centers /= scaling_for_page adjusted_centers [ p_num ] = corrected_centers if return_center_clusters_diffsums : diffsums [ p_num ] = diffsum if return_center_clusters_diffsums : return adjusted_centers , diffsums else : return adjusted_centers