コード例 #1
0
def get_boxes4deforming_area(vel_file, mask_file, step=2, num_pixel=30**2, min_percentage=0.2,
                             ramp_type='quadratic', display=False):
    """Get list of boxes to cover the deforming areas.
    A pixel is identified as deforming if its velocity exceeds the MAD of the whole image.
    Parameters: vel_file : str, path of velocity file
                mask_file : str, path of mask file
                win_size  : int, length and width of the output box
                min_percentage : float between 0 and 1, minimum percentage of deforming points in the box
                ramp_type : str, type of phase ramps to be removed while evaluating the deformation
                display   : bool, plot the identification result or not
    Returns:    box_list  : list of t-tuple of int, each indicating (col0, row0, col1, row1)
    """
    win_size = int(np.sqrt(num_pixel) * step)
    print('-'*30)
    print('get boxes on deforming areas with step: {} pixels'.format(step))
    mask = readfile.read(mask_file)[0]
    vel, atr = readfile.read(vel_file)
    print('removing a {} phase ramp from input velocity before the evaluation'.format(ramp_type))
    vel = deramp(vel, mask, ramp_type=ramp_type, metadata=atr)[0]               #remove ramp before the evaluation

    # get deforming pixels
    mad = ut.median_abs_deviation_threshold(vel[mask], center=0., cutoff=3)     #deformation threshold
    print('velocity threshold / median abs dev: {:.3f} cm/yr'.format(mad))
    vel[mask == 0] = 0
    mask_aoi = (vel >= mad) + (vel <= -1. * mad)
    print('number of points: {}'.format(np.sum(mask_aoi)))

    # get deforming boxes
    box_list = []
    min_num = min_percentage * (win_size ** 2)
    length, width = vel.shape
    num_row = np.ceil(length / win_size).astype(int)
    num_col = np.ceil(width / win_size).astype(int)
    for i in range(num_row):
        r0 = i * win_size
        r1 = min([length, r0 + win_size])
        for j in range(num_col):
            c0 = j * win_size
            c1 = min([width, c0 + win_size])
            box = (c0, r0, c1, r1)
            if np.sum(mask_aoi[r0:r1, c0:c1]) >= min_num:
                box_list.append(box)
    print('number of boxes : {}'.format(len(box_list)))

    if display:
        fig, axs = plt.subplots(nrows=1, ncols=2, figsize=[12, 8], sharey=True)
        vel[mask == 0] = np.nan
        axs[0].imshow(vel, cmap='jet')
        axs[1].imshow(mask_aoi, cmap='gray')
        for box in box_list:
            for ax in axs:
                rect = Rectangle((box[0],box[1]),
                                 width=(box[2]-box[0]),
                                 height=(box[3]-box[1]),
                                 linewidth=2, edgecolor='r', fill=False)
                ax.add_patch(rect)
        plt.show()
    return box_list
