Example #1
0
def quick_Rand(gt, pred, seq=False):
    counter_pairwise = fast64counter.ValueCountInt64()
    counter_gt = fast64counter.ValueCountInt64()
    counter_pred = fast64counter.ValueCountInt64()

    mask = (gt > 0)
    pred = thin_boundaries(pred, mask)
    gt = gt[mask].astype(np.int32)
    pred = pred[mask].astype(np.int32)
    counter_pairwise.add_values_pair32(gt, pred)
    counter_gt.add_values_32(gt)
    counter_pred.add_values_32(pred)

    # fetch counts
    frac_pairwise = counter_pairwise.get_counts()[1]
    frac_gt = counter_gt.get_counts()[1]
    frac_pred = counter_pred.get_counts()[1]

    #print 'frac_pairwise:', frac_pairwise
    #print 'frac_gt:', frac_gt
    #print 'frac_pred:', frac_pred

    # normalize to probabilities
    frac_pairwise = frac_pairwise.astype(np.double) / frac_pairwise.sum()
    frac_gt = frac_gt.astype(np.double) / frac_gt.sum()
    frac_pred = frac_pred.astype(np.double) / frac_pred.sum()

    return Rand(frac_pairwise, frac_gt, frac_pred, 0.5)
Example #2
0
def segmentation_metrics(ground_truth, prediction, seq=False):
    '''Computes adjusted FRand and VI between ground_truth and prediction.

    Metrics from: Crowdsourcing the creation of image segmentation algorithms
    for connectomics, Arganda-Carreras, et al., 2015, Frontiers in Neuroanatomy

    ground_truth - correct labels
    prediction - predicted labels

    Boundaries (label == 0) in prediction are thinned until gone, then are
    masked to foreground (label > 0) in ground_truth.

    Return value is ((FRand, FRand_split, FRand_merge), (VI, VI_split, VI_merge)).

    If seq is True, then it is assumed that the ground_truth and prediction are
    sequences that should be processed elementwise.

    '''

    # make non-sequences into sequences to simplify the code below
    if not seq:
        ground_truth = [ground_truth]
        prediction = [prediction]

    counter_pairwise = fast64counter.ValueCountInt64()
    counter_gt = fast64counter.ValueCountInt64()
    counter_pred = fast64counter.ValueCountInt64()

    for gt, pred in zip(ground_truth, prediction):
        mask = (gt > 0)
        pred = thin_boundaries(pred, mask)
        gt = gt[mask].astype(np.int32)
        pred = pred[mask].astype(np.int32)
        counter_pairwise.add_values_pair32(gt, pred)
        counter_gt.add_values_32(gt)
        counter_pred.add_values_32(pred)

    # fetch counts
    frac_pairwise = counter_pairwise.get_counts()[1]
    frac_gt = counter_gt.get_counts()[1]
    frac_pred = counter_pred.get_counts()[1]

    # normalize to probabilities
    frac_pairwise = frac_pairwise.astype(np.double) / frac_pairwise.sum()
    frac_gt = frac_gt.astype(np.double) / frac_gt.sum()
    frac_pred = frac_pred.astype(np.double) / frac_pred.sum()

    alphas = {'F-score': 0.5, 'split': 0.0, 'merge': 1.0}

    Rand_scores = {
        k: Rand(frac_pairwise, frac_gt, frac_pred, v)
        for k, v in alphas.items()
    }
    VI_scores = {
        k: VI(frac_pairwise, frac_gt, frac_pred, v)
        for k, v in alphas.items()
    }

    return {'Rand': Rand_scores, 'VI': VI_scores}
