def prune_contours(self, area_thresh=1000.0, dist_thresh=20,
                       preserve_topology=True):
        """Removes all white connected components with area less than area_thresh.
        Parameters
        ----------
        area_thresh : float
            The minimum area for which a white connected component will not be
            zeroed out.
        dist_thresh : int
            If a connected component is within dist_thresh of the top of the
            image, it will not be pruned out, regardless of its area.
        Returns
        -------
        :obj:`BinaryImage`
            The new pruned binary image.
        """
        # get all contours (connected components) from the binary image
        contours, hierarchy = cv2.findContours(
            self.data.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
        num_contours = len(contours)
        middle_pixel = np.array(self.shape)[:2] / 2
        middle_pixel = middle_pixel.reshape(1, 2)
        center_contour = None
        pruned_contours = []

        # find which contours need to be pruned
        for i in range(num_contours):
            area = cv2.contourArea(contours[i])
            if area > area_thresh:
                # check close to origin
                fill = np.zeros([self.height, self.width, 3])
                cv2.fillPoly(
                    fill,
                    pts=[
                        contours[i]],
                    color=(
                        BINARY_IM_MAX_VAL,
                        BINARY_IM_MAX_VAL,
                        BINARY_IM_MAX_VAL))
                nonzero_px = np.where(fill > 0)
                nonzero_px = np.c_[nonzero_px[0], nonzero_px[1]]
                dists = ssd.cdist(middle_pixel, nonzero_px)
                min_dist = np.min(dists)
                pruned_contours.append((contours[i], min_dist))

        if len(pruned_contours) == 0:
            return None

        pruned_contours.sort(key=lambda x: x[1])

        # keep all contours within some distance of the top
        num_contours = len(pruned_contours)
        keep_indices = [0]
        source_coords = pruned_contours[0][0].squeeze().astype(np.float32)
        for i in range(1, num_contours):
            target_coords = pruned_contours[i][0].squeeze().astype(np.float32)
            dists = ssd.cdist(source_coords, target_coords)
            min_dist = np.min(dists)
            if min_dist < dist_thresh:
                keep_indices.append(i)

        # keep the top num_areas pruned contours
        keep_indices = np.unique(keep_indices)
        pruned_contours = [pruned_contours[i][0] for i in keep_indices]

        # mask out bad areas in the image
        pruned_data = np.zeros([self.height, self.width, 3])
        for contour in pruned_contours:
            cv2.fillPoly(
                pruned_data,
                pts=[contour],
                color=(
                    BINARY_IM_MAX_VAL,
                    BINARY_IM_MAX_VAL,
                    BINARY_IM_MAX_VAL))
        pruned_data = pruned_data[:, :, 0]  # convert back to one channel

        # preserve topology of original image
        if preserve_topology:
            orig_zeros = np.where(self.data == 0)
            pruned_data[orig_zeros[0], orig_zeros[1]] = 0
        return BinaryImage(pruned_data.astype(np.uint8), self._frame)