コード例 #2
0
ファイル: timeseries_rms.py プロジェクト: tukuan1992/PySAR
def analyze_rms(date_list, rms_list, inps):
    # reference date
    ref_idx = np.argmin(rms_list)
    print('-' * 50 + '\ndate with min RMS: {} - {:.4f}'.format(
        date_list[ref_idx], rms_list[ref_idx]))
    ref_date_file = 'reference_date.txt'
    if ut.run_or_skip(
            out_file=ref_date_file,
            in_file=[inps.timeseries_file, inps.mask_file, inps.template_file],
            check_readable=False) == 'run':
        with open(ref_date_file, 'w') as f:
            f.write(date_list[ref_idx] + '\n')
        print('save date to file: ' + ref_date_file)

    # exclude date(s) - outliers
    try:
        rms_threshold = ut.median_abs_deviation_threshold(rms_list,
                                                          center=0.,
                                                          cutoff=inps.cutoff)
    except:
        # equivalent calculation using numpy assuming Gaussian distribution
        rms_threshold = np.median(rms_list) / .6745 * inps.cutoff

    ex_idx = [rms_list.index(i) for i in rms_list if i > rms_threshold]
    print(('-' * 50 + '\ndate(s) with RMS > {} * median RMS'
           ' ({:.4f})'.format(inps.cutoff, rms_threshold)))
    ex_date_file = 'exclude_date.txt'
    if ex_idx:
        # print
        for i in ex_idx:
            print('{} - {:.4f}'.format(date_list[i], rms_list[i]))
        # save to text file
        with open(ex_date_file, 'w') as f:
            for i in ex_idx:
                f.write(date_list[i] + '\n')
        print('save date(s) to file: ' + ex_date_file)
    else:
        print('None.')
        if os.path.isfile(ex_date_file):
            rmCmd = 'rm {}'.format(ex_date_file)
            print(rmCmd)
            os.system(rmCmd)

    # plot bar figure and save
    fig_file = os.path.splitext(inps.rms_file)[0] + '.pdf'
    fig, ax = plt.subplots(figsize=inps.fig_size)
    print('create figure in size:', inps.fig_size)
    ax = plot_rms_bar(ax,
                      date_list,
                      np.array(rms_list) * 1000.,
                      cutoff=inps.cutoff)
    fig.savefig(fig_file, bbox_inches='tight', transparent=True)
    print('save figure to file: ' + fig_file)
    return inps
コード例 #3
0
ファイル: save_kmz_timeseries.py プロジェクト: hfattahi/PySAR
def get_boxes4deforming_area(vel_file, mask_file, win_size=30, min_percentage=0.2, ramp_type='quadratic', display=False):
    """Get list of boxes to cover the deforming areas.
    A pixel is identified as deforming if its velocity exceeds the MAD of the whole image.
    Parameters: vel_file : str, path of velocity file
                mask_file : str, path of mask file
                win_size  : int, length and width of the output box
                min_percentage : float between 0 and 1, minimum percentage of deforming points in the box
                ramp_type : str, type of phase ramps to be removed while evaluating the deformation
                display   : bool, plot the identification result or not
    Returns:    box_list  : list of t-tuple of int, each indicating (col0, row0, col1, row1)
    """
    print('-'*30)
    print('get boxes on deforming areas')
    mask = readfile.read(mask_file)[0]
    vel, atr = readfile.read(vel_file)
    print('removing a {} phase ramp from input velocity before the evaluation'.format(ramp_type))
    vel = deramp(vel, mask, ramp_type=ramp_type, metadata=atr)[0]               #remove ramp before the evaluation

    # get deforming pixels
    mad = ut.median_abs_deviation_threshold(vel[mask], center=0., cutoff=3)     #deformation threshold
    print('velocity threshold / median abs dev: {:.3f} cm/yr'.format(mad))
    vel[mask == 0] = 0
    mask_aoi = (vel >= mad) + (vel <= -1. * mad)
    print('number of points: {}'.format(np.sum(mask_aoi)))

    # get deforming boxes
    box_list = []
    min_num = min_percentage * (win_size ** 2)
    length, width = vel.shape
    num_row = np.ceil(length / win_size).astype(int)
    num_col = np.ceil(width / win_size).astype(int)
    for i in range(num_row):
        r0 = i * win_size
        r1 = min([length, r0 + win_size])
        for j in range(num_col):
            c0 = j * win_size
            c1 = min([width, c0 + win_size])
            box = (c0, r0, c1, r1)
            if np.sum(mask_aoi[r0:r1, c0:c1]) >= min_num:
                box_list.append(box)
    print('number of boxes : {}'.format(len(box_list)))

    if display:
        fig, axs = plt.subplots(nrows=1, ncols=2, figsize=[12, 8], sharey=True)
        vel[mask == 0] = np.nan
        axs[0].imshow(vel, cmap='jet')
        axs[1].imshow(mask_aoi, cmap='gray')
        for box in box_list:
            for ax in axs:
                rect = Rectangle((box[0],box[1]), (box[2]-box[0]), (box[3]-box[1]), linewidth=2, edgecolor='r', fill=False)
                ax.add_patch(rect)
        plt.show()
    return box_list
