Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/scilpy/cli/scil_labels_from_mask.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ def _build_arg_parser():
p.add_argument('--min_volume', type=float, default=7,
help='Minimum volume in mm3 [%(default)s],'
'Useful for lesions.')
p.add_argument('--min_distance', type=int, default=None,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this will allow you to do what you want here:
p.add_argument('--watershed', metavar='MIN_DISTANCE', type=int, nargs='?', const=1, help='Use a watershed algorithm, if provided the parameter (default: 1 voxel) allow to ...')

This make so p.watershed is None by default, if you use --watershep it is 1 by default, but you can do --watershed 10 to change it.

help='Minimum distance in voxels [%(default)s],'
'Useful for confluent lesions.')

add_verbose_arg(p)
add_overwrite_arg(p)
Expand All @@ -57,11 +60,13 @@ def main():
mask_data = get_data_as_mask(mask_img)
voxel_volume = np.prod(np.diag(mask_img.affine)[:3])
min_voxel_count = args.min_volume // voxel_volume
min_distance = args.min_distance

# Get labels from mask
label_map = get_labels_from_mask(
mask_data, args.labels, args.background_label,
min_voxel_count=min_voxel_count)
min_voxel_count=min_voxel_count,
min_distance=min_distance)
# Save result
out_img = nib.Nifti1Image(label_map.astype(np.uint16), mask_img.affine)
nib.save(out_img, args.out_labels)
Expand Down
26 changes: 24 additions & 2 deletions src/scilpy/image/labels.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import numpy as np
from scipy import ndimage as ndi
from scipy.spatial import cKDTree
from skimage.segmentation import watershed
from skimage.feature import peak_local_max

from scilpy.tractanalysis.reproducibility_measures import compute_bundle_adjacency_voxel

Expand Down Expand Up @@ -72,7 +74,7 @@ def get_binary_mask_from_labels(atlas, label_list):


def get_labels_from_mask(mask_data, labels=None, background_label=0,
min_voxel_count=0):
min_voxel_count=0, min_distance=0):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be clearer if it was None and if your condition was if min_distance is not None: later

"""
Get labels from a binary mask which contains multiple blobs. Each blob
will be assigned a label, by default starting from 1. Background will
Expand All @@ -90,14 +92,34 @@ def get_labels_from_mask(mask_data, labels=None, background_label=0,
min_voxel_count: int, optional
Minimum number of voxels for a blob to be considered. Blobs with fewer
voxels will be ignored.
min_distance : int, optional
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be explicit in the docstring that this trigger a change to a watershed algorithm

The minimum voxels separating the peaks of two
neighboring blobs. If a confluent blob is detected, it will be
split into multiple labels based on this distance. If None, no
splitting is performed.

Returns
-------
label_map: np.ndarray
The labels.
"""
# Get the number of structures and assign labels to each blob
label_map, nb_structures = ndi.label(mask_data)
if min_distance:
distance = ndi.distance_transform_edt(mask_data)
coords = peak_local_max(
distance,
min_distance=min_distance,
labels=mask_data,
threshold_abs=0
)
mask = np.zeros(distance.shape, dtype=bool)
mask[tuple(coords.T)] = True
markers, _ = ndi.label(mask)
label_map = watershed(-distance, markers, mask=mask_data)
nb_structures = np.max(label_map)
else:
label_map, nb_structures = ndi.label(mask_data)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a small comment saying "Any contigous regions touching even by a single voxel will be considered a single label"


if min_voxel_count:
new_count = 0
for label in range(1, nb_structures + 1):
Expand Down