From 7a91d192034de8736dc8c8f53a8b9a13d11751e1 Mon Sep 17 00:00:00 2001 From: Qin Yu Date: Wed, 22 Jan 2025 00:19:40 +0100 Subject: [PATCH] refactor(post-processing): update docstrings and standardise style --- .../dataprocessing/advanced_dataprocessing.py | 36 ++++++++------- plantseg/tasks/dataprocessing_tasks.py | 39 +++++++--------- .../viewer_napari/widgets/dataprocessing.py | 44 +++++++++---------- 3 files changed, 57 insertions(+), 62 deletions(-) diff --git a/plantseg/functionals/dataprocessing/advanced_dataprocessing.py b/plantseg/functionals/dataprocessing/advanced_dataprocessing.py index 8c4d4364..46eec0a8 100644 --- a/plantseg/functionals/dataprocessing/advanced_dataprocessing.py +++ b/plantseg/functionals/dataprocessing/advanced_dataprocessing.py @@ -256,13 +256,14 @@ 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 @@ -270,32 +271,33 @@ def fix_over_under_segmentation_from_nuclei( 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 diff --git a/plantseg/tasks/dataprocessing_tasks.py b/plantseg/tasks/dataprocessing_tasks.py index 98fe2023..46c34268 100644 --- a/plantseg/tasks/dataprocessing_tasks.py +++ b/plantseg/tasks/dataprocessing_tasks.py @@ -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 diff --git a/plantseg/viewer_napari/widgets/dataprocessing.py b/plantseg/viewer_napari/widgets/dataprocessing.py index e7137bc8..eb986d46 100644 --- a/plantseg/viewer_napari/widgets/dataprocessing.py +++ b/plantseg/viewer_napari/widgets/dataprocessing.py @@ -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, @@ -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, @@ -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=[],