def pull_contour_check(img_path, cnts, points, scale, imgsize, dwnsmpl_lvl):
    # find the correct patch contour by checking centroids are inside
    inside_cnt = None
    count = 0
    for cnt_i in cnts:
        allgood = True
        for k in range(points.shape[0]):
            x = points[k, 0]
            y = points[k, 1]
            if not (cv2.pointPolygonTest(cnt_i, (int(x / scale), int(
                    y / scale)), False) == 0 or cv2.pointPolygonTest(
                        cnt_i, (int(x / scale), int(y / scale)), False) == 1):
                allgood = False
                count += 1
                break
        if allgood == True:
            inside_cnt = cnt_i
            break

    cnt = inside_cnt
    # create bounded box
    bb_m = np.asarray(cv2.boundingRect(cnt))
    bb_m[0] = int(bb_m[0] - np.maximum(100, (imgsize - bb_m[2]) / 2.))
    bb_m[1] = int(bb_m[1] - np.maximum(100, (imgsize - bb_m[3]) / 2.))
    bb_m[2] = int(bb_m[2] + np.maximum(100, (imgsize - bb_m[2])))
    bb_m[3] = int(bb_m[3] + np.maximum(100, (imgsize - bb_m[3])))
    bb_m[bb_m < 0] = 0
    img = getROI_img_osl(img_path, (bb_m[0], bb_m[1]), (bb_m[2], bb_m[3]),
                         dwnsmpl_lvl)
    rec_cnt = add_single_offset(cnt, (-bb_m[0], -bb_m[1]))
    cv2.drawContours(img, [rec_cnt], 0, (80, 80, 50), 2)
    return img
def pull_contour(img_path, cnt, dwnsmpl_lvl, imgsize):
    # create bounded box
    bb_m = np.asarray(cv2.boundingRect(cnt))
    bb_m[0] = int(bb_m[0] - np.maximum(100, (imgsize - bb_m[2]) / 2.))
    bb_m[1] = int(bb_m[1] - np.maximum(100, (imgsize - bb_m[3]) / 2.))
    bb_m[2] = int(bb_m[2] + np.maximum(100, (imgsize - bb_m[2])))
    bb_m[3] = int(bb_m[3] + np.maximum(100, (imgsize - bb_m[3])))
    bb_m[bb_m < 0] = 0
    img = getROI_img_osl(img_path, (bb_m[0], bb_m[1]), (bb_m[2], bb_m[3]),
                         dwnsmpl_lvl)
    rec_cnt = add_single_offset(cnt, (-bb_m[0], -bb_m[1]))
    cv2.drawContours(img, [rec_cnt], 0, (80, 80, 50), 2)
    return img
