def _add_submasks_and_decisions_one_section(self, sec, submasks, submask_decisions):

        for submask_ind, decision in submask_decisions.iteritems():
            m = submasks[submask_ind]
            cnts = find_contour_points(m, sample_every=1)[m.max()]
            if len(cnts) == 0:
                raise Exception('ERROR: section %d %d, submask %d - no contour' % (sec, submask_ind, len(cnts)))
            elif len(cnts) > 1:
                sys.stderr.write('WARNING: section %d, submask %d - %d contours\n' % (sec, submask_ind, len(cnts)))
                cnt = sorted(cnts, key=lambda c: len(c), reverse=True)[0]
            else:
                cnt = cnts[0]

            self._submask_decisions[sec][submask_ind] = decision

            if decision:
                color = 'g'
            else:
                color = 'r'

            # self.add_polygon(path=vertices_to_path(cnt), section=sec, linewidth=2, color=color)

            resampled_contour_vertices = resample_polygon(cnt, len_interval=20)
            self.add_polygon_with_circles(path=vertices_to_path(resampled_contour_vertices),
                                                        section=sec, linewidth=2, linecolor=color, vertex_radius=4)
def generate_mask_contour_visualization(fn):
    try:
        mask_fn = os.path.join(mask_dir, '%(fn)s_mask.png' % dict(fn=fn))
        mask = imread(mask_fn)

        contour_xys = find_contour_points(mask.astype(np.bool), sample_every=1)[1]

        image_fn = os.path.join(image_dir, '%(fn)s.tif' % dict(fn=fn))
        image = imread(image_fn)
        viz = image.copy()

        for cnt in contour_xys:
            cv2.polylines(viz, [cnt.astype(np.int)], True, (255,0,0), 1)

        output_fn = os.path.join(output_dir, '%(fn)s_mask_contour_viz.tif' % dict(fn=fn))

        if os.path.exists(output_fn):
            execute_command('rm -rf %s' % output_fn)

        imsave(output_fn, viz)

    except Exception as e:
        sys.stderr.write('%s\n'%e.message)
        sys.stderr.write('Mask error: %s\n' % fn)
        return
    def _add_submasks_and_decisions_one_section(self, sec, submasks,
                                                submask_decisions):

        for submask_ind, decision in submask_decisions.iteritems():
            m = submasks[submask_ind]
            cnts = find_contour_points(m, sample_every=1)[m.max()]
            if len(cnts) == 0:
                raise Exception(
                    'ERROR: section %d %d, submask %d - no contour' %
                    (sec, submask_ind, len(cnts)))
            elif len(cnts) > 1:
                sys.stderr.write(
                    'WARNING: section %d, submask %d - %d contours\n' %
                    (sec, submask_ind, len(cnts)))
                cnt = sorted(cnts, key=lambda c: len(c), reverse=True)[0]
            else:
                cnt = cnts[0]

            self._submask_decisions[sec][submask_ind] = decision

            if decision:
                color = 'g'
            else:
                color = 'r'

            # self.add_polygon(path=vertices_to_path(cnt), section=sec, linewidth=2, color=color)

            resampled_contour_vertices = resample_polygon(cnt, len_interval=20)
            self.add_polygon_with_circles(
                path=vertices_to_path(resampled_contour_vertices),
                section=sec,
                linewidth=2,
                linecolor=color,
                vertex_radius=4)
def generate_mask_contour_visualization(fn, tb_fmt):
    try:
        mask_fn = os.path.join(mask_dir, '%(fn)s_mask.png' % dict(fn=fn))
        mask = imread(mask_fn)

        contour_xys = find_contour_points(mask.astype(np.bool), sample_every=1)[1]

        image_fn = os.path.join(image_dir, '%(fn)s.%(tb_fmt)s' % dict(fn=fn, tb_fmt=tb_fmt))
        image = imread(image_fn)
        if image.ndim == 2:
            viz = gray2rgb(image)
        elif image.ndim == 3:
            viz = image.copy()
        else:
            raise

        for cnt in contour_xys:
            cv2.polylines(viz, [cnt.astype(np.int)], True, (255,0,0), 1)

        output_fn = os.path.join(output_dir, '%(fn)s_mask_contour_viz.tif' % dict(fn=fn))

        if os.path.exists(output_fn):
            execute_command('rm -rf %s' % output_fn)

        imsave(output_fn, viz)

    except Exception as e:
        sys.stderr.write('%s\n'%e.message)
        sys.stderr.write('Mask error: %s\n' % fn)
        return
def generate_mask(fn):
    try:
        img_rgb = imread(os.path.join(input_dir, fn + '.tif'))
        img = rgb2gray(img_rgb)

        entropy_mask = generate_entropy_mask(img)

        init_contours = [
            xys for xys in find_contour_points(entropy_mask.astype(np.int),
                                               sample_every=1)[1]
            if len(xys) > 50
        ]
        assert len(init_contours
                   ) > 0, 'No contour is detected from entropy mask %s' % fn

        img_adap = threshold_adaptive(img, 51)
        img_adap[~entropy_mask] = 1

        final_masks = []

        for init_cnt in init_contours:

            img_adap_gauss = gaussian(img_adap.astype(np.float), 1)

            snake = active_contour(img_adap_gauss,
                                   init_cnt.astype(np.float),
                                   alpha=1.,
                                   beta=1000.,
                                   gamma=1.,
                                   w_line=0.,
                                   w_edge=10.,
                                   max_iterations=1000)

            bg = np.zeros(img.shape[:2], bool)
            xys = points_inside_contour(snake.astype(np.int))
            bg[np.minimum(xys[:, 1], bg.shape[0] - 1),
               np.minimum(xys[:, 0], bg.shape[1] - 1)] = 1

            final_mask = bg & entropy_mask
            final_masks.append(final_mask)

        final_mask = np.any(final_masks, axis=0)

        mask_fn = os.path.join(output_dir, '%(fn)s_mask.png' % dict(fn=fn))

        if os.path.exists(mask_fn):
            sys.stderr.write('Mask exists, overwrite: %s\n' % mask_fn)

        imsave(mask_fn, img_as_ubyte(final_mask))

    except Exception as e:
        sys.stderr.write(e.message + '\n')
        sys.stderr.write('%d, Mask error: %s\n' % (len(final_masks), fn))
        return
def generate_submasks_viz(img, submasks, color=(255,0,0), linewidth=3):
    """Generate visualization of submasks."""

    viz = gray2rgb(img)
    for i, submask in enumerate(submasks):
        cnts = find_contour_points(submask)
        if 1 not in cnts or len(cnts[1]) == 0:
            sys.stderr.write('Submask %d has no contour.\n' % i)
            continue
        for cnt in cnts[1]:
            cv2.polylines(viz, [cnt.astype(np.int)], True, color, linewidth) # blue
    return viz