コード例 #4
0
ファイル: timeseries_rms.py プロジェクト: hfattahi/PySAR
def analyze_rms(date_list, rms_list, inps):
    # reference date
    ref_idx = np.argmin(rms_list)
    print('-'*50+'\ndate with min RMS: {} - {:.4f}'.format(date_list[ref_idx],
                                                           rms_list[ref_idx]))
    ref_date_file = 'reference_date.txt'
    if ut.run_or_skip(out_file=ref_date_file,
                      in_file=[inps.timeseries_file, inps.mask_file, inps.template_file],
                      check_readable=False) == 'run':
        with open(ref_date_file, 'w') as f:
            f.write(date_list[ref_idx]+'\n')
        print('save date to file: '+ref_date_file)

    # exclude date(s) - outliers
    try:
        rms_threshold = ut.median_abs_deviation_threshold(rms_list, center=0., cutoff=inps.cutoff)
    except:
        # equivalent calculation using numpy assuming Gaussian distribution
        rms_threshold = np.median(rms_list) / .6745 * inps.cutoff

    ex_idx = [rms_list.index(i) for i in rms_list if i > rms_threshold]
    print(('-'*50+'\ndate(s) with RMS > {} * median RMS'
           ' ({:.4f})'.format(inps.cutoff, rms_threshold)))
    ex_date_file = 'exclude_date.txt'
    if ex_idx:
        # print
        for i in ex_idx:
            print('{} - {:.4f}'.format(date_list[i], rms_list[i]))
        # save to text file
        with open(ex_date_file, 'w') as f:
            for i in ex_idx:
                f.write(date_list[i]+'\n')
        print('save date(s) to file: '+ex_date_file)
    else:
        print('None.')
        if os.path.isfile(ex_date_file):
            rmCmd = 'rm {}'.format(ex_date_file)
            print(rmCmd)
            os.system(rmCmd)

    # plot bar figure and save
    fig_file = os.path.splitext(inps.rms_file)[0]+'.pdf'
    fig, ax = plt.subplots(figsize=inps.fig_size)
    print('create figure in size:', inps.fig_size)
    ax = plot_rms_bar(ax, date_list, np.array(rms_list)*1000., cutoff=inps.cutoff)
    fig.savefig(fig_file, bbox_inches='tight', transparent=True)
    print('save figure to file: '+fig_file)
    return inps
