|
58 | 58 | np_unique, |
59 | 59 | np_unique_withoutzero, |
60 | 60 | np_volume, |
| 61 | + np_voronoi_labels, |
61 | 62 | ) |
62 | 63 | from TPTBox.core.vert_constants import ( |
63 | 64 | AFFINE, |
@@ -1808,6 +1809,59 @@ def dilate_msk_(self, n_pixel: int = 5, labels: LABEL_REFERENCE = None, connecti |
1808 | 1809 | """In-place variant of `dilate_msk`.""" |
1809 | 1810 | return self.dilate_msk(n_pixel=n_pixel, labels=labels, connectivity=connectivity, mask=mask, inplace=True, verbose=verbose,use_crop=use_crop,ignore_direction=ignore_direction) |
1810 | 1811 |
|
| 1812 | + def voronoi_labels(self, labels: LABEL_REFERENCE = None, max_distance: float | None = None, use_mm: bool = True, signed_background: bool = True, tie_to_zero: bool = True, inplace=False, verbose: logging = True) -> Self: |
| 1813 | + """Assigns every voxel the label of the nearest labelled region (a Voronoi partition). |
| 1814 | +
|
| 1815 | + Args: |
| 1816 | + labels (LABEL_REFERENCE, optional): Labels to partition by. Everything else is treated as |
| 1817 | + background. Defaults to None (all labels present in the segmentation). |
| 1818 | + max_distance (float | None, optional): Leave background voxels further than this from any |
| 1819 | + region at 0. In mm when `use_mm` is True, else in voxels. Defaults to None (no limit). |
| 1820 | + use_mm (bool, optional): If True, distances are measured in mm using this image's voxel |
| 1821 | + spacing, so the partition is correct on anisotropic images. If False, voxels are |
| 1822 | + treated as isotropic. Defaults to True. |
| 1823 | + signed_background (bool, optional): If True, voxels filled in from the background get the |
| 1824 | + negated label of their region, so the input mask stays recoverable as `out > 0`. The |
| 1825 | + returned NII then has a signed dtype. Defaults to True. |
| 1826 | + tie_to_zero (bool, optional): If True, voxels equidistant to two different regions are set |
| 1827 | + to 0 instead of being assigned to one of them. Defaults to True. |
| 1828 | + inplace (bool, optional): Whether to modify this NII in place or return a new object. |
| 1829 | + Defaults to False. |
| 1830 | + verbose (bool, optional): Whether to print a message. Defaults to True. |
| 1831 | +
|
| 1832 | + Returns: |
| 1833 | + NII: The partition, signed when `signed_background` is set. |
| 1834 | +
|
| 1835 | + Notes: |
| 1836 | + Runtime is independent of the number of labels (one Euclidean feature transform for the |
| 1837 | + whole volume), unlike `dilate_msk`. |
| 1838 | + Set `signed_background=False, tie_to_zero=False` and a `max_distance` for a plain, |
| 1839 | + unsigned expansion of the labels (`np_expand_labels`). |
| 1840 | +
|
| 1841 | + Examples: |
| 1842 | + >>> nii.voronoi_labels(max_distance=10) # doctest: +SKIP |
| 1843 | + # every voxel within 1 cm of a labelled region takes its label |
| 1844 | + """ |
| 1845 | + assert self.seg |
| 1846 | + log.print("voronoi labels", end="\r", verbose=verbose) |
| 1847 | + out = np_voronoi_labels( |
| 1848 | + self.get_seg_array(), |
| 1849 | + label_ref=labels, |
| 1850 | + max_distance=max_distance, |
| 1851 | + zoom=self.zoom if use_mm else None, |
| 1852 | + signed_background=signed_background, |
| 1853 | + tie_to_zero=tie_to_zero, |
| 1854 | + ) |
| 1855 | + if not signed_background: |
| 1856 | + # keep the image dtype; the negated labels of a signed partition would not fit an unsigned one |
| 1857 | + out = out.astype(self.dtype) |
| 1858 | + log.print("Voronoi partition of", len(np_unique_withoutzero(np.abs(out))), "labels", verbose=verbose) |
| 1859 | + return self.set_array(out, inplace=inplace) |
| 1860 | + |
| 1861 | + def voronoi_labels_(self, labels: LABEL_REFERENCE = None, max_distance: float | None = None, use_mm: bool = True, signed_background: bool = True, tie_to_zero: bool = True, verbose: logging = True) -> Self: |
| 1862 | + """In-place variant of `voronoi_labels`.""" |
| 1863 | + return self.voronoi_labels(labels=labels, max_distance=max_distance, use_mm=use_mm, signed_background=signed_background, tie_to_zero=tie_to_zero, inplace=True, verbose=verbose) |
| 1864 | + |
1811 | 1865 |
|
1812 | 1866 | def fill_holes(self, labels: LABEL_REFERENCE = None, slice_wise_dim: int|str | None = None, verbose:logging=False, inplace=False,use_crop=True) -> Self: # noqa: ARG002 |
1813 | 1867 | """Fills holes in segmentation. |
|
0 commit comments