Esempio n. 7
0
    def save_submasks_and_decisions(self, sec):
        # submasks = self.user_submasks
        # submask_decisions = self.user_submask_decisions

        if sec not in self.user_submasks or sec not in self.user_submasks_gscene._submask_decisions:
            return

        fn = self.valid_sections_to_filenames[sec]

        # submask_fn_dir = os.path.join(submasks_dir, fn)
        submask_fn_dir = DataManager.get_user_modified_submask_dir_filepath(stack=self.stack, fn=fn)
        execute_command('rm -rf \"%(dir_fp)s\"; mkdir -p \"%(dir_fp)s\"' % {'dir_fp': submask_fn_dir})

        # Save submasks
        for submask_ind, m in self.user_submasks[sec].iteritems():
            # submask_fp = os.path.join(submask_fn_dir, fn + '_alignedTo_' + self.anchor_fn + '_submask_%d.png' % submask_ind)
            submask_fp = DataManager.get_user_modified_submask_filepath(stack=self.stack, fn=fn, what='submask', submask_ind=submask_ind)
            imsave(submask_fp, np.uint8(m)*255)

        # Save submask contour vertices.
        submask_contour_vertices_fp = DataManager.get_user_modified_submask_filepath(stack=self.stack, fn=fn, what='contour_vertices')
        # submask_contour_vertices_fp = os.path.join(submask_fn_dir, fn + '_alignedTo_' + self.anchor_fn + '_submask_contour_vertices.pkl')
        submask_contour_vertices_dict = {}
        for submask_ind, m in self.user_submasks[sec].iteritems():
            cnts = find_contour_points(m)[1]
            if len(cnts) != 1:
                sys.stderr.write("Must have exactly one contour per submask, section %d, but the sizes are %s.\n" % (sec, map(len, cnts)))
            submask_contour_vertices_dict[submask_ind] = cnts[np.argsort(map(len, cnts))[-1]]
        save_pickle(submask_contour_vertices_dict, submask_contour_vertices_fp)

        # Save submask decisions.
        decisions_fp = DataManager.get_user_modified_submask_filepath(stack=self.stack, fn=fn, what='decisions')
        # decisions_fp = os.path.join(submask_fn_dir, fn +'_alignedTo_' + self.anchor_fn +  '_submasksUserReview.txt')
        from pandas import Series
        Series(self.user_submasks_gscene._submask_decisions[sec]).to_csv(decisions_fp)
        # save_json({k: int(v) for k,v in submask_decisions[sec].iteritems()}, decisions_fp)

        # Save parameters.
        params_fp = DataManager.get_user_modified_submask_filepath(stack=self.stack, fn=fn, what='parameters')
        params = {}
        if sec in self.selected_channels:
            params['channel'] = self.selected_channels[sec]
        if sec in self.selected_snake_lambda1:
            params['snake_lambda1'] = self.selected_snake_lambda1[sec]
        if sec in self.selected_snake_min_size:
            params['min_size'] = self.selected_snake_min_size[sec]
        if len(params) > 0:
            save_json(params, params_fp)
    def save_submasks_and_decisions(self, sec):
        # submasks = self.user_submasks
        # submask_decisions = self.user_submask_decisions

        if sec not in self.user_submasks or sec not in self.user_submasks_gscene._submask_decisions:
            return

        fn = self.valid_sections_to_filenames[sec]

        # submask_fn_dir = os.path.join(submasks_dir, fn)
        submask_fn_dir = DataManager.get_user_modified_submask_dir_filepath(stack=self.stack, fn=fn)
        execute_command('rm -rf \"%(dir_fp)s\"; mkdir -p \"%(dir_fp)s\"' % {'dir_fp': submask_fn_dir})

        # Save submasks
        for submask_ind, m in self.user_submasks[sec].iteritems():
            # submask_fp = os.path.join(submask_fn_dir, fn + '_alignedTo_' + self.anchor_fn + '_submask_%d.png' % submask_ind)
            submask_fp = DataManager.get_user_modified_submask_filepath(stack=self.stack, fn=fn, what='submask', submask_ind=submask_ind)
            imsave(submask_fp, np.uint8(m)*255)

        # Save submask contour vertices.
        submask_contour_vertices_fp = DataManager.get_user_modified_submask_filepath(stack=self.stack, fn=fn, what='contour_vertices')
        # submask_contour_vertices_fp = os.path.join(submask_fn_dir, fn + '_alignedTo_' + self.anchor_fn + '_submask_contour_vertices.pkl')
        submask_contour_vertices_dict = {}
        for submask_ind, m in self.user_submasks[sec].iteritems():
            cnts = find_contour_points(m)[1]
            if len(cnts) != 1:
                sys.stderr.write("Must have exactly one contour per submask, section %d, but the sizes are %s.\n" % (sec, map(len, cnts)))
            submask_contour_vertices_dict[submask_ind] = cnts[np.argsort(map(len, cnts))[-1]]
        save_pickle(submask_contour_vertices_dict, submask_contour_vertices_fp)

        # Save submask decisions.
        decisions_fp = DataManager.get_user_modified_submask_filepath(stack=self.stack, fn=fn, what='decisions')
        # decisions_fp = os.path.join(submask_fn_dir, fn +'_alignedTo_' + self.anchor_fn +  '_submasksUserReview.txt')
        from pandas import Series
        Series(self.user_submasks_gscene._submask_decisions[sec]).to_csv(decisions_fp)
        # save_json({k: int(v) for k,v in submask_decisions[sec].iteritems()}, decisions_fp)

        # Save parameters.
        params_fp = DataManager.get_user_modified_submask_filepath(stack=self.stack, fn=fn, what='parameters')
        params = {}
        if sec in self.selected_channels:
            params['channel'] = self.selected_channels[sec]
        if sec in self.selected_snake_lambda1:
            params['snake_lambda1'] = self.selected_snake_lambda1[sec]
        if sec in self.selected_snake_min_size:
            params['min_size'] = self.selected_snake_min_size[sec]
        if len(params) > 0:
            save_json(params, params_fp)