コード例 #5
0
def plot_rms_bar(ax,
                 date_list,
                 rms,
                 cutoff=3.,
                 font_size=12,
                 tick_year_num=1,
                 legend_loc='best',
                 disp_legend=True,
                 disp_side_plot=True,
                 disp_thres_text=False,
                 ylabel=r'Residual Phase $\hat \phi_{resid}$ RMS [mm]'):
    """ Bar plot Phase Residual RMS
    Parameters: ax : Axes object
                date_list : list of string in YYYYMMDD format
                rms    : 1D np.array of float for RMS value in mm
                cutoff : cutoff value of MAD outlier detection
                tick_year_num : int, number of years per major tick
                legend_loc : 'upper right' or (0.5, 0.5)
    Returns:    ax : Axes object
    """
    dates, datevector = ptime.date_list2vector(date_list)
    dates = np.array(dates)
    try:
        bar_width = min(ut.most_common(np.diff(dates).tolist(), k=2)) * 3 / 4
    except:
        bar_width = np.min(np.diff(dates).tolist()) * 3 / 4
    rms = np.array(rms)

    # Plot all dates
    ax.bar(dates, rms, bar_width.days, color=pp.mplColors[0])

    # Plot reference date
    ref_idx = np.argmin(rms)
    ax.bar(dates[ref_idx],
           rms[ref_idx],
           bar_width.days,
           color=pp.mplColors[1],
           label='Reference date')

    # Plot exclude dates
    rms_threshold = ut.median_abs_deviation_threshold(rms,
                                                      center=0.,
                                                      cutoff=cutoff)
    ex_idx = rms > rms_threshold
    if not np.all(ex_idx == False):
        ax.bar(dates[ex_idx],
               rms[ex_idx],
               bar_width.days,
               color='darkgray',
               label='Exclude date')

    # Plot rms_threshold line
    (ax, xmin, xmax) = pp.auto_adjust_xaxis_date(ax,
                                                 datevector,
                                                 font_size,
                                                 every_year=tick_year_num)
    ax.plot(np.array([xmin, xmax]),
            np.array([rms_threshold, rms_threshold]),
            '--k',
            label='Median Abs Dev * {}'.format(cutoff))

    # axis format
    ax = pp.auto_adjust_yaxis(ax,
                              np.append(rms, rms_threshold),
                              font_size,
                              ymin=0.0)
    ax.set_xlabel('Time [years]', fontsize=font_size)
    ax.set_ylabel(ylabel, fontsize=font_size)
    ax.tick_params(which='both',
                   direction='in',
                   labelsize=font_size,
                   bottom=True,
                   top=True,
                   left=True,
                   right=True)

    # 2nd axes for circles
    if disp_side_plot:
        divider = make_axes_locatable(ax)
        ax2 = divider.append_axes("right", "10%", pad="2%")
        ax2.plot(np.ones(rms.shape, np.float32) * 0.5,
                 rms,
                 'o',
                 mfc='none',
                 color=pp.mplColors[0])
        ax2.plot(np.ones(rms.shape, np.float32)[ref_idx] * 0.5,
                 rms[ref_idx],
                 'o',
                 mfc='none',
                 color=pp.mplColors[1])
        if not np.all(ex_idx == False):
            ax2.plot(np.ones(rms.shape, np.float32)[ex_idx] * 0.5,
                     rms[ex_idx],
                     'o',
                     mfc='none',
                     color='darkgray')
        ax2.plot(np.array([0, 1]), np.array([rms_threshold, rms_threshold]),
                 '--k')

        ax2.set_ylim(ax.get_ylim())
        ax2.set_xlim([0, 1])
        ax2.tick_params(which='both',
                        direction='in',
                        labelsize=font_size,
                        bottom=True,
                        top=True,
                        left=True,
                        right=True)
        ax2.get_xaxis().set_ticks([])
        ax2.get_yaxis().set_ticklabels([])

    if disp_legend:
        ax.legend(loc=legend_loc, frameon=False, fontsize=font_size)

    # rms_threshold text
    if disp_thres_text:
        ymin, ymax = ax.get_ylim()
        yoff = (ymax - ymin) * 0.1
        if (rms_threshold - ymin) > 0.5 * (ymax - ymin):
            yoff *= -1.
        ax.annotate('Median Abs Dev * {}'.format(cutoff),
                    xy=(xmin + (xmax - xmin) * 0.05, rms_threshold + yoff),
                    color='k',
                    xycoords='data',
                    fontsize=font_size)
    return ax