Example #3
0
 def overlaps():
     for z_spacing in range(1, maximum_link_distance + 1):
         overlap_areas = fast64counter.ValueCountInt64()
         for Z in range(numslices - z_spacing):
             for xslice, yslice in work_by_chunks(labels):
                 subimages_d1 = [
                     labels[yslice, xslice, Seg, Z][...].ravel()
                     for Seg in range(numsegs)
                 ]
                 subimages_d2 = [
                     labels[yslice, xslice, Seg,
                            Z + z_spacing][...].ravel()
                     for Seg in range(numsegs)
                 ]
                 for s1 in subimages_d1:
                     for s2 in subimages_d2:
                         overlap_areas.add_values_pair32(s1, s2)
         idxs1, idxs2, overlap_areas = overlap_areas.get_counts_pair32()
         mask = (idxs1 > 0) & (idxs2 > 0)
         idxs1 = idxs1[mask]
         idxs2 = idxs2[mask]
         overlap_areas = overlap_areas[mask]
         print len(idxs1), "Overlaps at spacing", z_spacing
         for idx1, idx2, overlap_area in zip(idxs1, idxs2, overlap_areas):
             yield idx1, idx2, link_worth(float(areas[idx1]),
                                          float(areas[idx2]),
                                          float(overlap_area), z_spacing)
Example #4
0
def count_overlaps_exclusionsets(numslices, numsegs, labels, link_worth):
    areacounter = fast64counter.ValueCountInt64()
    # Count areas of each label
    for xslice, yslice in work_by_chunks(labels):
        for Z in range(numslices):
            for Seg in range(numsegs):
                lbls = labels[yslice, xslice, Seg, Z][...]
                areacounter.add_values_32(lbls.ravel())
    keys, areas = areacounter.get_counts()
    areas = areas[np.argsort(keys)]
    areas[0] = 0

    # sanity check
    assert np.all(np.sort(keys) == np.arange(len(keys)))

    def exclusions():
        for Z in range(numslices):
            print "exl numslices", Z
            excls = set()
            for xslice, yslice in work_by_chunks(labels):
                subimages = [
                    labels[yslice, xslice, Seg, Z][...].ravel()
                    for Seg in range(numsegs)
                ]
                excls.update(set(zip(*subimages)))
            # filter out zeros
            excls = set(tuple(i for i in s if i) for s in excls)
            for excl in excls:
                if len(excl) > 1:
                    yield excl

    def overlaps():
        overlap_areas = fast64counter.ValueCountInt64()
        for Z in range(numslices - 1):
            for xslice, yslice in work_by_chunks(labels):
                subimages_d1 = [
                    labels[yslice, xslice, Seg, Z][...].ravel()
                    for Seg in range(numsegs)
                ]
                subimages_d2 = [
                    labels[yslice, xslice, Seg, Z + 1][...].ravel()
                    for Seg in range(numsegs)
                ]
                for s1 in subimages_d1:
                    for s2 in subimages_d2:
                        overlap_areas.add_values_pair32(s1, s2)
        idxs1, idxs2, overlap_areas = overlap_areas.get_counts_pair32()
        mask = (idxs1 > 0) & (idxs2 > 0)
        idxs1 = idxs1[mask]
        idxs2 = idxs2[mask]
        overlap_areas = overlap_areas[mask]
        print len(idxs1), "Overlaps"
        for idx1, idx2, overlap_area in zip(idxs1, idxs2, overlap_areas):
            yield idx1, idx2, link_worth(float(areas[idx1]),
                                         float(areas[idx2]),
                                         float(overlap_area))

    return areas, exclusions(), overlaps()