def snake(img,
          init_submasks=None,
          init_contours=None,
          lambda1=MORPHSNAKE_LAMBDA1,
          return_masks=True,
          min_size=MIN_SUBMASK_SIZE,
          crop_margin=50):
    """
    Args:
        crop_margin (int): crop the image to fit the extent of all masks plus a margin of `margin`. Set to negative for no cropping.
    """

    if init_contours is None:
        assert init_submasks is not None, "If no init_contours is given, must provide init_submasks."
        init_submasks = [
            m for m in init_submasks
            if np.count_nonzero(init_submasks) <= min_size
        ]
        assert len(
            init_submasks
        ) > 0, "No initial submasks are above the minimal area of %d." % min_size
        assert all([
            (m.shape[0] == img.shape[0]) & (m.shape[1] == img.shape[1])
            for m in init_submasks
        ]), "Not all shapes of initial submasks match the input image."
        bbox_all_submasks = [bbox_2d(m) for m in init_submasks]
        xmin, ymin = np.min(bbox_all_submasks[:, [0, 2]])
        xmax, ymax = np.max(bbox_all_submasks[:, [1, 3]])
    else:
        init_contours = [c.astype(np.int) for c in init_contours]
        xmin, ymin = np.min([np.min(c, axis=0) for c in init_contours], axis=0)
        xmax, ymax = np.max([np.max(c, axis=0) for c in init_contours], axis=0)

    # Crop to fit the extent.
    if crop_margin >= 0:
        crop_xmin = max(0, xmin - crop_margin)
        crop_ymin = max(0, ymin - crop_margin)
        crop_xmax = min(img.shape[1] - 1, xmax + crop_margin)
        crop_ymax = min(img.shape[0] - 1, ymax + crop_margin)
        cropped_img = img[crop_ymin:crop_ymax + 1, crop_xmin:crop_xmax + 1]
    else:
        crop_xmin = 0
        crop_ymin = 0
        crop_xmax = img.shape[1] - 1
        crop_ymax = img.shape[0] - 1
        cropped_img = img

    # Form initial levelsets
    init_levelsets = []
    if init_contours is None:
        for m in init_submasks:
            init_levelset = m[crop_ymin:crop_ymax + 1, crop_xmin:crop_xmax + 1]
            init_levelset[:10, :] = 0
            init_levelset[-10:, :] = 0
            init_levelset[:, :10] = 0
            init_levelset[:, -10:] = 0
            init_levelsets.append(init_levelset)
    else:
        for cnt in init_contours:
            init_contours_on_cropped_img = cnt - (crop_xmin, crop_ymin)
            init_levelset = contours_to_mask([init_contours_on_cropped_img],
                                             cropped_img.shape[:2])
            init_levelset[:10, :] = 0
            init_levelset[-10:, :] = 0
            init_levelset[:, :10] = 0
            init_levelset[:, -10:] = 0
            init_levelsets.append(init_levelset)

    sys.stderr.write('Found %d levelsets.\n' % len(init_levelsets))

    #####################
    # Evolve morphsnake #
    #####################

    final_masks = []

    for levelset_ind, init_levelset in enumerate(init_levelsets):
        sys.stderr.write('\nContour %d\n' % levelset_ind)

        discard = False
        init_area = np.count_nonzero(init_levelset)

        t = time.time()

        msnake = morphsnakes.MorphACWE(cropped_img.astype(np.float),
                                       smoothing=int(MORPHSNAKE_SMOOTHING),
                                       lambda1=lambda1,
                                       lambda2=MORPHSNAKE_LAMBDA2)
        msnake.levelset = init_levelset.copy()

        dq = deque([None, None])
        for i in range(MORPHSNAKE_MAXITER):
            # At stable stage, the levelset (thus contour) will oscilate,
            # so instead of comparing to previous levelset, must compare to the one before the previous
            oneBefore_levelset = dq.popleft()

            # If less than 3 pixels are changed, stop.
            if i > MORPHSNAKE_MINITER:
                if np.count_nonzero(msnake.levelset - oneBefore_levelset
                                    ) < PIXEL_CHANGE_TERMINATE_CRITERIA:
                    break

            # area = np.count_nonzero(msnake.levelset)
            # if area < min_size:
            #     discard = True
            #     sys.stderr.write('Too small, stop iteration.\n')
            #     break

            labeled_mask = label(msnake.levelset.astype(np.bool))
            component_sizes = []
            for l in np.unique(labeled_mask):
                if l != 0:
                    m = labeled_mask == l
                    component_area = np.count_nonzero(m)
                    component_sizes.append(component_area)
                    if component_area / float(
                            init_area) > AREA_CHANGE_RATIO_MAX:
                        msnake.levelset[m] = 0
                        sys.stderr.write(
                            'Component area expands too much - nullified.\n')
                    elif component_area < min_size:
                        msnake.levelset[m] = 0
                        sys.stderr.write(
                            'Component area is too small - nullified.\n')
            print component_sizes

            if np.count_nonzero(msnake.levelset) / float(
                    init_area) < AREA_CHANGE_RATIO_MIN:
                discard = True
                sys.stderr.write('Area shrinks too much, stop iteration.\n')
                break

            dq.append(msnake.levelset)

            #         t = time.time()
            msnake.step()
    #         sys.stderr.write('Step: %f seconds\n' % (time.time()-t)) # 0.6 second/step, roughly 200 steps takes 120s

        sys.stderr.write('Snake finished at iteration %d.\n' % i)
        sys.stderr.write('Snake: %.2f seconds\n' % (time.time() - t))  # 72s

        if discard:
            sys.stderr.write('Discarded.\n')
            continue
        else:
            # Handles the case that a single initial contour morphs into multiple contours
            labeled_mask = label(msnake.levelset.astype(np.bool))
            for l in np.unique(labeled_mask):
                if l != 0:
                    m = labeled_mask == l
                    if np.count_nonzero(m) > min_size:
                        final_masks.append(m)
                        sys.stderr.write('Final masks added.\n')

    if len(final_masks) == 0:
        sys.stderr.write('Snake return no valid submasks.\n')
        return []

    if return_masks:
        final_masks_uncropped = []
        for m in final_masks:
            uncropped_mask = np.zeros(img.shape[:2], np.bool)
            uncropped_mask[crop_ymin:crop_ymax + 1,
                           crop_xmin:crop_xmax + 1] = m
            final_masks_uncropped.append(uncropped_mask)
        return final_masks_uncropped
    else:
        final_contours = []
        for m in final_masks:
            cnts = [
                cnt_on_cropped + (crop_xmin, crop_ymin)
                for cnt_on_cropped in find_contour_points(m)[1]
            ]
            final_contours += cnts
        return final_contours