コード例 #6
0
ファイル: unwrap_error_hybrid.py プロジェクト: hfattahi/PySAR
def detect_unwrap_error(ifgram_file, mask_file, mask_cc_file='maskConnComp.h5', unwDatasetName='unwrapPhase',
                        cutoff=1., min_num_pixel=1e4):
    """Detect unwrapping error based on phase closure and extract coherent conn comps
    based on its histogram distribution

    Check:
    https://en.wikipedia.org/wiki/Otsu%27s_method
    from skimage.filters import threshold_otsu
    
    Parameters: ifgram_file : string, path of ifgram stack file
                mask_file   : string, path of mask file, e.g. waterMask.h5, maskConnComp.h5
                mask_cc_file: string, path of mask file for coherent conn comps
                cutoff : float, cutoff value for the mean number of nonzero phase closure
                    to be selected as coherent conn comps candidate
                min_num_pixel : float, min number of pixels left after morphology operation
                    to be determined as coherent conn comps
    Returns:    mask_cc_file : string, path of mask file for coherent conn comps
    """
    print('-'*50)
    print('detect unwraping error based on phase closure')
    obj = ifgramStack(ifgram_file)
    obj.open(print_msg=False)
    C = obj.get_design_matrix4triplet(obj.get_date12_list(dropIfgram=False))

    num_nonzero_closure = get_nonzero_phase_closure(ifgram_file, unwDatasetName=unwDatasetName)

    # get histogram of num_nonzero_phase_closure
    mask = readfile.read(mask_file)[0]
    mask *= num_nonzero_closure != 0.

    fig, ax = plt.subplots(nrows=1, ncols=2, figsize=[12, 4])
    num4disp = np.array(num_nonzero_closure, dtype=np.float32)
    num4disp[mask == 0] = np.nan
    im = ax[0].imshow(num4disp)
    ax[0].set_xlabel('Range [pix.]')
    ax[0].set_ylabel('Azimuth [pix.]')
    ax[0] = pp.auto_flip_direction(obj.metadata, ax=ax[0], print_msg=False)
    cbar = fig.colorbar(im, ax=ax[0])
    cbar.set_label('number of non-zero phase closure')

    print('2. extract coherent conn comps with unwrap error based on histogram distribution')
    max_nonzero_closure = int(np.max(num_nonzero_closure[mask]))
    bin_value, bin_edge = ax[1].hist(num_nonzero_closure[mask].flatten(),
                                     range=(0, max_nonzero_closure),
                                     log=True,
                                     bins=max_nonzero_closure)[0:2]
    ax[1].set_xlabel('number of non-zero phase closure')
    ax[1].set_ylabel('number of pixels')

    if 'Closure' not in unwDatasetName:
        print('eliminate pixels with number of nonzero phase closure < 5% of total phase closure number')
        print('\twhich can be corrected using phase closure alone.')
        bin_value[:int(C.shape[0]*0.05)] = 0.
    bin_value_thres = ut.median_abs_deviation_threshold(bin_value, cutoff=cutoff)
    print('median abs deviation cutoff value: {}'.format(cutoff))

    plt.plot([0, max_nonzero_closure], [bin_value_thres, bin_value_thres])
    out_img = 'numUnwErr_stat.png'
    fig.savefig(out_img, bbox_inches='tight', transparent=True, dpi=300)
    print('save unwrap error detection result to {}'.format(out_img))

    # histogram --> candidates of coherence conn comps --> mask_cc
    # find pixel clusters sharing similar number of non-zero phase closure
    print('searching connected components with more than {} pixels'.format(min_num_pixel))
    bin_label, n_bins = ndimage.label(bin_value > bin_value_thres)

    mask_cc = np.zeros(num_nonzero_closure.shape, dtype=np.int16)
    # first conn comp - reference conn comp with zero non-zero phase closure
    num_cc = 1
    mask_cc1 = num_nonzero_closure == 0.
    mask_cc1s = ut.get_all_conn_components(mask_cc1, min_num_pixel=min_num_pixel)
    for mask_cc1 in mask_cc1s:
        mask_cc += mask_cc1

    # other conn comps - target conn comps to be corrected for unwrap error
    for i in range(n_bins):
        idx = np.where(bin_label == i+1)[0]
        mask_cci0 = np.multiply(num_nonzero_closure >= bin_edge[idx[0]],
                                num_nonzero_closure <  bin_edge[idx[-1]+1])
        mask_ccis = ut.get_all_conn_components(mask_cci0, min_num_pixel=min_num_pixel)
        if mask_ccis:
            for mask_cci in mask_ccis:
                num_cc += 1
                mask_cc += mask_cci * num_cc
    
                fig, ax = plt.subplots(nrows=1, ncols=2, figsize=[8, 4])
                im = ax[0].imshow(mask_cci0)
                im = ax[1].imshow(mask_cci)
                fig.savefig('mask_cc{}.png'.format(num_cc),
                            bbox_inches='tight', transparent=True, dpi=300)

    # save to hdf5 file
    num_bridge = num_cc - 1
    atr = dict(obj.metadata)
    atr['FILE_TYPE'] = 'mask'
    atr['UNIT'] = 1
    writefile.write(mask_cc, out_file=mask_cc_file, metadata=atr)

    # plot and save figure to img file
    out_img = '{}.png'.format(os.path.splitext(mask_cc_file)[0])
    fig, ax = plt.subplots(figsize=[6, 8])
    im = ax.imshow(mask_cc)
    ax = pp.auto_flip_direction(atr, ax=ax, print_msg=False)
    divider = make_axes_locatable(ax)
    cax = divider.append_axes("right", "3%", pad="3%")
    cbar = plt.colorbar(im, cax=cax, ticks=np.arange(num_bridge+2))
    fig.savefig(out_img, bbox_inches='tight', transparent=True, dpi=300)
    print('save to {}'.format(out_img))

    return mask_cc_file
