| """ |
| Copyright © 2025 Howard Hughes Medical Institute, Authored by Carsen Stringer , Michael Rariden and Marius Pachitariu. |
| """ |
| import logging |
| import io |
| from tqdm import tqdm, trange |
| import cv2 |
| from scipy.ndimage import find_objects |
| import numpy as np |
| import fastremap |
| import fill_voids |
| from models.seg_post_model import metrics |
|
|
|
|
| class TqdmToLogger(io.StringIO): |
| """ |
| Output stream for TQDM which will output to logger module instead of |
| the StdOut. |
| """ |
| logger = None |
| level = None |
| buf = "" |
|
|
| def __init__(self, logger, level=None): |
| super(TqdmToLogger, self).__init__() |
| self.logger = logger |
| self.level = level or logging.INFO |
|
|
| def write(self, buf): |
| self.buf = buf.strip("\r\n\t ") |
|
|
| def flush(self): |
| self.logger.log(self.level, self.buf) |
|
|
|
|
|
|
| |
| |
|
|
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| def stitch3D(masks, stitch_threshold=0.25): |
| """ |
| Stitch 2D masks into a 3D volume using a stitch_threshold on IOU. |
| |
| Args: |
| masks (list or ndarray): List of 2D masks. |
| stitch_threshold (float, optional): Threshold value for stitching. Defaults to 0.25. |
| |
| Returns: |
| list: List of stitched 3D masks. |
| """ |
| mmax = masks[0].max() |
| empty = 0 |
| for i in trange(len(masks) - 1): |
| iou = metrics._intersection_over_union(masks[i + 1], masks[i])[1:, 1:] |
| if not iou.size and empty == 0: |
| masks[i + 1] = masks[i + 1] |
| mmax = masks[i + 1].max() |
| elif not iou.size and not empty == 0: |
| icount = masks[i + 1].max() |
| istitch = np.arange(mmax + 1, mmax + icount + 1, 1, masks.dtype) |
| mmax += icount |
| istitch = np.append(np.array(0), istitch) |
| masks[i + 1] = istitch[masks[i + 1]] |
| else: |
| iou[iou < stitch_threshold] = 0.0 |
| iou[iou < iou.max(axis=0)] = 0.0 |
| istitch = iou.argmax(axis=1) + 1 |
| ino = np.nonzero(iou.max(axis=1) == 0.0)[0] |
| istitch[ino] = np.arange(mmax + 1, mmax + len(ino) + 1, 1, masks.dtype) |
| mmax += len(ino) |
| istitch = np.append(np.array(0), istitch) |
| masks[i + 1] = istitch[masks[i + 1]] |
| empty = 1 |
|
|
| return masks |
|
|
|
|
| |
| |
| |
|
|
| |
| |
|
|
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| |
| |
| |
|
|
| |
| |
| |
|
|
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| |
| |
| |
|
|
| |
| |
|
|
| |
| |
| |
| |
| |
|
|
|
|
| def fill_holes_and_remove_small_masks(masks, min_size=15): |
| """ Fills holes in masks (2D/3D) and discards masks smaller than min_size. |
| |
| This function fills holes in each mask using fill_voids.fill. |
| It also removes masks that are smaller than the specified min_size. |
| |
| Parameters: |
| masks (ndarray): Int, 2D or 3D array of labelled masks. |
| 0 represents no mask, while positive integers represent mask labels. |
| The size can be [Ly x Lx] or [Lz x Ly x Lx]. |
| min_size (int, optional): Minimum number of pixels per mask. |
| Masks smaller than min_size will be removed. |
| Set to -1 to turn off this functionality. Default is 15. |
| |
| Returns: |
| ndarray: Int, 2D or 3D array of masks with holes filled and small masks removed. |
| 0 represents no mask, while positive integers represent mask labels. |
| The size is [Ly x Lx] or [Lz x Ly x Lx]. |
| """ |
|
|
| if masks.ndim > 3 or masks.ndim < 2: |
| raise ValueError("masks_to_outlines takes 2D or 3D array, not %dD array" % |
| masks.ndim) |
|
|
| |
| if min_size > 0: |
| counts = fastremap.unique(masks, return_counts=True)[1][1:] |
| masks = fastremap.mask(masks, np.nonzero(counts < min_size)[0] + 1) |
| fastremap.renumber(masks, in_place=True) |
| |
| slices = find_objects(masks) |
| j = 0 |
| for i, slc in enumerate(slices): |
| if slc is not None: |
| msk = masks[slc] == (i + 1) |
| msk = fill_voids.fill(msk) |
| masks[slc][msk] = (j + 1) |
| j += 1 |
|
|
| if min_size > 0: |
| counts = fastremap.unique(masks, return_counts=True)[1][1:] |
| masks = fastremap.mask(masks, np.nonzero(counts < min_size)[0] + 1) |
| fastremap.renumber(masks, in_place=True) |
| |
| return masks |
|
|