Esempio n. 10
0
def generate_mask(fn, tb_fmt='png'):

    try:
        submask_dir = create_if_not_exists(os.path.join(output_dir, fn))
        execute_command('rm -f %s/*' % submask_dir)

        img_rgb = imread(
            os.path.join(input_dir,
                         '%(fn)s.%(tb_fmt)s' % dict(fn=fn, tb_fmt=tb_fmt)))

        stds = []
        images = []

        # for c in range(3):
        for c in [0]:

            img = img_rgb[..., c].copy()

            border = np.median(np.r_[img[:10, :].flatten(),
                                     img[-10:, :].flatten(),
                                     img[:, :10].flatten(),
                                     img[:, -10:].flatten()])

            if border < 123:
                # dark background, fluorescent
                img = img.max(
                ) - img  # invert, make tissue dark on bright background

            # Stretch contrast
            img_flattened = img.flatten()

            vmax_perc = VMAX_PERCENTILE
            while vmax_perc > 80:
                vmax = np.percentile(img_flattened, vmax_perc)
                if vmax < 255:
                    break
                else:
                    vmax_perc -= 1

            vmin_perc = VMIN_PERCENTILE
            while vmin_perc < 20:
                vmin = np.percentile(img_flattened, vmin_perc)
                if vmin > 0:
                    break
                else:
                    vmin_perc += 1

            sys.stderr.write('%d(%d percentile), %d(%d percentile)\n' %
                             (vmin, vmin_perc, vmax, vmax_perc))

            img = img_as_ubyte(rescale_intensity(img, in_range=(vmin, vmax)))

            # img[(img <= vmax) & (img >= vmin)] = 255./(vmax-vmin)*(img[(img <= vmax) & (img >= vmin)]-vmin)
            # img[img > vmax] = 255
            # img[img < vmin] = 0
            # img = img.astype(np.uint8)

            images.append(img)

            std = np.std(img)
            stds.append(std)

            sys.stderr.write('std: %.2f\n' % (std))

        best_channel_id = np.argmax(stds)
        sys.stderr.write('Use channel %s.\n' %
                         ['RED', 'GREEN', 'BLUE'][best_channel_id])
        img = images[best_channel_id]

        #############################
        ## Graph cut based method. ##
        #############################

        # Input to slic() must be float
        slic_labels = slic(img.astype(np.float),
                           sigma=SLIC_SIGMA,
                           compactness=SLIC_COMPACTNESS,
                           n_segments=SLIC_N_SEGMENTS,
                           multichannel=False,
                           max_iter=SLIC_MAXITER)

        # sim_graph = rag_mean_color(img, slic_labels, mode='similarity', sigma=SUPERPIXEL_SIMILARITY_SIGMA)

        # Normalized cut - merge superpixels.

        # for _ in range(3):
        #     try:
        #         t = time.time()
        #         ncut_labels = cut_normalized(slic_labels, sim_graph, in_place=False, thresh=SUPERPIXEL_MERGE_SIMILARITY_THRESH,
        #                                     num_cuts=GRAPHCUT_NUM_CUTS)
        #         sys.stderr.write('Normalized Cut: %.2f seconds.\n' % (time.time() - t))
        #         break
        #     except ArpackError as e:
        #         sys.stderr.write('ArpackError encountered.\n')
        #         continue

        # ncut_boundaries_viz = mark_boundaries(img, label_img=ncut_labels, background_label=-1)
        # imsave(os.path.join(submask_dir, '%(fn)s_.png' % dict(fn=fn)), ncut_boundaries_viz)

        ncut_labels = slic_labels

        # Find background superpixels.

        background_labels = np.unique(
            np.concatenate([
                ncut_labels[:, 0], ncut_labels[:, -1], ncut_labels[0, :],
                ncut_labels[-1, :]
            ]))

        # Collect training superpixels.

        border_histos = []
        for b in background_labels:
            histo = np.histogram(img[ncut_labels == b],
                                 bins=np.arange(0, 256, 5))[0].astype(np.float)
            histo = histo / np.sum(histo)
            border_histos.append(histo)

        histos = {}
        for l in np.unique(ncut_labels):
            histo = np.histogram(img[ncut_labels == l],
                                 bins=np.arange(0, 256, 5))[0].astype(np.float)
            histo = histo / np.sum(histo)
            histos[l] = histo

        hist_distances = {}
        for l, h in histos.iteritems():
            hist_distances[l] = np.percentile(
                [chi2(h, th) for th in border_histos],
                BORDER_DISSIMILARITY_PERCENTILE)
            # min is too sensitive if there is a blob at the border

        if FOREGROUND_DISSIMILARITY_THRESHOLD is None:
            # Automatically determine this value

            dist_vals = np.asarray(hist_distances.values())
            ticks = np.linspace(0, dist_vals.max(), 100)
            percentages = [
                np.count_nonzero(dist_vals < th) / float(len(dist_vals))
                for th in ticks
            ]

            def moving_average(interval, window_size):
                window = np.ones(int(window_size)) / float(window_size)
                return np.convolve(interval, window, 'same')

            grad = np.gradient(percentages, 3)
            # smoothed_grad = moving_average(grad, 1)
            hessian = np.gradient(grad, 3)

            # plt.figure();
            # plt.plot(ticks, grad, label='grad');
            # plt.plot(ticks, smoothed_grad, label='smoothed');
            # plt.legend();
            # plt.xlabel('Chi2 distance');
            # plt.savefig(os.path.join(submask_dir, '%(fn)s_spDissimCumDistGradient.png' % dict(fn=fn)));
            # plt.close();

            # plt.figure();
            # plt.plot(ticks, h);
            # plt.title('Hessian - minima is the plateau point of cum. distr.');
            # plt.xlabel('Dissimlarity threshold');
            # plt.savefig(os.path.join(submask_dir, '%(fn)s_spDissimCumDistHessian.png' % dict(fn=fn)));
            # plt.close();

            # ticks_sorted = ticks[np.argsort(grad, kind='mergesort')]
            # ticks_sorted = ticks[np.argsort(smoothed_grad, kind='mergesort')]
            ticks_sorted = ticks[10:][hessian[10:].argsort()]
            ticks_sorted_reduced = ticks_sorted[
                ticks_sorted < FOREGROUND_DISSIMILARITY_THRESHOLD_MAX]

            init_contour_percentages = np.asarray([
                np.sum([
                    np.count_nonzero(ncut_labels == l)
                    for l, d in hist_distances.iteritems() if d > th
                ]) / float(img.size) for th in ticks_sorted_reduced
            ])

            threshold_candidates = ticks_sorted_reduced[(init_contour_percentages < INIT_CONTOUR_COVERAGE_MAX) &\
                                                              (init_contour_percentages > 0)]
            # np.savetxt(os.path.join(submask_dir, '%(fn)s_spThreshCandidates.txt' % dict(fn=fn)), threshold_candidates, fmt='%.3f')
            print threshold_candidates[:10]
            foreground_dissimilarity_threshold = threshold_candidates[0]
        else:
            foreground_dissimilarity_threshold = FOREGROUND_DISSIMILARITY_THRESHOLD

        sys.stderr.write('FOREGROUND_DISSIMILARITY_THRESHOLD: %.2f\n' %
                         foreground_dissimilarity_threshold)

        # Visualize superpixel border distance map
        superpixel_border_distances = np.zeros_like(img, np.float)
        for l, s in hist_distances.iteritems():
            superpixel_border_distances[ncut_labels == l] = s

        # plt.figure();
        # im = plt.imshow(superpixel_border_distances, vmin=0, vmax=2);
        # plt.title('Superpixels distance to border');
        # plt.colorbar(im, fraction=0.025, pad=0.02);
        # plt.savefig(os.path.join(submask_dir, '%(fn)s_spBorderDissim.png' % dict(fn=fn)));
        # plt.close();

        # Generate mask for snake's initial contours.

        superpixel_mask = np.zeros_like(img, np.bool)
        for l, d in hist_distances.iteritems():
            if d > foreground_dissimilarity_threshold:
                superpixel_mask[ncut_labels == l] = 1

        superpixel_mask = remove_small_objects(superpixel_mask,
                                               min_size=MIN_SIZE)

        labelmap, n_submasks = label(superpixel_mask, return_num=True)

        # superpixel_submasks = []
        dilated_superpixel_submasks = []

        for i in range(1, n_submasks + 1):
            m = labelmap == i
            # superpixel_submasks.append(m)
            dilated_m = binary_dilation(m, disk(10))
            dilated_m = remove_small_objects(dilated_m, min_size=MIN_SIZE)
            dilated_superpixel_submasks.append(dilated_m)

        # Visualize

        # viz = img_as_ubyte(ncut_boundaries_viz)
        # for submask in superpixel_submasks:
        #     for cnt in find_contour_points(submask)[1]:
        #         cv2.polylines(viz, [cnt.astype(np.int)], True, (255,0,0), 1) # red
        # for submask in dilated_superpixel_submasks:
        #     for cnt in find_contour_points(submask)[1]:
        #         cv2.polylines(viz, [cnt.astype(np.int)], True, (0,0,255), 1) # blue
        # imsave(os.path.join(submask_dir, '%(fn)s_ncutSubmasks.png' % dict(fn=fn)), viz)

        #####################

        # Find contours from mask.
        init_contours = []
        for submask in dilated_superpixel_submasks:
            cnts = find_contour_points(submask.astype(np.int), sample_every=1)
            if 1 not in cnts or len(cnts[1]) == 0:
                continue
            for cnt in cnts[1]:
                if len(cnt) > INIT_CONTOUR_MINLEN:
                    init_contours.append(cnt)

        # assert len(init_contours) > 0, 'No contour is detected from entropy mask %s' % fn
        sys.stderr.write('Extracted %d contours from mask.\n' %
                         len(init_contours))

        # Create initial levelset
        init_levelsets = []
        for cnt in init_contours:
            init_levelset = np.zeros_like(img, np.float)
            init_levelset[contours_to_mask([cnt], img.shape[:2])] = 1.
            init_levelset[:10, :] = 0
            init_levelset[-10:, :] = 0
            init_levelset[:, :10] = 0
            init_levelset[:, -10:] = 0

            init_levelsets.append(init_levelset)

        #######################
        # Binary Thresholding #
        #######################

        img_enhanced = img

        #####################
        # Evolve morphsnake #
        #####################

        final_masks = []

        for init_levelset in init_levelsets:

            discard = False
            init_area = np.count_nonzero(init_levelset)

            t = time.time()

            msnake = morphsnakes.MorphACWE(img_enhanced.astype(np.float),
                                           smoothing=int(MORPHSNAKE_SMOOTHING),
                                           lambda1=MORPHSNAKE_LAMBDA1,
                                           lambda2=MORPHSNAKE_LAMBDA2)

            msnake.levelset = init_levelset.copy()

            dq = deque([None, None])
            for i in range(MORPHSNAKE_MAXITER):

                # At stable stage, the levelset (thus contour) will oscilate,
                # so instead of comparing to previous levelset, must compare to the one before the previous
                oneBefore_levelset = dq.popleft()

                # If less than 3 pixels are changed, stop.
                if i > MORPHSNAKE_MINITER:
                    if np.count_nonzero(msnake.levelset - oneBefore_levelset
                                        ) < PIXEL_CHANGE_TERMINATE_CRITERIA:
                        break

                area = np.count_nonzero(msnake.levelset)

                if area < MIN_SIZE:
                    discard = True
                    sys.stderr.write('Too small, stop iteration.\n')
                    break

                labeled_mask = label(msnake.levelset.astype(np.bool))
                for l in np.unique(labeled_mask):
                    if l != 0:
                        m = labeled_mask == l
                        if np.count_nonzero(m) / float(
                                init_area) > AREA_CHANGE_RATIO_MAX:
                            msnake.levelset[m] = 0
                            sys.stderr.write('Area nullified.\n')

                if np.count_nonzero(msnake.levelset) / float(
                        init_area) < AREA_CHANGE_RATIO_MIN:
                    discard = True
                    sys.stderr.write(
                        'Area shrinks too much, stop iteration.\n')
                    break

                dq.append(msnake.levelset)

                #         t = time.time()
                msnake.step()
        #         sys.stderr.write('Step: %f seconds\n' % (time.time()-t)) # 0.6 second / step

            sys.stderr.write('Snake finished at iteration %d.\n' % i)
            sys.stderr.write('Snake: %.2f seconds\n' %
                             (time.time() - t))  # 72s

            if discard:
                sys.stderr.write('Discarded.\n')
                continue
            else:
                # Handles the case that a single initial contour morphs into multiple contours
                labeled_mask = label(msnake.levelset.astype(np.bool))
                for l in np.unique(labeled_mask):
                    if l != 0:
                        m = labeled_mask == l
                        if np.count_nonzero(m) > MIN_SIZE:
                            final_masks.append(m)
                            sys.stderr.write('Final masks added.\n')

        ############
        ## Export ##
        ############

        # submask_viz_dir = create_if_not_exists('/home/yuncong/CSHL_data_processed/%(stack)s/%(stack)s_submask_overlayViz/%(fn)s' % dict(stack=stack, fn=fn))
        # execute_command('rm %s/*' % submask_viz_dir)

        # img_rgb = imread(os.path.join(input_dir, fn + '.' + tb_fmt))

        # all_init_cnts = []
        # for i, init_levelset in enumerate(init_levelsets):
        #     all_init_cnts += find_contour_points(init_levelset)[1]

        for i, mask in enumerate(final_masks):
            imsave(
                os.path.join(submask_dir, '%(fn)s_submask_%(i)d.png' % {
                    'fn': fn,
                    'i': i
                }), img_as_ubyte(mask))

            # viz = img_rgb.copy()
            #
            # cnts = find_contour_points(mask)
            # if len(cnts) == 0:
            #     raise
            # for cnt in cnts[1]:
            #     cv2.polylines(viz, [cnt.astype(np.int)], True, (255,0,0), 3) # red
            #
            # for cnt in all_init_cnts:
            #     cv2.polylines(viz, [cnt.astype(np.int)], True, (0,255,0), 3) # green

            # viz_fn = os.path.join(submask_dir, '%(fn)s_submask_%(i)d_overlayViz.png' % dict(fn=fn, i=i+1))
            # sys.stderr.write(viz_fn + '\n')
            # imsave(viz_fn, viz)

        ################################################
        # Automatically judge the goodness of each mask
        ################################################

        submasks = final_masks

        n = len(final_masks)

        rank1 = np.argsort([np.count_nonzero(m) for m in submasks])[::-1]

        image_center = np.r_[submasks[0].shape[1] / 2,
                             submasks[0].shape[0] / 2]

        bbox_to_image_center_distance = []
        for m in submasks:
            xmin, xmax, ymin, ymax = bbox_2d(m)
            dist = np.sqrt(
                np.sum((image_center - ((xmin + xmax) / 2,
                                        (ymin + ymax) / 2))**2))
            bbox_to_image_center_distance.append(dist)

        rank2 = np.argsort(bbox_to_image_center_distance)

        r1 = np.asarray(
            [r for r, i in sorted(enumerate(rank1), key=lambda (r, i): i)])
        r2 = np.asarray(
            [r for r, i in sorted(enumerate(rank2), key=lambda (r, i): i)])
        rank = np.argsort(
            r1 +
            1.01 * r2)  # weight being close to center a bit more to break tie
        best_mask_ind = rank[0]

        decisions = [False for _ in range(n)]
        decisions[best_mask_ind] = True

        np.savetxt(os.path.join(submask_dir,
                                '%(fn)s_submasksAlgReview.txt' % dict(fn=fn)),
                   decisions,
                   fmt='%d')

    except Exception as e:
        sys.stderr.write('%s\n' % e)
        sys.stderr.write('Mask error: %s\n' % fn)
        return
        sys.stderr.write('Too few voxels %d.\n' % nz)
        continue

    init_cnt_xmin, init_cnt_xmax, init_cnt_ymin, init_cnt_ymax = bbox_2d(vol[..., z])

    margin = 20
    crop_xmin = init_cnt_xmin - margin
    crop_ymin = init_cnt_ymin - margin
    crop_xmax = init_cnt_xmax + margin
    crop_ymax = init_cnt_ymax + margin
    print crop_xmin, crop_xmax, crop_ymin, crop_ymax
    # score volume resol. : thumbnail resol

    init_mask = vol[crop_ymin:crop_ymax+1, crop_xmin:crop_xmax+1, z] > .3

    contours = find_contour_points(init_mask, sample_every=1)[1]
    assert len(contours) == 1
    init_contour = contours[0]

    # Crop around initial contour.

    img = imread(DataManager.get_image_filepath(stack=stack, section=sec))
    # lossless resol.

    img_cropped = img[crop_ymin*32:crop_ymax*32, crop_xmin*32:crop_xmax*32]
    im_cropped_h, im_cropped_w = img_cropped.shape[:2]
    print im_cropped_w, im_cropped_h

    # plt.figure(figsize=(20,20));
    # plt.imshow(img_cropped)
    # plt.title('Image cropped');