コード例 #7
0
ファイル: timeseries_rms.py プロジェクト: hfattahi/PySAR
def plot_rms_bar(ax, date_list, rms, cutoff=3., font_size=12, 
                 tick_year_num=1, legend_loc='best',
                 disp_legend=True, disp_side_plot=True, disp_thres_text=True,
                 ylabel=r'Residual Phase $\hat \phi_{resid}$ RMS [mm]'):
    """ Bar plot Phase Residual RMS
    Parameters: ax : Axes object
                date_list : list of string in YYYYMMDD format
                rms    : 1D np.array of float for RMS value in mm
                cutoff : cutoff value of MAD outlier detection
                tick_year_num : int, number of years per major tick
                legend_loc : 'upper right' or (0.5, 0.5)
    Returns:    ax : Axes object
    """
    dates, datevector = ptime.date_list2vector(date_list)
    dates = np.array(dates)
    try:
        bar_width = min(ut.most_common(np.diff(dates).tolist(), k=2))*3/4
    except:
        bar_width = np.min(np.diff(dates).tolist())*3/4
    rms = np.array(rms)

    # Plot all dates
    ax.bar(dates, rms, bar_width.days, color=pp.mplColors[0])

    # Plot reference date
    ref_idx = np.argmin(rms)
    ax.bar(dates[ref_idx], rms[ref_idx], bar_width.days, color=pp.mplColors[1], label='Reference date')

    # Plot exclude dates
    rms_threshold = ut.median_abs_deviation_threshold(rms, center=0., cutoff=cutoff)
    ex_idx = rms > rms_threshold
    if not np.all(ex_idx==False):
        ax.bar(dates[ex_idx], rms[ex_idx], bar_width.days, color='darkgray', label='Exclude date')

    # Plot rms_threshold line
    (ax, xmin, xmax) = pp.auto_adjust_xaxis_date(ax, datevector, font_size, every_year=tick_year_num)
    ax.plot(np.array([xmin, xmax]), np.array([rms_threshold, rms_threshold]), '--k', label='RMS threshold')

    # axis format
    ax = pp.auto_adjust_yaxis(ax, np.append(rms, rms_threshold), font_size, ymin=0.0)
    ax.set_xlabel('Time [years]', fontsize=font_size)
    ax.set_ylabel(ylabel, fontsize=font_size)
    ax.tick_params(which='both', direction='in', labelsize=font_size,
                   bottom=True, top=True, left=True, right=True)

    # 2nd axes for circles
    if disp_side_plot:
        divider = make_axes_locatable(ax)
        ax2 = divider.append_axes("right", "10%", pad="2%")
        ax2.plot(np.ones(rms.shape, np.float32) * 0.5, rms, 'o', mfc='none', color=pp.mplColors[0])
        ax2.plot(np.ones(rms.shape, np.float32)[ref_idx] * 0.5, rms[ref_idx], 'o', mfc='none', color=pp.mplColors[1])
        if not np.all(ex_idx==False):
            ax2.plot(np.ones(rms.shape, np.float32)[ex_idx] * 0.5, rms[ex_idx], 'o', mfc='none', color='darkgray')
        ax2.plot(np.array([0, 1]), np.array([rms_threshold, rms_threshold]), '--k')

        ax2.set_ylim(ax.get_ylim())
        ax2.set_xlim([0, 1])
        ax2.tick_params(which='both', direction='in', labelsize=font_size,
                        bottom=True, top=True, left=True, right=True)
        ax2.get_xaxis().set_ticks([])
        ax2.get_yaxis().set_ticklabels([])

    if disp_legend:
        ax.legend(loc=legend_loc, frameon=False, fontsize=font_size)

    # rms_threshold text
    if disp_thres_text:
        ymin, ymax = ax.get_ylim()
        yoff = (ymax - ymin) * 0.1
        if (rms_threshold - ymin) > 0.5 * (ymax - ymin):
            yoff *= -1.
        ax.annotate('Median Abs Dev * {}'.format(cutoff),
                    xy=(xmin + (xmax-xmin)*0.05, rms_threshold + yoff ),
                    color='k', xycoords='data', fontsize=font_size)
    return ax