Example #5
0
def count_overlaps(depth, numsegs, labels):
    st = time.time()

    htable = fast64counter.ValueCountInt64()
    # Count areas of each label
    for D in range(depth):
        for Seg in range(numsegs):
            lbls = labels[Seg, D, :, :][...]
            htable.add_values(lbls.ravel())
    keys, areas = htable.get_counts()
    areas = areas[np.argsort(keys)]
    areas[0] = 0

    # sanity check
    assert np.all(np.sort(keys) == np.arange(len(keys)))

    def exclusions():
        for D in range(depth):
            print "exl depth", D
            excls = set()
            for xpos in range(0, labels.shape[2], chunksize):
                for ypos in range(0, labels.shape[3], chunksize):
                    subimages = [labels[Seg, D, xpos:(xpos + chunksize), ypos:(ypos + chunksize)][...].ravel() for Seg in range(numsegs)]
                    excls.update(set(zip(*subimages)))
            for excl in excls:
                yield excl

    def overlaps():
        overlap_areas = fast64counter.ValueCountInt64()
        for D in range(depth - 1):
            print "depth", D
            for xpos in range(0, labels.shape[2], chunksize):
                for ypos in range(0, labels.shape[3], chunksize):
                    subimages_d1 = [labels[Seg, D, xpos:(xpos + chunksize), ypos:(ypos + chunksize)][...].ravel().astype(np.int32) for Seg in range(numsegs)]
                    subimages_d2 = [labels[Seg, D + 1, xpos:(xpos + chunksize), ypos:(ypos + chunksize)][...].ravel().astype(np.int32) for Seg in range(numsegs)]
                    for s1 in subimages_d1:
                        for s2 in subimages_d2:
                            overlap_areas.add_values_32(s1, s2)
        combined_idxs, overlap_areas = overlap_areas.get_counts()
        idxs1 = combined_idxs >> 32
        idxs2 = combined_idxs & 0xffffffff
        mask = (idxs1 > 0) & (idxs2 > 0)
        idxs1 = idxs1[mask]
        idxs2 = idxs2[mask]
        overlap_areas = overlap_areas[mask]
        print len(idxs1), "Overlaps"
        return idxs1, idxs2, link_worth(areas[idxs1], areas[idxs2], overlap_areas)

    _overlaps = overlaps()
    print "Area counting and overlaps took", int(time.time() - st), "seconds"

    return areas, exclusions(), _overlaps
Example #6
0
 def overlaps():
     overlap_areas = fast64counter.ValueCountInt64()
     for D in range(depth - 1):
         print "depth", D
         for xpos in range(0, labels.shape[2], chunksize):
             for ypos in range(0, labels.shape[3], chunksize):
                 subimages_d1 = [labels[Seg, D, xpos:(xpos + chunksize), ypos:(ypos + chunksize)][...].ravel().astype(np.int32) for Seg in range(numsegs)]
                 subimages_d2 = [labels[Seg, D + 1, xpos:(xpos + chunksize), ypos:(ypos + chunksize)][...].ravel().astype(np.int32) for Seg in range(numsegs)]
                 for s1 in subimages_d1:
                     for s2 in subimages_d2:
                         overlap_areas.add_values_32(s1, s2)
     combined_idxs, overlap_areas = overlap_areas.get_counts()
     idxs1 = combined_idxs >> 32
     idxs2 = combined_idxs & 0xffffffff
     mask = (idxs1 > 0) & (idxs2 > 0)
     idxs1 = idxs1[mask]
     idxs2 = idxs2[mask]
     overlap_areas = overlap_areas[mask]
     print len(idxs1), "Overlaps"
     return idxs1, idxs2, link_worth(areas[idxs1], areas[idxs2], overlap_areas)