Esempio n. 12
0
    img_fn = DataManager.get_image_filepath(stack=stack_fixed, section=sec, resol='thumbnail', version='cropped_tif')
    img = imread(img_fn)

    viz = img.copy()

    z = voxel_z_size * (sec - 1) - zmin_vol_f

    # Find fixed volume annotation contours
#     contours_f_on_volume = find_contour_points(volume_fixed[..., int(z)])
#     contours_f_on_cropped = {i: [cnt + (xmin_vol_f, ymin_vol_f) for cnt in cnts] for i, cnts in contours_f_on_volume.iteritems()}

    # Find moving volume annotation contours

    for stack, volume_m_aligned_to_f in annotation_volumes_volume_m_aligned_to_f.iteritems():
        contours_m_alignedTo_f_on_volume = find_contour_points(volume_m_aligned_to_f[..., int(z)])
        contours_m_alignedTo_f_on_cropped = {i: [cnt + (xmin_vol_f, ymin_vol_f) for cnt in cnts]
                                             for i, cnts in contours_m_alignedTo_f_on_volume.iteritems()}

    #     # Draw fixed volume annotation contours
    #     for ind_f, cnts_f in contours_f_on_cropped.iteritems():
    #         for cnt_f in cnts_f:
    #             cv2.polylines(viz, [cnt_f.astype(np.int)], True, (0,255,0), 2)

        # Draw moving volume annotation contours
        for ind_m, cnts_m in contours_m_alignedTo_f_on_cropped.iteritems():
            for cnt_m in cnts_m:
                cv2.polylines(viz, [cnt_m.astype(np.int)], True, stack_colors[stack], 2)

    viz_fn = os.path.join(viz_dir, '%(stack_moving)s_to_%(stack_fixed)s_%(sec)04d.jpg' % \
          {'stack_moving': stack_moving, 'stack_fixed': stack_fixed, 'sec': sec})
