Skip to content

Commit

Permalink
refactor(post-processing): update docstrings and standardise style
Browse files Browse the repository at this point in the history
  • Loading branch information
qin-yu committed Jan 21, 2025
1 parent d3335b5 commit 7a91d19
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 62 deletions.
36 changes: 19 additions & 17 deletions plantseg/functionals/dataprocessing/advanced_dataprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,46 +256,48 @@ def fix_over_segmentation(
def fix_over_under_segmentation_from_nuclei(
cell_seg: np.ndarray,
nuclei_seg: np.ndarray,
threshold_merge: float = 0.33,
threshold_split: float = 0.66,
quantiles_nuclei: tuple[float, float] = (0.3, 0.99),
threshold_merge: float,
threshold_split: float,
quantile_min: float,
quantile_max: float,
boundary: np.ndarray | None = None,
) -> np.ndarray:
"""
Corrects over-segmentation and under-segmentation of cells based on a trusted nuclei segmentation.
Correct over-segmentation and under-segmentation of cells based on nuclei information.
This function uses information from nuclei segmentation to refine cell segmentation by first identifying
over-segmented cells (cells mistakenly split into multiple segments) and merging them. It then corrects
under-segmented cells (multiple nuclei within a single cell) by splitting them based on nuclei position
and optional boundary information.
Args:
cell_seg (np.ndarray): A 2D or 3D array representing segmented cell instances.
nuclei_seg (np.ndarray): A 2D or 3D array representing segmented nuclei instances, with the same shape as `cell_seg`.
threshold_merge (float, optional): Threshold for identifying over-segmentation, based on the ratio of nuclei overlap.
Cells with overlap below this threshold will be merged. Default is 0.33.
threshold_split (float, optional): Threshold for identifying under-segmentation, based on the ratio of nuclei overlap.
Cells with overlap above this threshold will be split. Default is 0.66.
quantiles_nuclei (tuple[float, float], optional): Quantile range for filtering nuclei based on size, helping to ignore
outliers such as very small or very large nuclei. Default is (0.3, 0.99).
boundary (np.ndarray | None, optional): An optional boundary probability map for the cells. If None, a constant map
is used to treat all regions equally. This can help refine under-segmentation correction.
cell_seg (np.ndarray): 2D/3D array for cell segmentation.
nuclei_seg (np.ndarray): 2D/3D array for nuclei segmentation.
threshold_merge (float): Threshold for merging cells, as a fraction (0-1).
threshold_split (float): Threshold for splitting cells, as a fraction (0-1).
quantile_min (float): Minimum quantile for filtering nuclei sizes, as a fraction (0-1).
quantile_max (float): Maximum quantile for filtering nuclei sizes, as a fraction (0-1).
boundary (np.ndarray | None, optional): Optional boundary probability map to refine under-segmentation.
If None, a constant map is used to treat all regions equally.
Returns:
np.ndarray: The corrected cell segmentation array, of the same shape as the input `cell_seg`.
np.ndarray: Corrected cell segmentation array.
"""
# Find overlaps between cells and nuclei
cell_counts, nuclei_counts, cell_nuclei_counts = numba_find_overlaps(cell_seg, nuclei_seg)
nuclei_assignments = find_potential_over_seg(nuclei_counts, cell_nuclei_counts, threshold=threshold_merge)

# Identify over-segmentation and correct it
nuclei_assignments = find_potential_over_seg(nuclei_counts, cell_nuclei_counts, threshold=threshold_merge)
corrected_seg = fix_over_segmentation(cell_seg, nuclei_assignments)

# Identify under-segmentation and correct it
cell_counts, nuclei_counts, cell_nuclei_counts = numba_find_overlaps(corrected_seg, nuclei_seg)
cell_assignments = find_potential_under_seg(
nuclei_counts,
cell_counts,
cell_nuclei_counts,
threshold=threshold_split,
quantiles_clip=quantiles_nuclei,
quantiles_clip=(quantile_min, quantile_max),
)

boundary_pmap = np.ones_like(cell_seg) if boundary is None else boundary
Expand Down
39 changes: 17 additions & 22 deletions plantseg/tasks/dataprocessing_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,42 +232,37 @@ def remove_false_positives_by_foreground_probability_task(
def fix_over_under_segmentation_from_nuclei_task(
cell_seg: PlantSegImage,
nuclei_seg: PlantSegImage,
threshold_merge: float = 0.33,
threshold_split: float = 0.66,
quantiles_nuclei: tuple[float, float] = (0.3, 0.99),
threshold_merge: float,
threshold_split: float,
quantile_min: float,
quantile_max: float,
boundary: PlantSegImage | None = None,
) -> PlantSegImage:
"""
Task function to fix over- and under-segmentation in cell segmentation based on nuclear segmentation.
This function is used to run the over- and under-segmentation correction within a task management system.
It uses the segmentation arrays and nuclear information to merge and split cell regions. This task ensures
that the provided `cell_seg` and `nuclei_seg` have matching shapes and processes the data accordingly.
Task to fix over- and under-segmentation of cells based on nuclear segmentation.
Args:
cell_seg (PlantSegImage): Input cell segmentation as a `PlantSegImage` object.
nuclei_seg (PlantSegImage): Input nuclear segmentation as a `PlantSegImage` object.
threshold_merge (float, optional): Threshold for merging cells based on the overlap with nuclei. Default is 0.33.
threshold_split (float, optional): Threshold for splitting cells based on the overlap with nuclei. Default is 0.66.
quantiles_nuclei (tuple[float, float], optional): Quantiles used to filter nuclei by size. Default is (0.3, 0.99).
boundary (PlantSegImage | None, optional): Optional boundary probability map. If not provided, a constant map is used.
cell_seg (PlantSegImage): Input cell segmentation as a PlantSegImage object.
nuclei_seg (PlantSegImage): Input nuclear segmentation as a PlantSegImage object.
threshold_merge (float): Threshold for merging cells, as a fraction (0-1).
threshold_split (float): Threshold for splitting cells, as a fraction (0-1).
quantile_min (float): Minimum quantile for filtering nuclei sizes, as a fraction (0-1).
quantile_max (float): Maximum quantile for filtering nuclei sizes, as a fraction (0-1).
boundary (PlantSegImage | None, optional): Optional boundary probability map for segmentation refinement.
Returns:
PlantSegImage: A new `PlantSegImage` object containing the corrected cell segmentation.
PlantSegImage: Corrected cell segmentation as a PlantSegImage object.
"""
if cell_seg.shape != nuclei_seg.shape:
raise ValueError("Cell and nuclei segmentation must have the same shape.")

out_data = fix_over_under_segmentation_from_nuclei(
corrected_data = fix_over_under_segmentation_from_nuclei(
cell_seg.get_data(),
nuclei_seg.get_data(),
threshold_merge=threshold_merge,
threshold_split=threshold_split,
quantiles_nuclei=quantiles_nuclei,
quantile_min=quantile_min,
quantile_max=quantile_max,
boundary=boundary.get_data() if boundary else None,
)
new_image = cell_seg.derive_new(out_data, name=f"{cell_seg.name}_nuc_fixed")
return new_image
return cell_seg.derive_new(corrected_data, name=f"{cell_seg.name}_nuc_fixed")


@task_tracker
Expand Down
44 changes: 21 additions & 23 deletions plantseg/viewer_napari/widgets/dataprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,16 +483,16 @@ def widget_remove_false_positives_by_foreground(
segmentation_nuclei={'label': 'Nuclear instances'},
boundary_pmaps={'label': 'Boundary image'},
threshold={
'label': 'Boundary threshold',
'tooltip': 'Threshold range for merging (first value) and splitting (second value) cells. ',
'label': 'Boundary Threshold (%)',
'tooltip': 'Range for merging (first value) and splitting (second value) cells, represented as percentages (0-100%).',
'widget_type': 'FloatRangeSlider',
'max': 100,
'min': 0,
'step': 0.1,
},
quantile={
'label': 'Nuclei size filter',
'tooltip': 'Quantile range to filter nuclei size, ignoring outliers.',
'label': 'Nuclei Size Filter (%)',
'tooltip': 'Range to filter nuclei sizes, excluding extreme outliers. Represented as percentages (0-100%).',
'widget_type': 'FloatRangeSlider',
'max': 100,
'min': 0,
Expand All @@ -509,30 +509,27 @@ def widget_fix_over_under_segmentation_from_nuclei(
"""
Widget interface for correcting over- and under-segmentation of cells based on nuclei segmentation.
This GUI interface allows the user to specify the input cell and nuclear segmentations, along with optional boundary
probability maps. The user can control the merging and splitting thresholds, and define quantiles to filter out
irregular nuclei. The widget schedules the correction task in the background and updates the displayed results accordingly.
Args:
cell_segmentation (Labels): Input label layer for cell segmentation.
nuclei_segmentation (Labels): Input label layer for nuclei segmentation.
boundary_pmaps (Image | None, optional): Optional boundary probability map or image to assist in segmentation refinement.
threshold (tuple[float, float], optional): Threshold range for merging (first value) and splitting (second value) cells.
The values should be between 0 and 100, corresponding to 0%-100% overlap. Default is (33, 66).
quantile (tuple[float, float], optional): Quantile range to filter nuclei size, ignoring outliers.
Values should be between 0 and 100. Default is (0.3, 99.9).
segmentation_cells (Labels): Input label layer for cell segmentation.
segmentation_nuclei (Labels): Input label layer for nuclei segmentation.
boundary_pmaps (Image | None, optional): Optional boundary probability map to refine segmentation.
threshold (tuple[float, float], optional): Tuple with merge (first value) and split (second value) thresholds as percentages.
Default is (33, 66).
quantile (tuple[float, float], optional): Tuple with minimum and maximum quantile values for filtering nuclei sizes as percentages.
Default is (0.3, 99.9).
Returns:
Future[LayerDataTuple]: A future object that contains the corrected segmentation layer once the task completes.
None
"""
ps_seg_cel = PlantSegImage.from_napari_layer(segmentation_cells)
ps_seg_nuc = PlantSegImage.from_napari_layer(segmentation_nuclei)
if boundary_pmaps:
ps_pmap_cell_boundary = PlantSegImage.from_napari_layer(boundary_pmaps)
else:
ps_pmap_cell_boundary = None
threshold_merge, threshold_split = threshold[0] / 100, threshold[1] / 100
quantile = (quantile[0] / 100, quantile[1] / 100)
ps_pmap_cell_boundary = PlantSegImage.from_napari_layer(boundary_pmaps) if boundary_pmaps else None

# Normalize percentages to fractions
threshold_merge = threshold[0] / 100
threshold_split = threshold[1] / 100
quantile_min = quantile[0] / 100
quantile_max = quantile[1] / 100

return schedule_task(
fix_over_under_segmentation_from_nuclei_task,
Expand All @@ -541,7 +538,8 @@ def widget_fix_over_under_segmentation_from_nuclei(
'nuclei_seg': ps_seg_nuc,
'threshold_merge': threshold_merge,
'threshold_split': threshold_split,
'quantiles_nuclei': quantile,
'quantile_min': quantile_min,
'quantile_max': quantile_max,
'boundary': ps_pmap_cell_boundary,
},
widgets_to_update=[],
Expand Down

0 comments on commit 7a91d19

Please sign in to comment.