Example #7
0
    def pairwise_multimatch(self, cutout1, cutout2):
        '''Match the segments in two cutouts using pairwise marriage
        
        :param cutout1: The area to examine for overlapping segmentation from
                        the first volume.
        :param cutout2: The area to examine for overlapping segmentation from
                        the second volume.
        
        The code in this routine is adapted from Seymour Knowles-Barley's
        pairwise_multimatch: https://github.com/Rhoana/rhoana/blob/29526687202921e7173b33ec909fcd6e5b9e18bf/PairwiseMatching/pairwise_multimatch.py
        '''
        counter = fast64counter.ValueCountInt64()
        counter.add_values_pair32(
            cutout1.astype(np.int32).ravel(),
            cutout2.astype(np.int32).ravel())
        overlap_labels1, overlap_labels2, overlap_areas = \
            counter.get_counts_pair32()

        areacounter = fast64counter.ValueCountInt64()
        areacounter.add_values(np.int64(cutout1.ravel()))
        areacounter.add_values(np.int64(cutout2.ravel()))
        areas = dict(zip(*areacounter.get_counts()))
        # Merge with stable marrige matches best match = greatest overlap
        to_merge = []
        to_merge_overlap_areas = []

        m_preference = {}
        w_preference = {}

        # Generate preference lists
        for l1, l2, overlap_area in zip(overlap_labels1, overlap_labels2,
                                        overlap_areas):

            if l1 != 0 and l2 != 0 and\
               overlap_area >= self.min_overlap_volume:
                if l1 not in m_preference:
                    m_preference[l1] = [(l2, overlap_area)]
                else:
                    m_preference[l1].append((l2, overlap_area))
                if l2 not in w_preference:
                    w_preference[l2] = [(l1, overlap_area)]
                else:
                    w_preference[l2].append((l1, overlap_area))

        def get_area(l1, l2):
            return [_ for _ in m_preference[l1] if _[0] == l2][0][1]

        # Sort preference lists
        for mk in m_preference.keys():
            m_preference[mk] = sorted(m_preference[mk],
                                      key=lambda x: x[1],
                                      reverse=True)

        for wk in w_preference.keys():
            w_preference[wk] = sorted(w_preference[wk],
                                      key=lambda x: x[1],
                                      reverse=True)

        # Prep for proposals
        mlist = sorted(m_preference.keys())
        wlist = sorted(w_preference.keys())

        mfree = mlist[:] * self.max_poly_matches
        engaged = {}
        mprefers2 = copy.deepcopy(m_preference)
        wprefers2 = copy.deepcopy(w_preference)

        # Stable marriage loop
        rh_logger.logger.report_event("Entering stable marriage loop")
        t0 = time.time()
        while mfree:
            m = mfree.pop(0)
            mlist = mprefers2[m]
            if mlist:
                w = mlist.pop(0)[0]
                fiance = engaged.get(w)
                if not fiance:
                    # She's free
                    engaged[w] = [m]
                    rh_logger.logger.report_event(
                        "  {0} and {1} engaged".format(w, m),
                        log_level=logging.DEBUG)
                elif len(fiance) < self.max_poly_matches and m not in fiance:
                    # Allow polygamy
                    engaged[w].append(m)
                    rh_logger.logger.report_event(
                        "  {0} and {1} engaged".format(w, m),
                        log_level=logging.DEBUG)
                else:
                    # m proposes w
                    wlist = list(x[0] for x in wprefers2[w])
                    dumped = False
                    for current_match in fiance:
                        if wlist.index(current_match) > wlist.index(m):
                            # w prefers new m
                            engaged[w].remove(current_match)
                            engaged[w].append(m)
                            dumped = True
                            rh_logger.logger.report_event(
                                "  {0} dumped {1} for {2}".format(
                                    w, current_match, m),
                                log_level=logging.DEBUG)
                            if mprefers2[current_match]:
                                # current_match has more w to try
                                mfree.append(current_match)
                            break
                    if not dumped and mlist:
                        # She is faithful to old fiance - look again
                        mfree.append(m)
        rh_logger.logger.report_metric("Stable marriage loop time (sec)",
                                       time.time() - t0)

        # m_can_adopt = copy.deepcopy(overlap_labels1)
        # w_can_adopt = copy.deepcopy(overlap_labels1)
        m_partner = {}
        w_partner = {}
        t0 = time.time()
        for l2 in engaged.keys():
            for l1 in engaged[l2]:

                rh_logger.logger.report_event(
                    "Merging segments {1} and {0}.".format(l1, l2),
                    log_level=logging.DEBUG)
                to_merge.append((l1, l2))
                to_merge_overlap_areas.append(get_area(l1, l2))

                # Track partners
                if l1 in m_partner:
                    m_partner[l1].append(l2)
                else:
                    m_partner[l1] = [l2]
                if l2 in w_partner:
                    w_partner[l2].append(l1)
                else:
                    w_partner[l2] = [l1]
        rh_logger.logger.report_metric("Pairwise multimatch merge time (sec)",
                                       time.time() - t0)
        # Join all orphans that fit overlap proportion critera (no limit)
        if not self.dont_join_orphans:
            t0 = time.time()
            for l1 in m_preference.keys():

                # ignore any labels with a match
                # if l1 in m_partner.keys():
                #     continue

                l2, overlap_area = m_preference[l1][0]

                # ignore if this pair is already matched
                if l1 in m_partner.keys() and l2 in m_partner[l1]:
                    continue

                overlap_ratio = overlap_area / np.float32(areas[l1])

                if overlap_ratio >= self.orphan_min_overlap_ratio and \
                   overlap_area >= self.orphan_min_overlap_volume:
                    rh_logger.logger.report_event(
                        "Merging orphan segment {0} to {1} ({2} voxel overlap = {3:0.2f}%)."
                        .format(l1, l2, overlap_area, overlap_ratio * 100),
                        log_level=logging.DEBUG)
                    to_merge.append((l1, l2))
                    to_merge_overlap_areas.append(get_area(l1, l2))

            for l2 in w_preference.keys():

                # ignore any labels with a match
                # if l2 in w_partner.keys():
                #     continue

                l1, overlap_area = w_preference[l2][0]

                # ignore if this pair is already matched
                if l2 in w_partner.keys() and l1 in w_partner[l2]:
                    continue

                overlap_ratio = overlap_area / np.float32(areas[l2])

                if overlap_ratio >= self.orphan_min_overlap_ratio and \
                   overlap_area >= self.orphan_min_overlap_volume:
                    rh_logger.logger.report_event(
                        "Merging orphan segment {0} to {1} ({2} voxel overlap = {3:0.2f}%)."
                        .format(l2, l1, overlap_area, overlap_ratio * 100),
                        log_level=logging.DEBUG)
                    to_merge.append((l1, l2))
                    to_merge_overlap_areas.append(get_area(l1, l2))
            rh_logger.logger.report_metric(
                "Pairwise multimatch orphan joining time",
                time.time() - t0)
        #
        # Convert from np.uint32 or whatever to int to make JSON serializable
        #
        to_merge = [(int(a), int(b)) for a, b in to_merge]
        to_merge_overlap_areas = map(int, to_merge_overlap_areas)
        return to_merge, to_merge_overlap_areas
        hi_block2[direction] = 2 * halo_size

        if single_image_matching:
            lo_block1[direction] = lo_block1[direction] + halo_size
            lo_block2[direction] = lo_block2[direction] + halo_size
            hi_block1[direction] = lo_block1[direction] + 1
            hi_block2[direction] = lo_block2[direction] + 1

        block1_slice = tuple(slice(l, h) for l, h in zip(lo_block1, hi_block1))
        block2_slice = tuple(slice(l, h) for l, h in zip(lo_block2, hi_block2))
        packed_overlap1 = packed_block1[block1_slice]
        packed_overlap2 = packed_block2[block2_slice]
        print "block1", block1_slice, packed_overlap1.shape
        print "block2", block2_slice, packed_overlap2.shape

        counter = fast64counter.ValueCountInt64()
        counter.add_values_pair32(
            packed_overlap1.astype(np.int32).ravel(),
            packed_overlap2.astype(np.int32).ravel())
        overlap_labels1, overlap_labels2, overlap_areas = counter.get_counts_pair32(
        )

        areacounter = fast64counter.ValueCountInt64()
        areacounter.add_values(packed_overlap1.ravel())
        areacounter.add_values(packed_overlap2.ravel())
        areas = dict(zip(*areacounter.get_counts()))

        if Debug:
            from libtiff import TIFF

            # output full block images