def snake(img, init_submasks=None, init_contours=None, lambda1=MORPHSNAKE_LAMBDA1, return_masks=True, min_size=MIN_SIZE):

    # Find contours from mask.
    if init_contours is None:
        init_contours = []
        for submask in init_submasks:
            cnts = find_contour_points(submask.astype(np.int), sample_every=1)
            if 1 not in cnts or len(cnts[1]) == 0:
                continue
            for cnt in cnts[1]:
                if len(cnt) > INIT_CONTOUR_MINLEN:
                    init_contours.append(cnt)
    else:
        init_contours = [c.astype(np.int) for c in init_contours]

    xmin, ymin = np.min([np.min(c, axis=0) for c in init_contours], axis=0)
    xmax, ymax = np.max([np.max(c, axis=0) for c in init_contours], axis=0)
    margin = 50
    crop_xmin = max(0, xmin - margin)
    crop_ymin = max(0, ymin - margin)
    crop_xmax = min(img.shape[1], xmax + margin)
    crop_ymax = min(img.shape[0], ymax + margin)
    cropped_img = img[crop_ymin:crop_ymax+1, crop_xmin:crop_xmax+1]
    # cropped_img_height, cropped_img_width = cropped_img.shape[:2]
    init_contours_on_cropped_img = [c-(crop_xmin, crop_ymin) for c in init_contours]

    # init_contours = [xys for submask in submasks
    #                  for xys in find_contour_points(submask.astype(np.int), sample_every=1)[1]
    #                  if len(xys) > INIT_CONTOUR_MINLEN]
    sys.stderr.write('Extracted %d contours from mask.\n' % len(init_contours))

    # Create initial levelset
    init_levelsets = []
    for cnt in init_contours_on_cropped_img:
        init_levelset = np.zeros_like(cropped_img, np.float)

        t = time.time()
        init_levelset[contours_to_mask([cnt], cropped_img.shape[:2])] = 1.
        sys.stderr.write('Contour to levelset: %.2f seconds\n' % (time.time() - t)) # 10s

        init_levelset[:10, :] = 0
        init_levelset[-10:, :] = 0
        init_levelset[:, :10] = 0
        init_levelset[:, -10:] = 0
        init_levelsets.append(init_levelset)

    # img_enhanced = img.copy()

    #####################
    # Evolve morphsnake #
    #####################

    final_masks = []

    for levelset_ind, init_levelset in enumerate(init_levelsets):

        sys.stderr.write('\nContour %d\n' % levelset_ind)

        discard = False
        init_area = np.count_nonzero(init_levelset)

        t = time.time()

        msnake = morphsnakes.MorphACWE(cropped_img.astype(np.float), smoothing=int(MORPHSNAKE_SMOOTHING),
                                       lambda1=lambda1, lambda2=MORPHSNAKE_LAMBDA2)

        msnake.levelset = init_levelset.copy()

        dq = deque([None, None])
        for i in range(MORPHSNAKE_MAXITER):
            # At stable stage, the levelset (thus contour) will oscilate,
            # so instead of comparing to previous levelset, must compare to the one before the previous
            oneBefore_levelset = dq.popleft()

            # If less than 3 pixels are changed, stop.
            if i > MORPHSNAKE_MINITER:
                if np.count_nonzero(msnake.levelset - oneBefore_levelset) < PIXEL_CHANGE_TERMINATE_CRITERIA:
                    break

            area = np.count_nonzero(msnake.levelset)

            if area < min_size:
                discard = True
                sys.stderr.write('Too small, stop iteration.\n')
                break

            # If area changes more than 2, stop.
            labeled_mask = label(msnake.levelset.astype(np.bool))
            for l in np.unique(labeled_mask):
                if l != 0:
                    m = labeled_mask == l
                    if np.count_nonzero(m)/float(init_area) > AREA_CHANGE_RATIO_MAX:
                        msnake.levelset[m] = 0
                        sys.stderr.write('Area expands too much - nullified.\n')

            if  np.count_nonzero(msnake.levelset)/float(init_area) < AREA_CHANGE_RATIO_MIN:
                discard = True
                sys.stderr.write('Area shrinks too much, stop iteration.\n')
                break

            dq.append(msnake.levelset)

    #         t = time.time()
            msnake.step()
    #         sys.stderr.write('Step: %f seconds\n' % (time.time()-t)) # 0.6 second/step, roughly 200 steps takes 120s

        sys.stderr.write('Snake finished at iteration %d.\n' % i)
        sys.stderr.write('Snake: %.2f seconds\n' % (time.time()-t)) # 72s

        if discard:
            sys.stderr.write('Discarded.\n')
            continue
        else:
            # Handles the case that a single initial contour morphs into multiple contours
            labeled_mask = label(msnake.levelset.astype(np.bool))
            for l in np.unique(labeled_mask):
                if l != 0:
                    m = labeled_mask == l
                    if np.count_nonzero(m) > min_size:
                        final_masks.append(m)
                        sys.stderr.write('Final masks added.\n')

    if len(final_masks) == 0:
        sys.stderr.write('Snake return no valid submasks.\n')

    if return_masks:
        final_masks_uncropped = []
        for m in final_masks:
            uncropped_mask = np.zeros(img.shape[:2], np.bool)
            uncropped_mask[crop_ymin:crop_ymax+1, crop_xmin:crop_xmax+1] = m
            final_masks_uncropped.append(uncropped_mask)
        return final_masks_uncropped
    else:
        final_contours = []
        for m in final_masks:
            cnts = [cnt_on_cropped + (crop_xmin, crop_ymin) for cnt_on_cropped in find_contour_points(m)[1]]
            final_contours += cnts
        return final_contours