def main():
    parser = argparse.ArgumentParser(
        description=
        "This script allows manual curation of clones for a single slide. "
        "If the crypt and clone contours for a slide are saved in /base/path/Analysed_123456, "
        "then the clone images are loaded from /base/path/123456.svs using coordinates from  "
        "/base/path/Analysed_123456/crypt_network_data.txt. "
        "Clone images are curated using the '1' and '0' keys for 'good clone' or 'bad clone', respectively "
        "(alternatively the 'a' and 'd' keys can be used for good and bad). "
        "Pressing 'p' will undo the last choice and go back to the previous image. "
        "The slide counts, patch sizes and clone scores will be automatically updated to account for the manual curation. "
    )

    parser.add_argument(
        "folder_to_analyse",
        help=
        "The full or relative path to the Analysed_XXXXX folder containing crypt/clone contours, "
        "clone scores, patch sizes and crypt_network_data for the slide XXXXX.svs. "
    )

    parser.add_argument(
        '-s',
        action="store",
        dest="imgsize",
        default=1024,
        help="Optionally set the size of the images used for curation. "
        "Default set at 1024 pixels. ")
    parser.add_argument(
        '-d',
        action="store",
        dest="dwnsample",
        default=0,
        help=
        "Optionally set the (integer) downsample level of the images used for curation. "
        "Default set at 0 (highest resolution). ")
    parser.add_argument(
        '-r',
        action="store",
        dest="resolution",
        default=800,
        help=
        "Optionally set the resolution of the pop-up window showing the curation images. "
        "Default is 800 pixels. ")

    parser.add_argument(
        '-b',
        action="store_true",
        default=False,
        help=
        "Revert to backup version before running curation. Use this if a mistake was made lase time you ran the curation. "
    )

    args = parser.parse_args()
    dwnsmpl_lvl = int(args.dwnsample)
    resolution = int(args.resolution)
    imgsize = int(args.imgsize)
    bac = args.b
    random.seed()
    folder_to_analyse = os.path.abspath(args.folder_to_analyse)
    if "\\" in folder_to_analyse:
        listout = folder_to_analyse.split("\\")
        if listout[-1] == '':
            listout = listout[-2].split('_')[1:]
        else:
            listout = listout[-1].split('_')[1:]
    if "/" in folder_to_analyse:
        listout = folder_to_analyse.split("/")
        if listout[-1] == '':
            listout = listout[-2].split('_')[1:]
        else:
            listout = listout[-1].split('_')[1:]
    sep = '_'
    slide_number = sep.join(listout)
    img_path = folder_to_analyse.split("Analysed")[-3] + slide_number + ".svs"
    slide = osl.OpenSlide(img_path)
    scale = slide.level_downsamples[dwnsmpl_lvl]
    ## create backup of crypt network data; replace with backup each time you run to allow do-overs
    if os.path.isfile(folder_to_analyse + "/processed_output.csv"):
        if not os.path.isfile(folder_to_analyse + "/processed_output.bac"):
            shutil.copy2(folder_to_analyse + "/processed_output.csv",
                         folder_to_analyse + "/processed_output.bac")
    if os.path.isfile(folder_to_analyse + "/processed_output.npy"):
        os.remove(folder_to_analyse + "/processed_output.npy"
                  )  # remove binary as may need to update with new one
    if os.path.isfile(folder_to_analyse + "/clone_mask.txt"):
        if not os.path.isfile(folder_to_analyse + "/clone_mask.bac"):
            shutil.copy2(folder_to_analyse + "/clone_mask.txt",
                         folder_to_analyse + "/clone_mask.bac")
    if os.path.isfile(folder_to_analyse + "/patch_contours.txt"):
        if not os.path.isfile(folder_to_analyse + "/patch_contours.bac"):
            shutil.copy2(folder_to_analyse + "/patch_contours.txt",
                         folder_to_analyse + "/patch_contours.bac")

    if bac == True:
        try:
            shutil.copy2(folder_to_analyse + "/processed_output.bac",
                         folder_to_analyse + "/processed_output.csv")
            shutil.copy2(folder_to_analyse + "/clone_mask.bac",
                         folder_to_analyse + "/clone_mask.txt")
            shutil.copy2(folder_to_analyse + "/patch_contours.bac",
                         folder_to_analyse + "/patch_contours.txt")
        except:
            print('No backups found.')

    ## load graph of slide analysis output
    graphpath = folder_to_analyse + '/processed_output.csv'
    try:
        gg = pd.read_csv(graphpath)
        colnames = gg.columns
        fufi_col = np.where(colnames == 'p_fufi')[0][0]
        x_col = np.where(colnames == 'x')[0][0]
        y_col = np.where(colnames == 'y')[0][0]
        if gg.shape[0] == 0:
            gg = np.zeros(19)
    except:
        gg = np.zeros(19)
        clone_col = 4
        x_col = 1
        y_col = 2
    if len(gg.shape) == 1:
        gg = gg.reshape((1, gg.shape[0]))
    gg = np.asarray(gg)

    fufi_inds = np.where(gg[:, fufi_col] > 0)[0]
    good_inds = []
    bad_inds = []
    lastbinned_l = []
    i = 0
    print("The pertinent clone will be centered in the images that pop up.")
    print("Use '1' or 'a' for good fufis and '0' or 'd' for bad fufis!")
    print(
        "(Or press 'p' to undo the last choice and go back to the previous image.)"
    )
    print("If you want to quit, press 'h'.")
    while i < (len(fufi_inds) + 1):
        if i == len(fufi_inds):
            # allow one last 'p' in case final image was wrongly catergorised
            print("Fufis all done -- last chance to press 'p' and go back...")
            decision_flag = plot_img_keep_decision(load_last(),
                                                   resolution=resolution)
            if (decision_flag == ord('p') and i > 0):
                if lastbinned_l[-1] == "good": good_inds = good_inds[:-1]
                if lastbinned_l[-1] == "bad": bad_inds = bad_inds[:-1]
                # roll back binning
                lastbinned_l = lastbinned_l[:-1]
                i -= 1
            elif (decision_flag == ord('p') and i == 0):
                pass
            else:
                i += 1
        else:
            ind = fufi_inds[i]
            xy_vals = centred_tile(gg[ind, x_col:(y_col + 1)] / scale, imgsize,
                                   slide.level_dimensions[dwnsmpl_lvl])
            img = getROI_img_osl(img_path, xy_vals, (imgsize, imgsize),
                                 dwnsmpl_lvl)
            decision_flag = plot_img_keep_decision(img, resolution=resolution)
            if (decision_flag == ord('a') or decision_flag == ord('1')):
                good_inds.append(int(ind))
                i += 1
                lastbinned_l.append("good")
            elif (decision_flag == ord('d') or decision_flag == ord('0')):
                bad_inds.append(int(ind))
                i += 1
                lastbinned_l.append("bad")
            elif (decision_flag == ord('p') and i > 0):
                if lastbinned_l[-1] == "good": good_inds = good_inds[:-1]
                if lastbinned_l[-1] == "bad": bad_inds = bad_inds[:-1]
                # roll back binning
                lastbinned_l = lastbinned_l[:-1]
                i -= 1
            elif (decision_flag == ord('p') and i == 0):
                pass
            elif (decision_flag == ord('h')):
                print("Exit signal received. Breaking out.")
                break
            else:
                print(
                    "Use '1' or 'a' for good clones and '0' or 'd' for bad clones!"
                )
                print(
                    "(Or press 'p' to undo the last choice and go back to the previous image.)"
                )
                print("Repeating image...")
            print("%d of %d" % (i + 1, len(fufi_inds)))
    cv2.destroyAllWindows()
    if (decision_flag == ord('h')):
        print("Quitting!")
        return 0

    if (len(fufi_inds) == 0):
        print("No fufis to curate, doing nothing!")
        return 0

    if (len(fufi_inds) > 0):
        # update graph file
        for j in bad_inds:
            gg[j, fufi_col] = 0
        out_df = pd.DataFrame(gg)
        out_df.columns = colnames
        out_df.to_csv(graphpath, index=False)

        # update clone scores to zero for bad inds, one for good inds
        clone_scores = np.loadtxt(folder_to_analyse + "/fufi_mask.txt",
                                  ndmin=1)
        clone_scores[np.array(bad_inds, dtype=np.int32)] = 0
        clone_scores[np.array(good_inds, dtype=np.int32)] = 1
        write_score_text_file(clone_scores,
                              folder_to_analyse + "/fufi_mask.txt")

        # finally update the clone and patch counts in the batch csv using the slide number for the row reference
        if '\\' in folder_to_analyse:
            pathsep = '\\'
            listsep = folder_to_analyse.split('\\')
            if listsep[-1] == '':
                listsep = listsep[:-2]
            else:
                listsep = listsep[:-1]
            counts_folder = pathsep.join(listsep)
        if '/' in folder_to_analyse:
            listsep = folder_to_analyse.split('/')
            if listsep[-1] == '':
                listsep = listsep[:-2]
            else:
                listsep = listsep[:-1]
            counts_folder = '/' + os.path.join(*listsep)
        counts_pd = pd.read_csv(counts_folder + "/slide_counts.csv")
        colnames = np.asarray(counts_pd.columns)
        slide_ind = np.where(
            counts_pd['Slide_ID'].astype(str) == str(slide_number))[0][0]
        slide_counts = np.asarray(counts_pd)[:, 1:]
        slide_nums = np.asarray(counts_pd)[:, 0]

        slide_counts = construct_event_counts(slide_counts, out_df, slide_ind)

        slidecounts_p = pd.DataFrame(slide_counts, columns=colnames[1:])
        slidecounts_p = pd.concat(
            [pd.DataFrame({'Slide_ID': slide_nums}), slidecounts_p], axis=1)
        slidecounts_p.to_csv(counts_folder + "/slide_counts.csv",
                             sep=',',
                             index=False)