Example #9
0
def segmentation_metrics(ground_truth,
                         prediction,
                         seq=False,
                         per_object=False):
    '''Computes adjusted FRand and VI between ground_truth and prediction.

    Metrics from: Crowdsourcing the creation of image segmentation algorithms
    for connectomics, Arganda-Carreras, et al., 2015, Frontiers in Neuroanatomy

    ground_truth - correct labels
    prediction - predicted labels

    Boundaries (label == 0) in prediction are thinned until gone, then are
    masked to foreground (label > 0) in ground_truth.

    Return value is ((FRand, FRand_split, FRand_merge), (VI, VI_split, VI_merge)).

    If seq is True, then it is assumed that the ground_truth and prediction are
    sequences that should be processed elementwise.

    '''

    # make non-sequences into sequences to simplify the code below
    if not seq:
        ground_truth = [ground_truth]
        prediction = [prediction]

    counter_pairwise = fast64counter.ValueCountInt64()
    counter_gt = fast64counter.ValueCountInt64()
    counter_pred = fast64counter.ValueCountInt64()

    for gt, pred in zip(ground_truth, prediction):
        mask = (gt > 0)
        pred = thin_boundaries(pred, mask)
        gt = gt[mask].astype(np.int32)
        pred = pred[mask].astype(np.int32)
        counter_pairwise.add_values_pair32(gt, pred)
        counter_gt.add_values_32(gt)
        counter_pred.add_values_32(pred)

    # fetch counts
    tot_pairwise = counter_pairwise.get_counts()[1]
    frac_gt = counter_gt.get_counts()[1]
    frac_pred = counter_pred.get_counts()[1]

    # normalize to probabilities
    frac_pairwise = tot_pairwise.astype(np.double) / tot_pairwise.sum()
    frac_gt = frac_gt.astype(np.double) / frac_gt.sum()
    frac_pred = frac_pred.astype(np.double) / frac_pred.sum()

    alphas = {'F-score': 0.5, 'split': 0.0, 'merge': 1.0}

    Rand_scores = {
        k: Rand(frac_pairwise, frac_gt, frac_pred, v)
        for k, v in alphas.items()
    }
    finfo_scores = {
        k: f_info(frac_pairwise, frac_gt, frac_pred, v)
        for k, v in alphas.items()
    }
    vi_merge, vi_split = split_vi(pred, gt)
    vi = vi_merge + vi_split
    vi_scores = {"F-score": vi, "split": vi_split, "merge": vi_merge}

    result = {
        'Rand': Rand_scores,
        'F_Info': finfo_scores,
        'VI': vi_scores,
        "tot_pairwise": counter_pairwise.get_counts_pair32()
    }
    if per_object:
        #
        # Compute summary statistics per object
        #
        ij, counts = counter_pairwise.get_counts()
        #
        # The label of predicted objects
        #
        i = ij & 0xffffffff
        #
        # The label of ground truth objects
        #
        j = ij >> 32
        #
        # # of pixels per predicted object
        #
        per_object_counts = np.bincount(i, weights=counts)
        #
        # Fraction of predicted object per ground truth object
        #
        frac = counts.astype(float) / per_object_counts[i]
        #
        # Entropy is - sum(p * log2(p))
        # Entropy = 0 for an exact match
        #
        entropy = -frac * np.log(frac) / np.log(2)
        tot_entropy = np.bincount(i, weights=entropy)
        unique_i = np.unique(i)
        #
        # area
        #
        area = np.bincount(np.hstack([_.flatten() for _ in prediction]))
        result["per_object"] = dict(
            object_id=unique_i.tolist(),
            area=area[unique_i].tolist(),
            overlap_area=per_object_counts[unique_i].tolist(),
            entropy=tot_entropy[unique_i].tolist())
    return result