def generate_mask(fn, tb_fmt='png'):

    try:
        submask_dir = create_if_not_exists(os.path.join(output_dir, fn))
        execute_command('rm -f %s/*' % submask_dir)

        img_rgb = imread(os.path.join(input_dir, '%(fn)s.%(tb_fmt)s' % dict(fn=fn, tb_fmt=tb_fmt)))

        stds = []
        images = []

        # for c in range(3):
        for c in [0]:

            img = img_rgb[..., c].copy()

            border = np.median(np.r_[img[:10, :].flatten(),
                                     img[-10:, :].flatten(),
                                     img[:, :10].flatten(),
                                     img[:, -10:].flatten()])

            if border < 123:
                # dark background, fluorescent
                img = img.max() - img # invert, make tissue dark on bright background

            # Stretch contrast
            img_flattened = img.flatten()

            vmax_perc = VMAX_PERCENTILE
            while vmax_perc > 80:
                vmax = np.percentile(img_flattened, vmax_perc)
                if vmax < 255:
                    break
                else:
                    vmax_perc -= 1

            vmin_perc = VMIN_PERCENTILE
            while vmin_perc < 20:
                vmin = np.percentile(img_flattened, vmin_perc)
                if vmin > 0:
                    break
                else:
                    vmin_perc += 1

            sys.stderr.write('%d(%d percentile), %d(%d percentile)\n' % (vmin, vmin_perc, vmax, vmax_perc) )

            img = img_as_ubyte(rescale_intensity(img, in_range=(vmin, vmax)))

            # img[(img <= vmax) & (img >= vmin)] = 255./(vmax-vmin)*(img[(img <= vmax) & (img >= vmin)]-vmin)
            # img[img > vmax] = 255
            # img[img < vmin] = 0
            # img = img.astype(np.uint8)

            images.append(img)

            std = np.std(img)
            stds.append(std)

            sys.stderr.write('std: %.2f\n' % (std))

        best_channel_id = np.argmax(stds)
        sys.stderr.write('Use channel %s.\n' % ['RED', 'GREEN', 'BLUE'][best_channel_id])
        img = images[best_channel_id]

        #############################
        ## Graph cut based method. ##
        #############################

        # Input to slic() must be float
        slic_labels = slic(img.astype(np.float), sigma=SLIC_SIGMA, compactness=SLIC_COMPACTNESS, n_segments=SLIC_N_SEGMENTS,
                   multichannel=False, max_iter=SLIC_MAXITER)

        # sim_graph = rag_mean_color(img, slic_labels, mode='similarity', sigma=SUPERPIXEL_SIMILARITY_SIGMA)

        # Normalized cut - merge superpixels.

        # for _ in range(3):
        #     try:
        #         t = time.time()
        #         ncut_labels = cut_normalized(slic_labels, sim_graph, in_place=False, thresh=SUPERPIXEL_MERGE_SIMILARITY_THRESH,
        #                                     num_cuts=GRAPHCUT_NUM_CUTS)
        #         sys.stderr.write('Normalized Cut: %.2f seconds.\n' % (time.time() - t))
        #         break
        #     except ArpackError as e:
        #         sys.stderr.write('ArpackError encountered.\n')
        #         continue

        # ncut_boundaries_viz = mark_boundaries(img, label_img=ncut_labels, background_label=-1)
        # imsave(os.path.join(submask_dir, '%(fn)s_.png' % dict(fn=fn)), ncut_boundaries_viz)

        ncut_labels = slic_labels

        # Find background superpixels.

        background_labels = np.unique(np.concatenate([ncut_labels[:,0], ncut_labels[:,-1], ncut_labels[0,:], ncut_labels[-1,:]]))

        # Collect training superpixels.

        border_histos = []
        for b in background_labels:
            histo = np.histogram(img[ncut_labels == b], bins=np.arange(0,256,5))[0].astype(np.float)
            histo = histo/np.sum(histo)
            border_histos.append(histo)

        histos = {}
        for l in np.unique(ncut_labels):
            histo = np.histogram(img[ncut_labels == l], bins=np.arange(0,256,5))[0].astype(np.float)
            histo = histo/np.sum(histo)
            histos[l] = histo

        hist_distances = {}
        for l, h in histos.iteritems():
            hist_distances[l] = np.percentile([chi2(h, th) for th in border_histos], BORDER_DISSIMILARITY_PERCENTILE)
            # min is too sensitive if there is a blob at the border

        if FOREGROUND_DISSIMILARITY_THRESHOLD is None:
            # Automatically determine this value

            dist_vals = np.asarray(hist_distances.values())
            ticks = np.linspace(0, dist_vals.max(), 100)
            percentages = [np.count_nonzero(dist_vals < th) / float(len(dist_vals)) for th in ticks]

            def moving_average(interval, window_size):
                window = np.ones(int(window_size))/float(window_size)
                return np.convolve(interval, window, 'same')

            grad = np.gradient(percentages, 3)
            # smoothed_grad = moving_average(grad, 1)
            hessian = np.gradient(grad, 3)

            # plt.figure();
            # plt.plot(ticks, grad, label='grad');
            # plt.plot(ticks, smoothed_grad, label='smoothed');
            # plt.legend();
            # plt.xlabel('Chi2 distance');
            # plt.savefig(os.path.join(submask_dir, '%(fn)s_spDissimCumDistGradient.png' % dict(fn=fn)));
            # plt.close();

            # plt.figure();
            # plt.plot(ticks, h);
            # plt.title('Hessian - minima is the plateau point of cum. distr.');
            # plt.xlabel('Dissimlarity threshold');
            # plt.savefig(os.path.join(submask_dir, '%(fn)s_spDissimCumDistHessian.png' % dict(fn=fn)));
            # plt.close();

            # ticks_sorted = ticks[np.argsort(grad, kind='mergesort')]
            # ticks_sorted = ticks[np.argsort(smoothed_grad, kind='mergesort')]
            ticks_sorted = ticks[10:][hessian[10:].argsort()]
            ticks_sorted_reduced = ticks_sorted[ticks_sorted < FOREGROUND_DISSIMILARITY_THRESHOLD_MAX]

            init_contour_percentages = np.asarray([np.sum([np.count_nonzero(ncut_labels == l)
                                                           for l, d in hist_distances.iteritems()
                                                           if d > th]) / float(img.size)
                                                   for th in ticks_sorted_reduced])

            threshold_candidates = ticks_sorted_reduced[(init_contour_percentages < INIT_CONTOUR_COVERAGE_MAX) &\
                                                              (init_contour_percentages > 0)]
            # np.savetxt(os.path.join(submask_dir, '%(fn)s_spThreshCandidates.txt' % dict(fn=fn)), threshold_candidates, fmt='%.3f')
            print threshold_candidates[:10]
            foreground_dissimilarity_threshold = threshold_candidates[0]
        else:
            foreground_dissimilarity_threshold = FOREGROUND_DISSIMILARITY_THRESHOLD

        sys.stderr.write('FOREGROUND_DISSIMILARITY_THRESHOLD: %.2f\n' % foreground_dissimilarity_threshold)

        # Visualize superpixel border distance map
        superpixel_border_distances = np.zeros_like(img, np.float)
        for l, s in hist_distances.iteritems():
            superpixel_border_distances[ncut_labels == l] = s

        # plt.figure();
        # im = plt.imshow(superpixel_border_distances, vmin=0, vmax=2);
        # plt.title('Superpixels distance to border');
        # plt.colorbar(im, fraction=0.025, pad=0.02);
        # plt.savefig(os.path.join(submask_dir, '%(fn)s_spBorderDissim.png' % dict(fn=fn)));
        # plt.close();

        # Generate mask for snake's initial contours.

        superpixel_mask = np.zeros_like(img, np.bool)
        for l, d in hist_distances.iteritems():
            if d > foreground_dissimilarity_threshold:
                superpixel_mask[ncut_labels == l] = 1

        superpixel_mask = remove_small_objects(superpixel_mask, min_size=MIN_SIZE)

        labelmap, n_submasks = label(superpixel_mask, return_num=True)

        # superpixel_submasks = []
        dilated_superpixel_submasks = []

        for i in range(1, n_submasks+1):
            m = labelmap == i
            # superpixel_submasks.append(m)
            dilated_m = binary_dilation(m, disk(10))
            dilated_m = remove_small_objects(dilated_m, min_size=MIN_SIZE)
            dilated_superpixel_submasks.append(dilated_m)

        # Visualize

        # viz = img_as_ubyte(ncut_boundaries_viz)
        # for submask in superpixel_submasks:
        #     for cnt in find_contour_points(submask)[1]:
        #         cv2.polylines(viz, [cnt.astype(np.int)], True, (255,0,0), 1) # red
        # for submask in dilated_superpixel_submasks:
        #     for cnt in find_contour_points(submask)[1]:
        #         cv2.polylines(viz, [cnt.astype(np.int)], True, (0,0,255), 1) # blue
        # imsave(os.path.join(submask_dir, '%(fn)s_ncutSubmasks.png' % dict(fn=fn)), viz)

        #####################

        # Find contours from mask.
        init_contours = []
        for submask in dilated_superpixel_submasks:
            cnts = find_contour_points(submask.astype(np.int), sample_every=1)
            if 1 not in cnts or len(cnts[1]) == 0:
                continue
            for cnt in cnts[1]:
                if len(cnt) > INIT_CONTOUR_MINLEN:
                    init_contours.append(cnt)

        # assert len(init_contours) > 0, 'No contour is detected from entropy mask %s' % fn
        sys.stderr.write('Extracted %d contours from mask.\n' % len(init_contours))

        # Create initial levelset
        init_levelsets = []
        for cnt in init_contours:
            init_levelset = np.zeros_like(img, np.float)
            init_levelset[contours_to_mask([cnt], img.shape[:2])] = 1.
            init_levelset[:10, :] = 0
            init_levelset[-10:, :] = 0
            init_levelset[:, :10] = 0
            init_levelset[:, -10:] = 0

            init_levelsets.append(init_levelset)

        #######################
        # Binary Thresholding #
        #######################

        img_enhanced = img

        #####################
        # Evolve morphsnake #
        #####################

        final_masks = []

        for init_levelset in init_levelsets:

            discard = False
            init_area = np.count_nonzero(init_levelset)

            t = time.time()

            msnake = morphsnakes.MorphACWE(img_enhanced.astype(np.float), smoothing=int(MORPHSNAKE_SMOOTHING),
                                           lambda1=MORPHSNAKE_LAMBDA1, lambda2=MORPHSNAKE_LAMBDA2)

            msnake.levelset = init_levelset.copy()

            dq = deque([None, None])
            for i in range(MORPHSNAKE_MAXITER):

                # At stable stage, the levelset (thus contour) will oscilate,
                # so instead of comparing to previous levelset, must compare to the one before the previous
                oneBefore_levelset = dq.popleft()

                # If less than 3 pixels are changed, stop.
                if i > MORPHSNAKE_MINITER:
                    if np.count_nonzero(msnake.levelset - oneBefore_levelset) < PIXEL_CHANGE_TERMINATE_CRITERIA:
                        break

                area = np.count_nonzero(msnake.levelset)

                if area < MIN_SIZE:
                    discard = True
                    sys.stderr.write('Too small, stop iteration.\n')
                    break

                labeled_mask = label(msnake.levelset.astype(np.bool))
                for l in np.unique(labeled_mask):
                    if l != 0:
                        m = labeled_mask == l
                        if np.count_nonzero(m)/float(init_area) > AREA_CHANGE_RATIO_MAX:
                            msnake.levelset[m] = 0
                            sys.stderr.write('Area nullified.\n')

                if  np.count_nonzero(msnake.levelset)/float(init_area) < AREA_CHANGE_RATIO_MIN:
                    discard = True
                    sys.stderr.write('Area shrinks too much, stop iteration.\n')
                    break

                dq.append(msnake.levelset)

        #         t = time.time()
                msnake.step()
        #         sys.stderr.write('Step: %f seconds\n' % (time.time()-t)) # 0.6 second / step

            sys.stderr.write('Snake finished at iteration %d.\n' % i)
            sys.stderr.write('Snake: %.2f seconds\n' % (time.time()-t)) # 72s

            if discard:
                sys.stderr.write('Discarded.\n')
                continue
            else:
                # Handles the case that a single initial contour morphs into multiple contours
                labeled_mask = label(msnake.levelset.astype(np.bool))
                for l in np.unique(labeled_mask):
                    if l != 0:
                        m = labeled_mask == l
                        if np.count_nonzero(m) > MIN_SIZE:
                            final_masks.append(m)
                            sys.stderr.write('Final masks added.\n')

        ############
        ## Export ##
        ############

        # submask_viz_dir = create_if_not_exists('/home/yuncong/CSHL_data_processed/%(stack)s/%(stack)s_submask_overlayViz/%(fn)s' % dict(stack=stack, fn=fn))
        # execute_command('rm %s/*' % submask_viz_dir)

        # img_rgb = imread(os.path.join(input_dir, fn + '.' + tb_fmt))

        # all_init_cnts = []
        # for i, init_levelset in enumerate(init_levelsets):
        #     all_init_cnts += find_contour_points(init_levelset)[1]

        for i, mask in enumerate(final_masks):
            imsave(os.path.join(submask_dir,'%(fn)s_submask_%(i)d.png' % {'fn': fn, 'i':i}), img_as_ubyte(mask))

            # viz = img_rgb.copy()
            #
            # cnts = find_contour_points(mask)
            # if len(cnts) == 0:
            #     raise
            # for cnt in cnts[1]:
            #     cv2.polylines(viz, [cnt.astype(np.int)], True, (255,0,0), 3) # red
            #
            # for cnt in all_init_cnts:
            #     cv2.polylines(viz, [cnt.astype(np.int)], True, (0,255,0), 3) # green

            # viz_fn = os.path.join(submask_dir, '%(fn)s_submask_%(i)d_overlayViz.png' % dict(fn=fn, i=i+1))
            # sys.stderr.write(viz_fn + '\n')
            # imsave(viz_fn, viz)

        ################################################
        # Automatically judge the goodness of each mask
        ################################################

        submasks = final_masks

        n = len(final_masks)

        rank1 = np.argsort([np.count_nonzero(m) for m in submasks])[::-1]

        image_center = np.r_[submasks[0].shape[1]/2, submasks[0].shape[0]/2]

        bbox_to_image_center_distance = []
        for m in submasks:
            xmin, xmax, ymin, ymax = bbox_2d(m)
            dist = np.sqrt(np.sum((image_center - ((xmin + xmax)/2, (ymin+ymax)/2))**2))
            bbox_to_image_center_distance.append(dist)

        rank2 = np.argsort(bbox_to_image_center_distance)

        r1 = np.asarray([r for r, i in sorted(enumerate(rank1), key=lambda (r,i): i)])