Skip to content

Commit

Permalink
Create seg_PCNA workflows (#79)
Browse files Browse the repository at this point in the history
* Create seg_PCNA workflows

Two versions of PCNA segmentation workflows which includes OTSU masking of apoptotic debris.  PCNA_lateS has fewest G1/late S/G2 errors.  PCNA_earlyS_midS detects all dim early S / mid S phase spots but picks up bright G2 pixels.

* update pcna workflow function names

* Updated PCNA lateS workflow with hole filling

3D dot filter method creates holes if spot is too large.  This workflow update addresses that issue.

* Removed vesselness filter step

* Added scipy import

---------

Co-authored-by: Benjamin Morris <benjijamorris@gmail.com>
  • Loading branch information
derekthirstrup and benjijamorris authored Jul 22, 2024
1 parent c9c7b68 commit 1910df7
Show file tree
Hide file tree
Showing 3 changed files with 437 additions and 0 deletions.
138 changes: 138 additions & 0 deletions aicssegmentation/structure_wrapper/seg_PCNA_earlyS_midS.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import numpy as np
from typing import Union
from pathlib import Path

from aicssegmentation.core.vessel import vesselness3D
from aicssegmentation.core.seg_dot import dot_3d
from aicssegmentation.core.pre_processing_utils import (
intensity_normalization,
edge_preserving_smoothing_3d,
)
from skimage.morphology import remove_small_objects
from aicssegmentation.core.output_utils import (
save_segmentation,
generate_segmentation_contour,
)

from skimage.filters import threshold_otsu


def Workflow_PCNA_earlyS_midS(
struct_img: np.ndarray,
rescale_ratio: float = -1,
output_type: str = "default",
output_path: Union[str, Path] = None,
fn: Union[str, Path] = None,
output_func=None,
):
"""
classic segmentation workflow wrapper for structure pcna
Parameter:
-----------
struct_img: np.ndarray
the 3D image to be segmented
rescale_ratio: float
an optional parameter to allow rescale the image before running the
segmentation functions, default is no rescaling
output_type: str
select how to handle output. Currently, four types are supported:
1. default: the result will be saved at output_path whose filename is
original name without extention + "_struct_segmentaiton.tiff"
2. array: the segmentation result will be simply returned as a numpy array
3. array_with_contour: segmentation result will be returned together with
the contour of the segmentation
4. customize: pass in an extra output_func to do a special save. All the
intermediate results, names of these results, the output_path, and the
original filename (without extension) will be passed in to output_func.
"""
##########################################################################
# PARAMETERS:
# note that these parameters are supposed to be fixed for the structure
# and work well accross different datasets
##########################################################################

intensity_norm_param = [0.5, 10]
intensity_norm_param_otsu = [0.5, 15]
scaling_factor = 1.0
vesselness_sigma = [1.0]
vesselness_cutoff = 0.0125
minArea = 20
dot_3d_sigma = 1.0
##########################################################################

out_img_list = []
out_name_list = []

###################
# PRE_PROCESSING
###################
# intenisty normalization (min/max)
struct_img = intensity_normalization(struct_img, scaling_param=intensity_norm_param)
struct_img_otsu = intensity_normalization(struct_img, scaling_param=intensity_norm_param_otsu)

out_img_list.append(struct_img.copy())
out_name_list.append("im_norm")

# smoothing with boundary preserving smoothing
structure_img_smooth = edge_preserving_smoothing_3d(struct_img)
structure_img_smooth_otsu = edge_preserving_smoothing_3d(struct_img_otsu)

out_img_list.append(structure_img_smooth.copy())
out_name_list.append("im_smooth")
out_img_list.append(structure_img_smooth_otsu.copy())
out_name_list.append("im_smooth_otsu")

###################
# core algorithm
###################
otsu_thresh = threshold_otsu(structure_img_smooth_otsu)
global_thresh = otsu_thresh * scaling_factor
bw_otsu_mask = structure_img_smooth_otsu > global_thresh

response_f3 = vesselness3D(structure_img_smooth, sigmas=vesselness_sigma, tau=1, whiteonblack=True)
response_f3 = response_f3 > vesselness_cutoff

response_s3_1 = dot_3d(structure_img_smooth, log_sigma=dot_3d_sigma)
response_s3_3 = dot_3d(structure_img_smooth, log_sigma=3)

bw_small_inverse = remove_small_objects(response_s3_1 > 0.035, min_size=150)
bw_small = np.logical_xor(bw_small_inverse, response_s3_1 > 0.025)
bw_medium = np.logical_or(bw_small, response_s3_1 > 0.07)
bw_large = np.logical_or(response_s3_3 > 0.2, response_f3 > 0.25)
bw_dots = np.logical_or(np.logical_or(bw_small, bw_medium), bw_large)
bw = np.logical_and(bw_dots, bw_otsu_mask)

###################
# POST-PROCESSING
###################
bw = remove_small_objects(bw > 0, min_size=minArea, connectivity=1, in_place=False)
for zz in range(bw.shape[0]):
bw[zz, :, :] = remove_small_objects(bw[zz, :, :], min_size=3, connectivity=1, in_place=False)

seg = remove_small_objects(bw > 0, min_size=minArea, connectivity=1, in_place=False)

from aicssegmentation.core.utils import remove_hot_pixel

seg = seg > 0
seg_clean = remove_hot_pixel(seg)
seg = seg_clean.astype(np.uint8)
seg[seg > 0] = 255

out_img_list.append(seg.copy())
out_name_list.append("bw_final")

if output_type == "default":
# the default final output, simply save it to the output path
save_segmentation(seg, False, Path(output_path), fn)
elif output_type == "customize":
# the hook for passing in a customized output function
# use "out_img_list" and "out_name_list" in your hook to
# customize your output functions
output_func(out_img_list, out_name_list, Path(output_path), fn)
elif output_type == "array":
return seg
elif output_type == "array_with_contour":
return (seg, generate_segmentation_contour(seg))
else:
raise NotImplementedError("invalid output type: {output_type}")
154 changes: 154 additions & 0 deletions aicssegmentation/structure_wrapper/seg_PCNA_lateS.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import numpy as np
from typing import Union
from pathlib import Path

from aicssegmentation.core.vessel import vesselness3D
from aicssegmentation.core.seg_dot import dot_3d
from aicssegmentation.core.pre_processing_utils import (
intensity_normalization,
edge_preserving_smoothing_3d,
)
from skimage.morphology import remove_small_objects
from aicssegmentation.core.output_utils import (
save_segmentation,
generate_segmentation_contour,
)

from skimage.filters import threshold_otsu


def Workflow_PCNA_lateS(
struct_img: np.ndarray,
rescale_ratio: float = -1,
output_type: str = "default",
output_path: Union[str, Path] = None,
fn: Union[str, Path] = None,
output_func=None,
):
"""
classic segmentation workflow wrapper for structure pcna
Parameter:
-----------
struct_img: np.ndarray
the 3D image to be segmented
rescale_ratio: float
an optional parameter to allow rescale the image before running the
segmentation functions, default is no rescaling
output_type: str
select how to handle output. Currently, four types are supported:
1. default: the result will be saved at output_path whose filename is
original name without extention + "_struct_segmentaiton.tiff"
2. array: the segmentation result will be simply returned as a numpy array
3. array_with_contour: segmentation result will be returned together with
the contour of the segmentation
4. customize: pass in an extra output_func to do a special save. All the
intermediate results, names of these results, the output_path, and the
original filename (without extension) will be passed in to output_func.
"""
##########################################################################
# PARAMETERS:
# note that these parameters are supposed to be fixed for the structure
# and work well accross different datasets
##########################################################################

intensity_norm_param = [0.5, 15]
intensity_norm_param_otsu = [0.5, 15]
scaling_factor = 1.0
vesselness_sigma = [1.0]
vesselness_cutoff = 0.15
minArea = 30
dot_3d_sigma = 1.0
otsumask_min_area = 50
##########################################################################

out_img_list = []
out_name_list = []

###################
# PRE_PROCESSING
###################
# intenisty normalization (min/max)
struct_img = intensity_normalization(struct_img, scaling_param=intensity_norm_param)
struct_img_otsu = intensity_normalization(struct_img, scaling_param=intensity_norm_param_otsu)

out_img_list.append(struct_img.copy())
out_name_list.append("im_norm")

# smoothing with boundary preserving smoothing
structure_img_smooth = edge_preserving_smoothing_3d(struct_img)
structure_img_smooth_otsu = edge_preserving_smoothing_3d(struct_img_otsu)


out_img_list.append(structure_img_smooth.copy())
out_name_list.append("im_smooth")
out_img_list.append(structure_img_smooth_otsu.copy())
out_name_list.append("im_smooth_otsu")


###################
# core algorithm
###################
otsu_thresh = threshold_otsu(structure_img_smooth_otsu)
global_thresh = otsu_thresh * scaling_factor
bw_otsu_mask = structure_img_smooth_otsu > global_thresh
bw_otsu_mask_removesmallobjects = remove_small_objects(
bw_otsu_mask, min_size=otsumask_min_area, connectivity=1, in_place=False
)

response_f3 = vesselness3D(structure_img_smooth, sigmas=vesselness_sigma, tau=1, whiteonblack=True)
response_f3 = response_f3 > vesselness_cutoff

response_s3_1 = dot_3d(structure_img_smooth, log_sigma=dot_3d_sigma)
response_s3_3 = dot_3d(structure_img_smooth, log_sigma=3)

#bw_small_inverse = remove_small_objects(response_s3_1 > 0.035, min_size=150)
#bw_small = np.logical_xor(bw_small_inverse, response_s3_1 > 0.025)
#bw_small = response_s3_1 > 0.12
bw_medium = response_s3_1 > 0.05
#bw_large = response_s3_3 > 0.45
#bw_large = np.logical_or(response_s3_3 > 0.45, response_f3 > 0.5)
#bw_dots = np.logical_or(bw_medium, bw_large)
#bw = np.logical_and(bw_dots, bw_otsu_mask_removesmallobjects)
bw = np.logical_and(bw_medium, bw_otsu_mask_removesmallobjects)

out_img_list.append(bw_otsu_mask.copy())
out_name_list.append("bw_otsu_mask")
out_img_list.append(bw_otsu_mask_removesmallobjects.copy())
out_name_list.append("bw_otsu_mask_removesmallobjects")
out_img_list.append(bw.copy())
out_name_list.append("bw")

###################
# POST-PROCESSING
###################
bw = remove_small_objects(bw > 0, min_size=minArea, connectivity=1, in_place=False)
for zz in range(bw.shape[0]):
bw[zz, :, :] = remove_small_objects(bw[zz, :, :], min_size=3, connectivity=1, in_place=False)

seg = remove_small_objects(bw > 0, min_size=minArea, connectivity=1, in_place=False)

from aicssegmentation.core.utils import remove_hot_pixel

seg = seg > 0
seg_clean = remove_hot_pixel(seg)
seg = seg_clean.astype(np.uint8)
seg[seg > 0] = 255

out_img_list.append(seg.copy())
out_name_list.append("bw_final")

if output_type == "default":
# the default final output, simply save it to the output path
save_segmentation(seg, False, Path(output_path), fn)
elif output_type == "customize":
# the hook for passing in a customized output function
# use "out_img_list" and "out_name_list" in your hook to
# customize your output functions
output_func(out_img_list, out_name_list, Path(output_path), fn)
elif output_type == "array":
return seg
elif output_type == "array_with_contour":
return (seg, generate_segmentation_contour(seg))
else:
raise NotImplementedError("invalid output type: {output_type}")
Loading

0 comments on commit 1910df7

Please sign in to comment.