Example #10
0
def condense_labels(Z, numsegs, labels):
    # project all labels at a given Z into a single plane, then merge any that
    # end up mapping to the same projected subregions (merge = remove one of
    # them)
    #
    # Regions in the presegmentations tend to be similar.  This step merges
    # segments that differ only near the boundaries.

    # work by chunks for speed.  Note that this approach splits projected
    # segments at chunk boundaries, but since we're just using them for
    # identifying similar regions, this is fine.
    overlap_counter = fast64counter.ValueCountInt64()
    sublabel_offset = 0
    for xslice, yslice in work_by_chunks(labels):
        chunklabels = [
            labels[yslice, xslice, S, Z][...] for S in range(numsegs)
        ]
        projected = chunklabels[0] > 0
        for S in range(1, numsegs):
            projected &= chunklabels[S] > 0  # Mask out boundaries
        labeled_projected, num_found = ndimage_label(projected,
                                                     output=np.int32)
        labeled_projected[labeled_projected > 0] += sublabel_offset
        sublabel_offset += num_found
        for sub in chunklabels:
            overlap_counter.add_values_pair32(labeled_projected.ravel(),
                                              sub.ravel())

    # Build original label to set of projected label map
    projected_labels, original_labels, areas = overlap_counter.get_counts_pair32(
    )
    label_to_projected = defaultdict(set)
    for pl, ol in zip(projected_labels, original_labels):
        if ol and pl:
            label_to_projected[ol].add(pl)

    # Reverse the map
    projected_to_label = defaultdict(list)
    for ol, plset in label_to_projected.iteritems():
        projected_to_label[tuple(sorted(plset))].append(ol)

    # Build a remapper to remove merged labels
    remapper = np.arange(np.max(original_labels) + 1)
    for original_label_list in projected_to_label.itervalues():
        # keep the first, but zero the rest
        if len(original_label_list) > 1:
            remapper[original_label_list[1:]] = 0

    # pack the labels in the remapper
    final_label_count = np.sum(remapper > 0)
    remapper[0] = 1  # simplify next line
    remapper[remapper > 0] = np.arange(final_label_count + 1, dtype=np.int32)

    # remap the labels by chunk
    for xslice, yslice in work_by_chunks(labels):
        for S in range(numsegs):
            l = labels[yslice, xslice, S, Z]
            labels[yslice, xslice, S, Z] = remapper[l]

    if DEBUG:
        assert len(np.unique(labels[:, :, :,
                                    Z].ravel())) == final_label_count + 1

    return final_label_count
