docs: def fill_labeled_holes(labels, mask=None, size_fn = None):
      '''Fill all background pixels that are holes inside the foreground
   
      A pixel is a hole inside a foreground object if
      
      * there is no path from the pixel to the edge AND
      
      * there is no path from the pixel to any other non-hole
        pixel AND
        
      * there is no path from the pixel to two similarly-labeled pixels that
        are adjacent to two differently labeled non-hole pixels.
      
      labels - the current labeling
      
      mask - mask of pixels to ignore
      
      size_fn - if not None, it is a function that takes a size and a boolean
                indicating whether it is foreground (True) or background (False)
                The function should return True to analyze and False to ignore
      
      returns a filled copy of the labels matrix
      '''
      #
      # The algorithm:
      #
      # Label the background to get distinct background objects
      # Construct a graph of both foreground and background objects.
      # Walk the graph according to the rules.
      #
      labels_type = labels.dtype
      background = labels == 0
      if mask is not None:
          background &= mask
      
      blabels, count = scind.label(background, four_connect)
      labels = labels.copy().astype(int)
      lcount = np.max(labels)
      labels[blabels != 0] = blabels[blabels != 0] + lcount + 1
      lmax = lcount + count + 1
      is_not_hole = np.ascontiguousarray(np.zeros(lmax + 1, np.uint8))
      #
      # Find the indexes on the edge and use to populate the to-do list
      #
      to_do = np.unique(np.hstack((
          labels[0, :], labels[:, 0], labels[-1, :], labels[:, -1])))
      to_do = to_do[to_do != 0]
      is_not_hole[to_do] = True
      to_do = list(to_do)
      #
      # An array that names the first non-hole object
      #
      adjacent_non_hole = np.ascontiguousarray(np.zeros(lmax + 1), np.uint32)
      #
      # Find all 4-connected adjacent pixels
      # Note that there will be some i, j not in j, i
      #
      i = np.hstack([labels[:-1, :].flatten(), labels[:, :-1].flatten()])
      j = np.hstack([labels[1:, :].flatten(), labels[:, 1:].flatten()])
      i, j = i[i!=j], j[i!=j]
      if (len(i)) > 0:
          order = np.lexsort((j, i))
          i = i[order]
          j = j[order]
          # Remove duplicates and stack to guarantee that j, i is in i, j
          first = np.hstack([[True], (i[:-1] != i[1:]) | (j[:-1] != j[1:])])
          i, j = np.hstack((i[first], j[first])), np.hstack((j[first], i[first]))
          # Remove dupes again. (much shorter)
          order = np.lexsort((j, i))
          i = i[order]
          j = j[order]
          first = np.hstack([[True], (i[:-1] != i[1:]) | (j[:-1] != j[1:])])
          i, j = i[first], j[first]
          #
          # Now we make a ragged array of i and j
          #
          i_count = np.bincount(i)
          if len(i_count) < lmax + 1:
              i_count = np.hstack((i_count, np.zeros(lmax + 1 - len(i_count), int)))
          indexer = Indexes([i_count])
          #
          # Filter using the size function passed, if any
          #
          if size_fn is not None:
              areas = np.bincount(labels.flatten())
              for ii, area in enumerate(areas):
                  if (ii > 0 and area > 0 and not is_not_hole[ii] and 
                      not size_fn(area, ii <= lcount)):
                      is_not_hole[ii] = True
                      to_do.append(ii)
  
          to_do_count = len(to_do)
          if len(to_do) < len(is_not_hole):
              to_do += [ 0 ] * (len(is_not_hole) - len(to_do))
          to_do = np.ascontiguousarray(np.array(to_do), np.uint32)
          fill_labeled_holes_loop(
              np.ascontiguousarray(i, np.uint32),
              np.ascontiguousarray(j, np.uint32),
              np.ascontiguousarray(indexer.fwd_idx, np.uint32),
              np.ascontiguousarray(i_count, np.uint32),
              is_not_hole, adjacent_non_hole, to_do, lcount, to_do_count)
      #
      # Make an array that assigns objects to themselves and background to 0
      #
      new_indexes = np.arange(len(is_not_hole)).astype(np.uint32)
      new_indexes[(lcount+1):] = 0
      #
      # Fill the holes by replacing the old value by the value of the
      # enclosing object.
      #
      is_not_hole = is_not_hole.astype(bool)
      new_indexes[~is_not_hole] = adjacent_non_hole[~ is_not_hole]
      if mask is not None:
          labels[mask] = new_indexes[labels[mask]]
      else:
          labels = new_indexes[labels]
      return labels.astype(labels_type)