コード例 #8
0
def detect_unwrap_error(ifgram_file,
                        mask_file,
                        mask_cc_file='maskConnComp.h5',
                        unwDatasetName='unwrapPhase',
                        cutoff=1.,
                        min_num_pixel=1e4):
    """Detect unwrapping error based on phase closure and extract coherent conn comps
    based on its histogram distribution

    Check:
    https://en.wikipedia.org/wiki/Otsu%27s_method
    from skimage.filters import threshold_otsu
    
    Parameters: ifgram_file : string, path of ifgram stack file
                mask_file   : string, path of mask file, e.g. waterMask.h5, maskConnComp.h5
                mask_cc_file: string, path of mask file for coherent conn comps
                cutoff : float, cutoff value for the mean number of nonzero phase closure
                    to be selected as coherent conn comps candidate
                min_num_pixel : float, min number of pixels left after morphology operation
                    to be determined as coherent conn comps
    Returns:    mask_cc_file : string, path of mask file for coherent conn comps
    """
    print('-' * 50)
    print('detect unwraping error based on phase closure')
    obj = ifgramStack(ifgram_file)
    obj.open(print_msg=False)
    C = obj.get_design_matrix4triplet(obj.get_date12_list(dropIfgram=False))

    num_nonzero_closure = get_nonzero_phase_closure(
        ifgram_file, unwDatasetName=unwDatasetName)

    # get histogram of num_nonzero_phase_closure
    mask = readfile.read(mask_file)[0]
    mask *= num_nonzero_closure != 0.

    fig, ax = plt.subplots(nrows=1, ncols=2, figsize=[12, 4])
    num4disp = np.array(num_nonzero_closure, dtype=np.float32)
    num4disp[mask == 0] = np.nan
    im = ax[0].imshow(num4disp)
    ax[0].set_xlabel('Range [pix.]')
    ax[0].set_ylabel('Azimuth [pix.]')
    ax[0] = pp.auto_flip_direction(obj.metadata, ax=ax[0], print_msg=False)
    cbar = fig.colorbar(im, ax=ax[0])
    cbar.set_label('number of non-zero phase closure')

    print(
        '2. extract coherent conn comps with unwrap error based on histogram distribution'
    )
    max_nonzero_closure = int(np.max(num_nonzero_closure[mask]))
    bin_value, bin_edge = ax[1].hist(num_nonzero_closure[mask].flatten(),
                                     range=(0, max_nonzero_closure),
                                     log=True,
                                     bins=max_nonzero_closure)[0:2]
    ax[1].set_xlabel('number of non-zero phase closure')
    ax[1].set_ylabel('number of pixels')

    if 'Closure' not in unwDatasetName:
        print(
            'eliminate pixels with number of nonzero phase closure < 5% of total phase closure number'
        )
        print('\twhich can be corrected using phase closure alone.')
        bin_value[:int(C.shape[0] * 0.05)] = 0.
    bin_value_thres = ut.median_abs_deviation_threshold(bin_value,
                                                        cutoff=cutoff)
    print('median abs deviation cutoff value: {}'.format(cutoff))

    plt.plot([0, max_nonzero_closure], [bin_value_thres, bin_value_thres])
    out_img = 'numUnwErr_stat.png'
    fig.savefig(out_img, bbox_inches='tight', transparent=True, dpi=300)
    print('save unwrap error detection result to {}'.format(out_img))

    # histogram --> candidates of coherence conn comps --> mask_cc
    # find pixel clusters sharing similar number of non-zero phase closure
    print('searching connected components with more than {} pixels'.format(
        min_num_pixel))
    bin_label, n_bins = ndimage.label(bin_value > bin_value_thres)

    mask_cc = np.zeros(num_nonzero_closure.shape, dtype=np.int16)
    # first conn comp - reference conn comp with zero non-zero phase closure
    num_cc = 1
    mask_cc1 = num_nonzero_closure == 0.
    mask_cc1s = ut.get_all_conn_components(mask_cc1,
                                           min_num_pixel=min_num_pixel)
    for mask_cc1 in mask_cc1s:
        mask_cc += mask_cc1

    # other conn comps - target conn comps to be corrected for unwrap error
    for i in range(n_bins):
        idx = np.where(bin_label == i + 1)[0]
        mask_cci0 = np.multiply(num_nonzero_closure >= bin_edge[idx[0]],
                                num_nonzero_closure < bin_edge[idx[-1] + 1])
        mask_ccis = ut.get_all_conn_components(mask_cci0,
                                               min_num_pixel=min_num_pixel)
        if mask_ccis:
            for mask_cci in mask_ccis:
                num_cc += 1
                mask_cc += mask_cci * num_cc

                fig, ax = plt.subplots(nrows=1, ncols=2, figsize=[8, 4])
                im = ax[0].imshow(mask_cci0)
                im = ax[1].imshow(mask_cci)
                fig.savefig('mask_cc{}.png'.format(num_cc),
                            bbox_inches='tight',
                            transparent=True,
                            dpi=300)

    # save to hdf5 file
    num_bridge = num_cc - 1
    atr = dict(obj.metadata)
    atr['FILE_TYPE'] = 'mask'
    atr['UNIT'] = 1
    writefile.write(mask_cc, out_file=mask_cc_file, metadata=atr)

    # plot and save figure to img file
    out_img = '{}.png'.format(os.path.splitext(mask_cc_file)[0])
    fig, ax = plt.subplots(figsize=[6, 8])
    im = ax.imshow(mask_cc)
    ax = pp.auto_flip_direction(atr, ax=ax, print_msg=False)
    divider = make_axes_locatable(ax)
    cax = divider.append_axes("right", "3%", pad="3%")
    cbar = plt.colorbar(im, cax=cax, ticks=np.arange(num_bridge + 2))
    fig.savefig(out_img, bbox_inches='tight', transparent=True, dpi=300)
    print('save to {}'.format(out_img))

    return mask_cc_file