Example #11
0
def compute_split_merge_errors_2d_image(gt_image,
                                        seg_image,
                                        min_seg_size=10,
                                        epsilon=0.4,
                                        display_results=True):

    error_image = np.zeros(gt_image.shape, dtype=np.int32)

    gt_profiles, gt_nprofiles, gt_profile_sizes = split_profiles_connected_components(
        gt_image)
    #seg_profiles, seg_nprofiles, seg_profile_sizes = split_profiles_connected_components(seg_image)
    # Ignore regions where the ground-truth label is zero
    seg_profiles, seg_nprofiles, seg_profile_sizes = split_profiles_connected_components(
        seg_image, gt_image == 0)

    counter = fast64counter.ValueCountInt64()
    counter.add_values_pair32(
        gt_profiles.astype(np.int32).ravel(),
        seg_profiles.astype(np.int32).ravel())
    overlap_labels_gt, overlap_labels_seg, overlap_areas = counter.get_counts_pair32(
    )

    gt_valid_regions = 0
    ngood_regions = 0
    nsplit_errors = 0
    nmerge_errors = 0
    nunique_merge_errors = 0
    nadjust_errors = 0
    goodRegionIndex = []

    good_area = 0
    gt_good_area = 0

    split_area = 0
    merge_area = 0
    adjust_area = 0
    gt_total_area = 0
    seg_total_area = 0

    repeat_merge_errors = {}

    for gt_id in range(1, gt_nprofiles + 1):

        gt_size = float(gt_profile_sizes[gt_id])
        if gt_size < min_seg_size:
            continue

        gt_valid_regions += 1
        gt_total_area += gt_size

        valid_overlaps = np.nonzero(
            np.logical_and(overlap_labels_gt == gt_id,
                           overlap_labels_seg != 0))[0]
        seg_counts = overlap_areas[valid_overlaps]
        #consider segs in order of size
        seg_order = np.argsort(seg_counts)[-1::-1]
        seg_counts = seg_counts[seg_order]
        seg_ids = overlap_labels_seg[valid_overlaps][seg_order]

        has_good_region = False
        has_merge_errors = False
        has_new_merge_errors = False
        has_split_errors = False
        has_adjust_errors = False

        for i, seg_id in enumerate(seg_ids):
            seg_size = float(seg_profile_sizes[seg_id])
            if seg_size < min_seg_size:
                continue

            overlap_size = seg_counts[i]
            seg_total_area += overlap_size

            gt_ratio = float(overlap_size) / gt_size
            seg_ratio = float(overlap_size) / seg_size
            match_gt = gt_ratio > 1 - epsilon  # big enough to match gt
            match_seg = seg_ratio > 1 - epsilon  # big enough to match seg

            overlap_region = np.logical_and(gt_profiles == gt_id,
                                            seg_profiles == seg_id)

            if match_gt and match_seg:
                has_good_region = True
                goodRegionIndex.append(gt_id)
                error_image[overlap_region] = 1
                good_area += overlap_size
                break
            elif match_gt and not match_seg:
                # false positive (merge error)
                if not seg_id in repeat_merge_errors:
                    repeat_merge_errors[seg_id] = True
                    has_new_merge_errors = True
                has_merge_errors = True
                error_image[overlap_region] = 2
                merge_area += overlap_size
            elif not match_gt and match_seg:
                # false negative (split error)
                has_split_errors = True
                error_image[overlap_region] = 3
                split_area += overlap_size
            else:
                # both
                has_adjust_errors = True
                error_image[overlap_region] = 4
                adjust_area += overlap_size

        if has_good_region:
            ngood_regions += 1
            gt_good_area += gt_size
        elif has_merge_errors:
            nmerge_errors += 1
            if has_new_merge_errors:
                nunique_merge_errors += 1
        elif has_split_errors:
            nsplit_errors += 1
        elif has_adjust_errors:
            nadjust_errors += 1

    if display_results:

        # dx, dy = np.gradient(gt_image)
        # gt_boundaries = boundary = np.logical_or(dx!=0, dy!=0)
        # dx, dy = np.gradient(seg_image)
        # seg_boundaries = boundary = np.logical_or(dx!=0, dy!=0)

        # color_boundary_image = np.zeros((gt_image.shape[0], gt_image.shape[1], 3), dtype=np.uint8)
        # color_boundary_image[:,:,0] = seg_boundaries * 255
        # color_boundary_image[:,:,1] = gt_boundaries * 255
        # color_boundary_image[:,:,2] = seg_boundaries * 255

        # figure(figsize=(20,20))
        # imshow(color_boundary_image)

        color_error_image = np.zeros((gt_image.shape[0], gt_image.shape[1], 3),
                                     dtype=np.uint8)
        color_error_image[:, :, 0] = np.logical_or(error_image == 2,
                                                   error_image == 4) * 255
        color_error_image[:, :, 1] = (error_image == 1) * 255
        color_error_image[:, :, 2] = np.logical_or(error_image == 3,
                                                   error_image == 4) * 255

        figure(figsize=(20, 20))
        imshow(color_error_image)

        print '{0:8d} good (2d) regions    ={1:6.2f}% of regions ={2:6.2f}% of pixels.'.format(
            ngood_regions,
            float(ngood_regions) / gt_valid_regions * 100,
            float(gt_good_area) / gt_total_area * 100)
        print '{0:8d} split error regions  ={1:6.2f}% of regions ={2:6.2f}% of pixels.'.format(
            nsplit_errors,
            float(nsplit_errors) / gt_valid_regions * 100,
            float(split_area) / gt_total_area * 100)
        print '{0:8d} merge error regions  ={1:6.2f}% of regions ={2:6.2f}% of pixels.'.format(
            nmerge_errors,
            float(nmerge_errors) / gt_valid_regions * 100,
            float(merge_area) / gt_total_area * 100)
        print '{0:8d} Umerge error regions ={1:6.2f}% of regions ={2:6.2f}% of pixels.'.format(
            nunique_merge_errors,
            float(nunique_merge_errors) / gt_valid_regions * 100,
            float(merge_area) / gt_total_area * 100)
        print '{0:8d} adjust error regions ={1:6.2f}% of regions ={2:6.2f}% of pixels.'.format(
            nadjust_errors,
            float(nadjust_errors) / gt_valid_regions * 100,
            float(adjust_area) / gt_total_area * 100)

    return ngood_regions, nsplit_errors, nmerge_errors, nunique_merge_errors, nadjust_errors, gt_valid_regions, error_image