def main():
    parser = argparse.ArgumentParser(
        description=
        "This script allows manual curation of clones for a single slide. "
        "If the crypt and clone contours for a slide are saved in /base/path/Analysed_123456, "
        "then the clone images are loaded from /base/path/123456.svs using coordinates from  "
        "/base/path/Analysed_123456/crypt_network_data.txt. "
        "Clone images are curated using the '1' and '0' keys for 'good clone' or 'bad clone', respectively "
        "(alternatively the 'a' and 'd' keys can be used for good and bad). "
        "Pressing 'p' will undo the last choice and go back to the previous image. "
        "The slide counts, patch sizes and clone scores will be automatically updated to account for the manual curation. "
    )

    parser.add_argument(
        "folder_to_analyse",
        help=
        "The full or relative path to the Analysed_XXXXX folder containing crypt/clone contours, "
        "clone scores, patch sizes and crypt_network_data for the slide XXXXX.svs. "
    )

    parser.add_argument(
        '-s',
        action="store",
        dest="imgsize",
        default=1024,
        help="Optionally set the size of the images used for curation. "
        "Default set at 1024 pixels. ")
    parser.add_argument(
        '-d',
        action="store",
        dest="dwnsample",
        default=0,
        help=
        "Optionally set the (integer) downsample level of the images used for curation. "
        "Default set at 0 (highest resolution). ")
    parser.add_argument(
        '-r',
        action="store",
        dest="resolution",
        default=800,
        help=
        "Optionally set the resolution of the pop-up window showing the curation images. "
        "Default is 800 pixels. ")

    parser.add_argument(
        '-b',
        action="store_true",
        default=False,
        help=
        "Revert to backup version before running curation. Use this if a mistake was made lase time you ran the curation. "
    )

    args = parser.parse_args()
    dwnsmpl_lvl = int(args.dwnsample)
    resolution = int(args.resolution)
    imgsize = int(args.imgsize)
    bac = args.b
    random.seed()
    folder_to_analyse = os.path.abspath(args.folder_to_analyse)
    if "\\" in folder_to_analyse:
        listout = folder_to_analyse.split("\\")
        if listout[-1] == '':
            listout = listout[-2].split('_')[1:]
        else:
            listout = listout[-1].split('_')[1:]
    if "/" in folder_to_analyse:
        listout = folder_to_analyse.split("/")
        if listout[-1] == '':
            listout = listout[-2].split('_')[1:]
        else:
            listout = listout[-1].split('_')[1:]
    sep = '_'
    slide_number = sep.join(listout)
    img_path = folder_to_analyse.split("Analysed")[-3] + slide_number + ".svs"
    slide = osl.OpenSlide(img_path)
    scale = slide.level_downsamples[dwnsmpl_lvl]
    ## create backup of crypt network data; replace with backup each time you run to allow do-overs
    if os.path.isfile(folder_to_analyse + "/processed_output.csv"):
        if not os.path.isfile(folder_to_analyse + "/processed_output.bac"):
            shutil.copy2(folder_to_analyse + "/processed_output.csv",
                         folder_to_analyse + "/processed_output.bac")
    if os.path.isfile(folder_to_analyse + "/processed_output.npy"):
        os.remove(folder_to_analyse + "/processed_output.npy"
                  )  # remove binary as may need to update with new one
    if os.path.isfile(folder_to_analyse + "/clone_mask.txt"):
        if not os.path.isfile(folder_to_analyse + "/clone_mask.bac"):
            shutil.copy2(folder_to_analyse + "/clone_mask.txt",
                         folder_to_analyse + "/clone_mask.bac")
    if os.path.isfile(folder_to_analyse + "/patch_contours.txt"):
        if not os.path.isfile(folder_to_analyse + "/patch_contours.bac"):
            shutil.copy2(folder_to_analyse + "/patch_contours.txt",
                         folder_to_analyse + "/patch_contours.bac")

    if bac == True:
        try:
            shutil.copy2(folder_to_analyse + "/processed_output.bac",
                         folder_to_analyse + "/processed_output.csv")
            shutil.copy2(folder_to_analyse + "/clone_mask.bac",
                         folder_to_analyse + "/clone_mask.txt")
            shutil.copy2(folder_to_analyse + "/patch_contours.bac",
                         folder_to_analyse + "/patch_contours.txt")
        except:
            print('No backups found.')

    ## load graph of slide analysis output
    graphpath = folder_to_analyse + '/processed_output.csv'
    try:
        gg = pd.read_csv(graphpath)
        colnames = gg.columns
        clone_col = np.where(colnames == 'p_clone')[0][0]
        patch_size_col = np.where(colnames == 'patch_size')[0][0]
        patch_id_col = np.where(colnames == 'patch_id')[0][0]
        x_col = np.where(colnames == 'x')[0][0]
        y_col = np.where(colnames == 'y')[0][0]
        if gg.shape[0] == 0:
            gg = np.zeros(19)
    except:
        gg = np.zeros(19)
        clone_col = 3
        patch_size_col = 17
        patch_id_col = 18
        x_col = 1
        y_col = 2
    if len(gg.shape) == 1:
        gg = gg.reshape((1, gg.shape[0]))
    gg = np.asarray(gg)

    allclones = np.where(gg[:, clone_col] > 0)[0]
    patchid_inds = np.where(gg[:, patch_id_col] > 0)[0]
    sizes_ = gg[patchid_inds, patch_size_col]
    ids_ = gg[patchid_inds, patch_id_col].astype(np.int32)
    uniq_ids_ = np.unique(ids_).astype(np.int32)

    all_okay = False
    while all_okay == False:
        all_okay = True
        for jj in uniq_ids_:
            quoted_sizes = sizes_[np.where(ids_ == jj)[0]]
            crypts_with_size = len(sizes_[np.where(ids_ == jj)[0]])
            if (np.all(quoted_sizes == crypts_with_size) == False):
                all_okay = False
                # reduce the patch size for the rest of the patch
                thispatch = np.where(gg[:, patch_id_col] == jj)[0]
                for k in thispatch:
                    gg[k, patch_id_col] = crypts_with_size
                if len(thispatch) == 1:
                    # no longer a patch
                    gg[thispatch[0], patch_size_col] = 0
                    gg[thispatch[0], patch_id_col] = 0
        # recalculate arrays
        allclones = np.where(gg[:, clone_col] > 0)[0]
        patchid_inds = np.where(gg[:, patch_id_col] > 0)[0]
        sizes_ = gg[patchid_inds, patch_size_col]
        ids_ = gg[patchid_inds, patch_id_col].astype(np.int32)
        uniq_ids_ = np.unique(ids_).astype(np.int32)

    # get patch contours
    patchcnts_fn = folder_to_analyse + "/patch_contours.txt"
    patchcnts = load_contours(patchcnts_fn, scale=scale)

    good_patches = []
    bad_patches = []
    examine_inds = []
    lastbinned_l = []
    i = 0
    print("Use '1' or 'a' for good patches and '0' or 'd' for bad patches!")
    print(
        "Use '3' or 'w' for patches that you want to curate in more detail afterwards."
    )
    print(
        "(Or press 'p' to undo the last choice and go back to the previous image.)"
    )
    while i < (len(uniq_ids_) + 1):
        if i == len(uniq_ids_):
            # allow one last 'p' in case final image was wrongly catergorised
            print(
                "Patches all done -- last chance to press 'p' and go back...")
            decision_flag = plot_img_keep_decision(load_last(),
                                                   resolution=resolution)
            if (decision_flag == ord('p') and i > 0):
                if lastbinned_l[-1] == "good": good_patches = good_patches[:-1]
                if lastbinned_l[-1] == "bad": bad_patches = bad_patches[:-1]
                if lastbinned_l[-1] == "unsure":
                    examine_inds = examine_inds[:-1]
                # roll back binning
                lastbinned_l = lastbinned_l[:-1]
                i -= 1
            elif (decision_flag == ord('p') and i == 0):
                pass
            else:
                i += 1
        else:
            ind = uniq_ids_[i]
            thissize = sizes_[np.where(ids_ == ind)[0][0]]
            print("patch size = %d" % int(thissize))
            #         thiscnt = patchcnts[i]
            #         img = pull_contour(img_path, thiscnt, dwnsmpl_lvl, imgsize)
            pnts = gg[np.where(gg[:, patch_id_col] == ind)[0],
                      x_col:(y_col + 1)]
            img = pull_contour_check(img_path, patchcnts, pnts, scale, imgsize,
                                     dwnsmpl_lvl)
            decision_flag = plot_img_keep_decision(img, resolution=resolution)
            if (decision_flag == ord('a') or decision_flag == ord('1')):
                good_patches.append(int(ind))
                i += 1
                lastbinned_l.append("good")
            elif (decision_flag == ord('w') or decision_flag == ord('3')):
                examine_inds.append(int(ind))
                i += 1
                lastbinned_l.append("unsure")
            elif (decision_flag == ord('d') or decision_flag == ord('0')):
                bad_patches.append(int(ind))
                i += 1
                lastbinned_l.append("bad")
            elif (decision_flag == ord('p') and i > 0):
                if lastbinned_l[-1] == "good": good_patches = good_patches[:-1]
                if lastbinned_l[-1] == "bad": bad_patches = bad_patches[:-1]
                if lastbinned_l[-1] == "unsure":
                    examine_inds = examine_inds[:-1]
                # roll back binning
                lastbinned_l = lastbinned_l[:-1]
                i -= 1
            elif (decision_flag == ord('p') and i == 0):
                pass
            elif (decision_flag == ord('h')):
                print("Exit signal received. Breaking out.")
                break
            else:
                print(
                    "Use '1' or 'a' for good patches and '0' or 'd' for bad patches!"
                )
                print(
                    "Use '3' or 'w' for patches that you want to curate in more detail afterwards."
                )
                print(
                    "(Or press 'p' to undo the last choice and go back to the previous image.)"
                )
                print("Repeating image...")
            print("%d of %d" % (i + 1, len(uniq_ids_)))
    cv2.destroyAllWindows()
    if (decision_flag == ord('h')):
        print("Quitting!")
        return 0

    if (len(uniq_ids_) > 0):
        # update graph file
        for kk in bad_patches:
            cinds = patchid_inds[np.where(ids_ == kk)[0]]
            for j in cinds:
                gg[j, clone_col] = 0  # mutant
                gg[j, patch_size_col] = 0  # patch size
                cur_pid = gg[j, patch_id_col]
                gg[j, patch_id_col] = 0  # patch id
                if cur_pid > 0:
                    # reduce the patch size for the rest of the patch
                    thispatch = np.where(gg[:, patch_id_col] == cur_pid)[0]
                    for k in thispatch:
                        gg[k, patch_size_col] = gg[k, patch_size_col] - 1
                    if len(thispatch) == 1:
                        # no longer a patch
                        gg[thispatch[0], patch_size_col] = 0
                        gg[thispatch[0], patch_id_col] = 0

    # find the clones remaining after patch curation
    clone_inds = np.setdiff1d(np.where(gg[:, clone_col] > 0)[0], patchid_inds)
    extra_clone_inds = []
    for ii in range(len(examine_inds)):
        extra_clone_inds = extra_clone_inds + [
            ps for ps in patchid_inds[np.where(ids_ == examine_inds[ii])[0]]
        ]
    clone_inds = np.hstack([clone_inds,
                            np.asarray(extra_clone_inds)]).astype(np.int32)

    good_inds = []
    bad_inds = []
    partials = []
    lastbinned_l = []
    i = 0
    print("The pertinent clone will be centered in the images that pop up.")
    print("Use '1' or 'a' for good clones and '0' or 'd' for bad clones!")
    print(
        "Use '3' or 'w' for partial clones, which will also label them 'good'."
    )
    print(
        "(Or press 'p' to undo the last choice and go back to the previous image.)"
    )
    print("If you want to quit, press 'h'.")
    while i < (len(clone_inds) + 1):
        if i == len(clone_inds):
            # allow one last 'p' in case final image was wrongly catergorised
            print("Clones all done -- last chance to press 'p' and go back...")
            decision_flag = plot_img_keep_decision(load_last(),
                                                   resolution=resolution)
            if (decision_flag == ord('p') and i > 0):
                if lastbinned_l[-1] == "good": good_inds = good_inds[:-1]
                if lastbinned_l[-1] == "bad": bad_inds = bad_inds[:-1]
                if lastbinned_l[-1] == "partial":
                    good_inds = good_inds[:-1]
                    partials = partials[:-1]
                # roll back binning
                lastbinned_l = lastbinned_l[:-1]
                i -= 1
            elif (decision_flag == ord('p') and i == 0):
                pass
            else:
                i += 1
        else:
            ind = clone_inds[i]
            xy_vals = centred_tile(gg[ind, x_col:(y_col + 1)] / scale, imgsize,
                                   slide.level_dimensions[dwnsmpl_lvl])
            img = getROI_img_osl(img_path, xy_vals, (imgsize, imgsize),
                                 dwnsmpl_lvl)
            decision_flag = plot_img_keep_decision(img, resolution=resolution)
            if (decision_flag == ord('a') or decision_flag == ord('1')):
                good_inds.append(int(ind))
                i += 1
                lastbinned_l.append("good")
            elif (decision_flag == ord('w') or decision_flag == ord('3')):
                good_inds.append(int(ind))
                partials.append(int(ind))
                i += 1
                lastbinned_l.append("partial")
            elif (decision_flag == ord('d') or decision_flag == ord('0')):
                bad_inds.append(int(ind))
                i += 1
                lastbinned_l.append("bad")
            elif (decision_flag == ord('p') and i > 0):
                if lastbinned_l[-1] == "good": good_inds = good_inds[:-1]
                if lastbinned_l[-1] == "bad": bad_inds = bad_inds[:-1]
                if lastbinned_l[-1] == "partial":
                    good_inds = good_inds[:-1]
                    partials = partials[:-1]
                # roll back binning
                lastbinned_l = lastbinned_l[:-1]
                i -= 1
            elif (decision_flag == ord('p') and i == 0):
                pass
            elif (decision_flag == ord('h')):
                print("Exit signal received. Breaking out.")
                break
            else:
                print(
                    "Use '1' or 'a' for good clones and '0' or 'd' for bad clones!"
                )
                print(
                    "Use '3' or 'w' for partial clones, which will also label them 'good'."
                )
                print(
                    "(Or press 'p' to undo the last choice and go back to the previous image.)"
                )
                print("Repeating image...")
            print("%d of %d" % (i + 1, len(clone_inds)))
    cv2.destroyAllWindows()
    if (decision_flag == ord('h')):
        print("Quitting!")
        return 0

    if (len(clone_inds) == 0):
        print("No clones to curate, doing nothing!")
        return 0

    if (len(clone_inds) > 0):
        # update graph file
        for j in bad_inds:
            gg[j, clone_col] = 0  # mutant
            gg[j, patch_size_col] = 0  # patch size
            cur_pid = gg[j, patch_id_col]
            gg[j, patch_id_col] = 0  # patch id
            if cur_pid > 0:
                # reduce the patch size for the rest of the patch
                thispatch = np.where(gg[:, patch_id_col] == cur_pid)[0]
                for k in thispatch:
                    gg[k, patch_size_col] = gg[k, patch_size_col] - 1
                if len(thispatch) == 1:
                    # no longer a patch
                    gg[thispatch[0], patch_size_col] = 0
                    gg[thispatch[0], patch_id_col] = 0

        out_df = pd.DataFrame(gg)
        out_df.columns = colnames
        out_df.to_csv(graphpath, index=False)

        # update clone scores to zero for bad inds, one for good inds
        local_good_inds = np.array(good_inds, dtype=np.int32)
        local_bad_inds = np.array(bad_inds, dtype=np.int32)
        for kk in good_patches:
            local_good_inds = np.hstack(
                [local_good_inds, patchid_inds[np.where(ids_ == kk)[0]]])
        for kk in bad_patches:
            local_bad_inds = np.hstack(
                [local_bad_inds, patchid_inds[np.where(ids_ == kk)[0]]])

        clone_scores = np.loadtxt(folder_to_analyse + "/clone_mask.txt",
                                  ndmin=1)
        clone_scores[np.array(local_bad_inds, dtype=np.int32)] = 0
        clone_scores[np.array(local_good_inds, dtype=np.int32)] = 1
        write_score_text_file(clone_scores,
                              folder_to_analyse + "/clone_mask.txt")

        unique_patches = np.vstack(
            list({
                tuple(row)
                for row in np.hstack([
                    gg[allclones,
                       patch_size_col:(patch_size_col +
                                       1)], gg[allclones,
                                               patch_id_col:(patch_id_col + 1)]
                ])
            }))  # size, id
        numpatches = unique_patches.shape[0] - 1  # get rid of zero-zero row
        patchsum = int(np.sum(unique_patches, axis=0)[0])

        # update the patch contour file using the new graph data
        patchid_inds = np.where(gg[:, patch_id_col] > 0)[0]
        uids_ = (np.unique(gg[patchid_inds, patch_id_col]) - 1).astype(
            np.int32)  # to start at 0
        patch_contours_new = [patchcnts[ct] for ct in uids_]
        write_cnt_text_file(patch_contours_new,
                            folder_to_analyse + "/patch_contours.txt")

        # finally update the clone and patch counts in the batch csv using the slide number for the row reference
        if '\\' in folder_to_analyse:
            pathsep = '\\'
            listsep = folder_to_analyse.split('\\')
            if listsep[-1] == '':
                listsep = listsep[:-2]
            else:
                listsep = listsep[:-1]
            counts_folder = pathsep.join(listsep)
        if '/' in folder_to_analyse:
            listsep = folder_to_analyse.split('/')
            if listsep[-1] == '':
                listsep = listsep[:-2]
            else:
                listsep = listsep[:-1]
            counts_folder = '/' + os.path.join(*listsep)
        counts_pd = pd.read_csv(counts_folder + "/slide_counts.csv")
        colnames = np.asarray(counts_pd.columns)
        slide_ind = np.where(
            counts_pd['Slide_ID'].astype(str) == str(slide_number))[0][0]
        slide_counts = np.asarray(counts_pd)[:, 1:]
        slide_nums = np.asarray(counts_pd)[:, 0]

        slide_counts = construct_event_counts(slide_counts, out_df, slide_ind)

        slidecounts_p = pd.DataFrame(slide_counts, columns=colnames[1:])
        slidecounts_p = pd.concat(
            [pd.DataFrame({'Slide_ID': slide_nums}), slidecounts_p], axis=1)
        slidecounts_p.to_csv(counts_folder + "/slide_counts.csv",
                             sep=',',
                             index=False)