From 6f62c04df60a751be9f6cbde22b8e3c37b3afac3 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 22 Jan 2020 14:44:24 -0500 Subject: [PATCH 001/419] Adding _get_membrane_mask and _get_nuclei_mask functions to WatershedSegmentNucleiCV2 class --- merlin/analysis/segment.py | 209 +++++++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 00014983..36fb0687 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -2,10 +2,14 @@ import numpy as np from skimage import measure from skimage import segmentation +from skimage import morphology +from skimage import feature +from skimage import filters import rtree from shapely import geometry from typing import List, Dict from scipy.spatial import cKDTree +from scipy.ndimage.morphology import binary_fill_holes from merlin.core import dataset from merlin.core import analysistask @@ -119,6 +123,211 @@ def _read_and_filter_image_stack(self, fov: int, channelIndex: int, (filterSize, filterSize), filterSigma) for z in range(len(self.dataSet.get_z_positions()))]) +class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): + + """ + An analysis task that determines the boundaries of features in the + image data in each field of view using a watershed algorithm + implemented in CV2. + + A tutorial explaining the general scheme of the method can be + found in https://opencv-python-tutroals.readthedocs.io/en/latest/ + py_tutorials/py_imgproc/py_watershed/py_watershed.html. + + The watershed segmentation is performed in each z-position independently and + combined into 3D objects in a later step + + Since each field of view is analyzed individually, the segmentation results + should be cleaned in order to merge cells that cross the field of + view boundary. + """ + + def __init__(self, dataSet, parameters=None, analysisName=None): + super().__init__(dataSet, parameters, analysisName) + + if 'seed_channel_name' not in self.parameters: + self.parameters['seed_channel_name'] = 'WGA' + if 'watershed_channel_name' not in self.parameters: + self.parameters['watershed_channel_name'] = 'DAPI' + + def fragment_count(self): + return len(self.dataSet.get_fovs()) + + def get_estimated_memory(self): + # TODO - refine estimate + return 2048 + + def get_estimated_time(self): + # TODO - refine estimate + return 5 + + def get_dependencies(self): + return [self.parameters['warp_task'], + self.parameters['global_align_task']] + + def get_cell_boundaries(self) -> List[spatialfeature.SpatialFeature]: + featureDB = self.get_feature_database() + return featureDB.read_features() + + def _run_analysis(self, fragmentIndex): + globalTask = self.dataSet.load_analysis_task( + self.parameters['global_align_task']) + + seedIndex = self.dataSet.get_data_organization().get_data_channel_index( + self.parameters['seed_channel_name']) + seedImages = self._read_and_filter_image_stack(fragmentIndex, + seedIndex, 5) + + watershedIndex = self.dataSet.get_data_organization() \ + .get_data_channel_index(self.parameters['watershed_channel_name']) + watershedImages = self._read_and_filter_image_stack(fragmentIndex, + watershedIndex, 5) + seeds = watershed.separate_merged_seeds( + watershed.extract_seeds(seedImages)) + normalizedWatershed, watershedMask = watershed.prepare_watershed_images( + watershedImages) + + seeds[np.invert(watershedMask)] = 0 + watershedOutput = segmentation.watershed( + normalizedWatershed, measure.label(seeds), mask=watershedMask, + connectivity=np.ones((3, 3, 3)), watershed_line=True) + + zPos = np.array(self.dataSet.get_data_organization().get_z_positions()) + featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( + (watershedOutput == i), fragmentIndex, + globalTask.fov_to_global_transform(fragmentIndex), zPos) + for i in np.unique(watershedOutput) if i != 0] + + featureDB = self.get_feature_database() + featureDB.write_features(featureList, fragmentIndex) + + def _get_membrane_mask(self, fov: int, channelIndex: int, + filterSigma: float) -> np.ndarray: + warpTask = self.dataSet.load_analysis_task( + self.parameters['warp_task']) + + imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) + for z in range(len(self.dataSet.get_z_positions()))]) + + # generate mask based on edge detection + edgeMask = np.zeros(imageStack.shape) + for z in range(len(self.dataSet.get_z_positions())): + edgeMask[:,:,z] = canny( + white_tophat(imageStack[:,:,z], selem.disk(10)), + sigma=2, use_quantiles=True, low_threshold=0.5, + high_threshold=0.8) + edgeMask[:,:,z] = binary_closing(edgeMask[:,:,z],selem.disk(5)) + edgeMask[:,:,z] = remove_small_objects( + edgeMask[:,:,z].astype('bool'), min_size=100, connectivity=1) + edgeMask[:,:,z] = skeletonize(edgeMask[:,:,z]) + + # generate mask based on thresholding + tresholdingMask = np.zeros(imageStack.shape) + fineBlockSize = 61 + for z in range(len(self.dataSet.get_z_positions())): + tresholdingMask[:,:,z] = imageStack[:,:,z] > + threshold_local(imageStack[:,:,z], fineBlockSize, offset=0) + tresholdingMask[:,:,z] = remove_small_objects( + imageStack[:,:,z].astype('bool'), min_size=100, connectivity=1) + tresholdingMask[:,:,z] = binary_closing(imageStack[:,:,z], + selem.disk(5)) + tresholdingMask[:,:,z] = skeletonize(imageStack[:,:,z]) + + # combine masks + return edgeMask + thresholdingMask + + def _get_nuclei_mask(self, fov: int, channelIndex: int, + filterSigma: float) -> np.ndarray: + + warpTask = self.dataSet.load_analysis_task( + self.parameters['warp_task']) + + imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) + for z in range(len(self.dataSet.get_z_positions()))]) + + # generate nuclei mask based on thresholding + thresholdingMask = np.zeros(imageStack.shape) + coarseBlockSize = 241 + fineBlockSize = 61 + for z in range(len(self.dataSet.get_z_positions())): + coarseThresholdingMask = imageStack[:,:,z] > + threshold_local(imageStack[:,:,z], coarseBlockSize, offset=0) + fineThresholdingMask = imageStack[:,:,z] > + threshold_local(imageStack[:,:,z], fineBlockSize, offset=0) + thresholdingMask[:,:,z] = coarseThresholdingMask* + fineThresholdingMask + thresholdingMask[:,:,z] = binary_fill_holes(thresholdingMask[:,:,z]) + + # generate nuclei mask from hessian, fine + fineHessianMask = np.zeros(imageStack.shape) + for z in range(len(self.dataSet.get_z_positions())): + fineHessian = hessian(imageStack[:,:,z]) + fineHessianMask[:,:,z] = fineHessian == fineHessian.max() + fineHessianMask[:,:,z] = binary_closing( + fineHessianMask[:,:,z], selem.disk(5)) + fineHessianMask[:,:,z] = binary_fill_holes(fineHessianMask[:,:,z]) + + # generate dapi mask from hessian, coarse + coarseHessianMask = np.zeros(imageStack.shape) + for z in range(len(self.dataSet.get_z_positions())): + coarseHessian = hessian(imageStack[:,:,z] - + white_tophat(imageStack[:,:,z], selem.disk(20))) + coarseHessianMask[:,:,z] = coarseHessian == coarseHessian.max() + coarseHessianMask[:,:,z] = binary_closing(coarseHessianMask[:,:,z], + selem.disk(5)) + coarseHessianMask[:,:,z] = binary_fill_holes( + coarseHessianMask[:,:,z]) + + # combine masks + nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask + return binary_fill_holes(nucleiMask) + + def _generate_watershed_mask(self, fov: int, channelIndex: int, + filterSigma: float) -> np.ndarray: + + + def _read_and_filter_image_stack(self, fov: int, channelIndex: int, + filterSigma: float) -> np.ndarray: + filterSize = int(2*np.ceil(2*filterSigma)+1) + warpTask = self.dataSet.load_analysis_task( + self.parameters['warp_task']) + return np.array([cv2.GaussianBlur( + warpTask.get_aligned_image(fov, channelIndex, z), + (filterSize, filterSize), filterSigma) + for z in range(len(self.dataSet.get_z_positions()))]) + + +#-------------------------------------------------------------------------------------------------------------------- +# Generate. As before, use a combination of masks +#-------------------------------------------------------------------------------------------------------------------- +sure_bg_dapi = sm.dilation(foreground,sm.selem.disk(15)) + +mask_wga_dil = sm.dilation(mask_wga,sm.selem.disk(10)) +sure_fg = sm.erosion(foreground*~mask_wga_dil,sm.selem.disk(5)) + +unknown_dapi = sure_bg_dapi*~sure_fg + +sure_bg_dapi = np.uint8(sure_bg_dapi)*255 +sure_fg = np.uint8(sure_fg)*255 +unknown_dapi = np.uint8(unknown_dapi)*255 + + +# Marker labelling +ret, markers = cv2.connectedComponents(sure_fg) + +# Add one to all labels so that sure background is not 0, but 1 +markers_dapi = markers+100 + +# Now, mark the region of unknown with zero +markers_dapi[unknown_dapi==255] = 0 + +# Apply watershed using cv2 +markers_ws_dapi = cv2.watershed(Idapi_inv,markers_dapi) + + + + + class CleanCellBoundaries(analysistask.ParallelAnalysisTask): ''' From acba80c39fcf343cc4e52304594daa80246985b6 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Jan 2020 08:49:46 -0500 Subject: [PATCH 002/419] changed formatting to comply to pep8 --- merlin/analysis/segment.py | 50 ++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 36fb0687..8b1e9144 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -134,12 +134,12 @@ class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): found in https://opencv-python-tutroals.readthedocs.io/en/latest/ py_tutorials/py_imgproc/py_watershed/py_watershed.html. - The watershed segmentation is performed in each z-position independently and - combined into 3D objects in a later step + The watershed segmentation is performed in each z-position + independently and combined into 3D objects in a later step - Since each field of view is analyzed individually, the segmentation results - should be cleaned in order to merge cells that cross the field of - view boundary. + Since each field of view is analyzed individually, the segmentation + results should be cleaned in order to merge cells that cross the + field of view boundary. """ def __init__(self, dataSet, parameters=None, analysisName=None): @@ -213,9 +213,9 @@ def _get_membrane_mask(self, fov: int, channelIndex: int, edgeMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): edgeMask[:,:,z] = canny( - white_tophat(imageStack[:,:,z], selem.disk(10)), - sigma=2, use_quantiles=True, low_threshold=0.5, - high_threshold=0.8) + white_tophat(imageStack[:,:,z], selem.disk(10)), + sigma=2, use_quantiles=True, + low_threshold=0.5, high_threshold=0.8) edgeMask[:,:,z] = binary_closing(edgeMask[:,:,z],selem.disk(5)) edgeMask[:,:,z] = remove_small_objects( edgeMask[:,:,z].astype('bool'), min_size=100, connectivity=1) @@ -225,12 +225,13 @@ def _get_membrane_mask(self, fov: int, channelIndex: int, tresholdingMask = np.zeros(imageStack.shape) fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - tresholdingMask[:,:,z] = imageStack[:,:,z] > + tresholdingMask[:,:,z] = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], fineBlockSize, offset=0) tresholdingMask[:,:,z] = remove_small_objects( - imageStack[:,:,z].astype('bool'), min_size=100, connectivity=1) - tresholdingMask[:,:,z] = binary_closing(imageStack[:,:,z], - selem.disk(5)) + imageStack[:,:,z].astype('bool'), + min_size=100, connectivity=1) + tresholdingMask[:,:,z] = binary_closing(imageStack[:,:,z], + selem.disk(5)) tresholdingMask[:,:,z] = skeletonize(imageStack[:,:,z]) # combine masks @@ -250,12 +251,14 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int, coarseBlockSize = 241 fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = imageStack[:,:,z] > - threshold_local(imageStack[:,:,z], coarseBlockSize, offset=0) + coarseThresholdingMask = imageStack[:,:,z] > + threshold_local(imageStack[:,:,z], + coarseBlockSize, offset=0) fineThresholdingMask = imageStack[:,:,z] > - threshold_local(imageStack[:,:,z], fineBlockSize, offset=0) + threshold_local(imageStack[:,:,z], + fineBlockSize, offset=0) thresholdingMask[:,:,z] = coarseThresholdingMask* - fineThresholdingMask + fineThresholdingMask thresholdingMask[:,:,z] = binary_fill_holes(thresholdingMask[:,:,z]) # generate nuclei mask from hessian, fine @@ -263,20 +266,21 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions())): fineHessian = hessian(imageStack[:,:,z]) fineHessianMask[:,:,z] = fineHessian == fineHessian.max() - fineHessianMask[:,:,z] = binary_closing( - fineHessianMask[:,:,z], selem.disk(5)) + fineHessianMask[:,:,z] = binary_closing(fineHessianMask[:,:,z], + selem.disk(5)) fineHessianMask[:,:,z] = binary_fill_holes(fineHessianMask[:,:,z]) # generate dapi mask from hessian, coarse coarseHessianMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): - coarseHessian = hessian(imageStack[:,:,z] - - white_tophat(imageStack[:,:,z], selem.disk(20))) + coarseHessian = hessian(imageStack[:,:,z] - + white_tophat(imageStack[:,:,z], + selem.disk(20))) coarseHessianMask[:,:,z] = coarseHessian == coarseHessian.max() - coarseHessianMask[:,:,z] = binary_closing(coarseHessianMask[:,:,z], - selem.disk(5)) + coarseHessianMask[:,:,z] = binary_closing(coarseHessianMask[:,:,z], + selem.disk(5)) coarseHessianMask[:,:,z] = binary_fill_holes( - coarseHessianMask[:,:,z]) + coarseHessianMask[:,:,z]) # combine masks nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask From 8ccc69e1127dcc1c72ccf560f93a4882f811473d Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Jan 2020 09:47:58 -0500 Subject: [PATCH 003/419] add borderMask to _get_nuclei_mask function --- merlin/analysis/segment.py | 44 ++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 8b1e9144..664872d1 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -210,6 +210,7 @@ def _get_membrane_mask(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions()))]) # generate mask based on edge detection + """ edgeMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): edgeMask[:,:,z] = canny( @@ -218,24 +219,28 @@ def _get_membrane_mask(self, fov: int, channelIndex: int, low_threshold=0.5, high_threshold=0.8) edgeMask[:,:,z] = binary_closing(edgeMask[:,:,z],selem.disk(5)) edgeMask[:,:,z] = remove_small_objects( - edgeMask[:,:,z].astype('bool'), min_size=100, connectivity=1) + edgeMask[:,:,z].astype('bool'), + min_size=100, connectivity=1) edgeMask[:,:,z] = skeletonize(edgeMask[:,:,z]) + """ # generate mask based on thresholding tresholdingMask = np.zeros(imageStack.shape) fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:,:,z] = imageStack[:,:,z] > - threshold_local(imageStack[:,:,z], fineBlockSize, offset=0) + threshold_local(imageStack[:,:,z], + fineBlockSize, offset=0) tresholdingMask[:,:,z] = remove_small_objects( - imageStack[:,:,z].astype('bool'), - min_size=100, connectivity=1) + imageStack[:,:,z].astype('bool'), + min_size=100, connectivity=1) tresholdingMask[:,:,z] = binary_closing(imageStack[:,:,z], - selem.disk(5)) + selem.disk(5)) tresholdingMask[:,:,z] = skeletonize(imageStack[:,:,z]) # combine masks - return edgeMask + thresholdingMask + # return edgeMask + thresholdingMask + return thresholdingMask def _get_nuclei_mask(self, fov: int, channelIndex: int, filterSigma: float) -> np.ndarray: @@ -253,21 +258,31 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions())): coarseThresholdingMask = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], - coarseBlockSize, offset=0) + coarseBlockSize, offset=0) fineThresholdingMask = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], - fineBlockSize, offset=0) + fineBlockSize, offset=0) thresholdingMask[:,:,z] = coarseThresholdingMask* fineThresholdingMask - thresholdingMask[:,:,z] = binary_fill_holes(thresholdingMask[:,:,z]) - + thresholdingMask[:,:,z] = binary_fill_holes( + thresholdingMask[:,:,z]) + + # generate border mask, necessary to avoid making a single + # connected component when using binary_fill_holes below + borderMask = np.zeros((2048,2048)) + borderMask[25:2023,25:2023] = 1 + + # TODO - use the image size variable for borderMask + + # generate nuclei mask from hessian, fine fineHessianMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): fineHessian = hessian(imageStack[:,:,z]) fineHessianMask[:,:,z] = fineHessian == fineHessian.max() fineHessianMask[:,:,z] = binary_closing(fineHessianMask[:,:,z], - selem.disk(5)) + selem.disk(5)) + fineHessianMask[:,:,z] = fineHessianMask[:,:,z]*borderMask fineHessianMask[:,:,z] = binary_fill_holes(fineHessianMask[:,:,z]) # generate dapi mask from hessian, coarse @@ -275,12 +290,13 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions())): coarseHessian = hessian(imageStack[:,:,z] - white_tophat(imageStack[:,:,z], - selem.disk(20))) + selem.disk(20))) coarseHessianMask[:,:,z] = coarseHessian == coarseHessian.max() coarseHessianMask[:,:,z] = binary_closing(coarseHessianMask[:,:,z], - selem.disk(5)) + selem.disk(5)) + coarseHessianMask[:,:,z] = coarseHessianMask[:,:,z]*borderMask coarseHessianMask[:,:,z] = binary_fill_holes( - coarseHessianMask[:,:,z]) + coarseHessianMask[:,:,z]) # combine masks nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask From 967c9f268267b4f2e67a51cc7fc7c199c73fc549 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Jan 2020 14:11:30 -0500 Subject: [PATCH 004/419] added method _generate_markers to WatershedSegmentNucleiCV2 --- merlin/analysis/segment.py | 102 +++++++++++++++++++++---------------- 1 file changed, 57 insertions(+), 45 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 664872d1..05ef8455 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -180,6 +180,16 @@ def _run_analysis(self, fragmentIndex): watershedIndex = self.dataSet.get_data_organization() \ .get_data_channel_index(self.parameters['watershed_channel_name']) + + membraneMask = self._get_membrane_mask(fragmentIndex, watershedIndex) + nucleiMask = self._get_nuclei_mask(fragmentIndex, watershedIndex) + watershedMarkers = self._generate_markers(nucleiMask,membraneMask) + + + + + + watershedImages = self._read_and_filter_image_stack(fragmentIndex, watershedIndex, 5) seeds = watershed.separate_merged_seeds( @@ -201,8 +211,7 @@ def _run_analysis(self, fragmentIndex): featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) - def _get_membrane_mask(self, fov: int, channelIndex: int, - filterSigma: float) -> np.ndarray: + def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) @@ -230,20 +239,20 @@ def _get_membrane_mask(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:,:,z] = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], - fineBlockSize, offset=0) + fineBlockSize, + offset=0) tresholdingMask[:,:,z] = remove_small_objects( - imageStack[:,:,z].astype('bool'), - min_size=100, connectivity=1) + imageStack[:,:,z].astype('bool'), + min_size=100, connectivity=1) tresholdingMask[:,:,z] = binary_closing(imageStack[:,:,z], - selem.disk(5)) + selem.disk(5)) tresholdingMask[:,:,z] = skeletonize(imageStack[:,:,z]) # combine masks # return edgeMask + thresholdingMask return thresholdingMask - def _get_nuclei_mask(self, fov: int, channelIndex: int, - filterSigma: float) -> np.ndarray: + def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) @@ -258,10 +267,12 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions())): coarseThresholdingMask = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], - coarseBlockSize, offset=0) + coarseBlockSize, + offset=0) fineThresholdingMask = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], - fineBlockSize, offset=0) + fineBlockSize, + offset=0) thresholdingMask[:,:,z] = coarseThresholdingMask* fineThresholdingMask thresholdingMask[:,:,z] = binary_fill_holes( @@ -281,7 +292,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int, fineHessian = hessian(imageStack[:,:,z]) fineHessianMask[:,:,z] = fineHessian == fineHessian.max() fineHessianMask[:,:,z] = binary_closing(fineHessianMask[:,:,z], - selem.disk(5)) + selem.disk(5)) fineHessianMask[:,:,z] = fineHessianMask[:,:,z]*borderMask fineHessianMask[:,:,z] = binary_fill_holes(fineHessianMask[:,:,z]) @@ -290,10 +301,10 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions())): coarseHessian = hessian(imageStack[:,:,z] - white_tophat(imageStack[:,:,z], - selem.disk(20))) + selem.disk(20))) coarseHessianMask[:,:,z] = coarseHessian == coarseHessian.max() coarseHessianMask[:,:,z] = binary_closing(coarseHessianMask[:,:,z], - selem.disk(5)) + selem.disk(5)) coarseHessianMask[:,:,z] = coarseHessianMask[:,:,z]*borderMask coarseHessianMask[:,:,z] = binary_fill_holes( coarseHessianMask[:,:,z]) @@ -302,6 +313,39 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int, nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask return binary_fill_holes(nucleiMask) + def _generate_markers(self, nucleiMask: np.ndarray, + membraneMask: np.ndarray) -> np.ndarray: + + watershedMarker = np.zeros(nucleiMask.shape) + + for z in range(len(self.dataSet.get_z_positions())): + + # generate areas of sure bg and fg, as well as the area of + # unknown classification + background = sm.dilation(nucleiMask[:,:,z],sm.selem.disk(15)) + membraneDilated = sm.dilation(membraneMask[:,:,z].astype('bool'), + sm.selem.disk(10)) + foreground = sm.erosion(nucleiMask[:,:,z]*~membraneDilated, + sm.selem.disk(5)) + unknown = background*~foreground + + background = np.uint8(background)*255 + foreground = np.uint8(foreground)*255 + unknown = np.uint8(unknown)*255 + + # Marker labelling + ret, markers = cv2.connectedComponents(foreground) + + # Add one to all labels so that sure background is not 0, but 1 + markers = markers+100 + + # Now, mark the region of unknown with zero + markers[unknown==255] = 0 + + watershedMarker[:,:,z] = markers + + return watershedMarker + def _generate_watershed_mask(self, fov: int, channelIndex: int, filterSigma: float) -> np.ndarray: @@ -317,38 +361,6 @@ def _read_and_filter_image_stack(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions()))]) -#-------------------------------------------------------------------------------------------------------------------- -# Generate. As before, use a combination of masks -#-------------------------------------------------------------------------------------------------------------------- -sure_bg_dapi = sm.dilation(foreground,sm.selem.disk(15)) - -mask_wga_dil = sm.dilation(mask_wga,sm.selem.disk(10)) -sure_fg = sm.erosion(foreground*~mask_wga_dil,sm.selem.disk(5)) - -unknown_dapi = sure_bg_dapi*~sure_fg - -sure_bg_dapi = np.uint8(sure_bg_dapi)*255 -sure_fg = np.uint8(sure_fg)*255 -unknown_dapi = np.uint8(unknown_dapi)*255 - - -# Marker labelling -ret, markers = cv2.connectedComponents(sure_fg) - -# Add one to all labels so that sure background is not 0, but 1 -markers_dapi = markers+100 - -# Now, mark the region of unknown with zero -markers_dapi[unknown_dapi==255] = 0 - -# Apply watershed using cv2 -markers_ws_dapi = cv2.watershed(Idapi_inv,markers_dapi) - - - - - - class CleanCellBoundaries(analysistask.ParallelAnalysisTask): ''' A task to construct a network graph where each cell is a node, and overlaps From a8f5051ae1967c721e82173be15144f8913cd14a Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Jan 2020 14:26:39 -0500 Subject: [PATCH 005/419] added method _apply_watershed --- merlin/analysis/segment.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 05ef8455..a8a8fd46 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -183,9 +183,9 @@ def _run_analysis(self, fragmentIndex): membraneMask = self._get_membrane_mask(fragmentIndex, watershedIndex) nucleiMask = self._get_nuclei_mask(fragmentIndex, watershedIndex) - watershedMarkers = self._generate_markers(nucleiMask,membraneMask) - - + watershedMarkers = self._get_watershed_markers(nucleiMask,membraneMask) + watershedOutput = self._apply_watershed(fragmentIndex, watershedIndex, + watershedMarkers) @@ -313,7 +313,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask return binary_fill_holes(nucleiMask) - def _generate_markers(self, nucleiMask: np.ndarray, + def _get_watershed_markers(self, nucleiMask: np.ndarray, membraneMask: np.ndarray) -> np.ndarray: watershedMarker = np.zeros(nucleiMask.shape) @@ -346,8 +346,8 @@ def _generate_markers(self, nucleiMask: np.ndarray, return watershedMarker - def _generate_watershed_mask(self, fov: int, channelIndex: int, - filterSigma: float) -> np.ndarray: + def _apply_watershed(self, fov: int, channelIndex: int, + watershedMarkers: np.ndarray) -> np.ndarray: def _read_and_filter_image_stack(self, fov: int, channelIndex: int, From 5750670d4a207fd59a902c2d8be64da52c39f49b Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Jan 2020 16:33:41 -0500 Subject: [PATCH 006/419] added method _convert_grayscale_to_rgb --- merlin/analysis/segment.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index a8a8fd46..0891af3c 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -346,8 +346,33 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, return watershedMarker + def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: + # create 3D images of 8bit + # https://stackoverflow.com/questions/25485886/how-to-convert-a + # -16-bit-to-an-8-bit-image-in-opencv + + # invert image + uint16Image = 2**16 - uint16Image + + # convert to uint8 + ratio = np.amax(uint16Image) / 256 ; + uint8Image = (uint16Image / ratio).astype('uint8') + + rgbImage = np.zeros((2048,2048,3)) + rgbImage[:,:,0] = uint8Image + rgbImage[:,:,1] = uint8Image + rgbImage[:,:,2] = uint8Image + rgbImage = rgbImage.astype('uint8') + + return rgbImage + def _apply_watershed(self, fov: int, channelIndex: int, watershedMarkers: np.ndarray) -> np.ndarray: + warpTask = self.dataSet.load_analysis_task( + self.parameters['warp_task']) + + imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) + for z in range(len(self.dataSet.get_z_positions()))]) def _read_and_filter_image_stack(self, fov: int, channelIndex: int, From 03973793edc2704d38baaec888e5fcd26692b0a7 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Jan 2020 16:45:05 -0500 Subject: [PATCH 007/419] modify _apply_watershed method --- merlin/analysis/segment.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 0891af3c..ae13b7e4 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -374,6 +374,14 @@ def _apply_watershed(self, fov: int, channelIndex: int, imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) for z in range(len(self.dataSet.get_z_positions()))]) + watershedOutput = np.zeros(watershedMarkers.shape) + for z in range(len(self.dataSet.get_z_positions())): + rgbImage = convert_grayscale_to_rgb(dapiStack[:,:,z]) + watershedOutput[:,:,z] = cv2.watershed(rgbImage, + watershedMarkers[:,:,z]. + astype('int32')) + return watershedOutput + def _read_and_filter_image_stack(self, fov: int, channelIndex: int, filterSigma: float) -> np.ndarray: From 76444abe045b42139e172d021f1abe7b1b9b91b2 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Jan 2020 17:55:02 -0500 Subject: [PATCH 008/419] adding _combine_watershed_z_positions method, starting by pseudocode --- merlin/analysis/segment.py | 61 ++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index ae13b7e4..6a976c37 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -181,15 +181,20 @@ def _run_analysis(self, fragmentIndex): watershedIndex = self.dataSet.get_data_organization() \ .get_data_channel_index(self.parameters['watershed_channel_name']) + # Prepare masks for cv2 watershed membraneMask = self._get_membrane_mask(fragmentIndex, watershedIndex) nucleiMask = self._get_nuclei_mask(fragmentIndex, watershedIndex) watershedMarkers = self._get_watershed_markers(nucleiMask,membraneMask) + + # perform watershed in individual z positions watershedOutput = self._apply_watershed(fragmentIndex, watershedIndex, watershedMarkers) + # combine all z positions in watershed + watershedCombinedOutput = self._combine_watershed_z_positions( + watershedOutput) - - + """ watershedImages = self._read_and_filter_image_stack(fragmentIndex, watershedIndex, 5) seeds = watershed.separate_merged_seeds( @@ -201,6 +206,7 @@ def _run_analysis(self, fragmentIndex): watershedOutput = segmentation.watershed( normalizedWatershed, measure.label(seeds), mask=watershedMask, connectivity=np.ones((3, 3, 3)), watershed_line=True) + """ zPos = np.array(self.dataSet.get_data_organization().get_z_positions()) featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( @@ -239,7 +245,7 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:,:,z] = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], - fineBlockSize, + fineBlockSize, offset=0) tresholdingMask[:,:,z] = remove_small_objects( imageStack[:,:,z].astype('bool'), @@ -267,11 +273,11 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: for z in range(len(self.dataSet.get_z_positions())): coarseThresholdingMask = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], - coarseBlockSize, + coarseBlockSize, offset=0) fineThresholdingMask = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], - fineBlockSize, + fineBlockSize, offset=0) thresholdingMask[:,:,z] = coarseThresholdingMask* fineThresholdingMask @@ -310,17 +316,17 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseHessianMask[:,:,z]) # combine masks - nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask + nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask return binary_fill_holes(nucleiMask) - def _get_watershed_markers(self, nucleiMask: np.ndarray, + def _get_watershed_markers(self, nucleiMask: np.ndarray, membraneMask: np.ndarray) -> np.ndarray: watershedMarker = np.zeros(nucleiMask.shape) for z in range(len(self.dataSet.get_z_positions())): - # generate areas of sure bg and fg, as well as the area of + # generate areas of sure bg and fg, as well as the area of # unknown classification background = sm.dilation(nucleiMask[:,:,z],sm.selem.disk(15)) membraneDilated = sm.dilation(membraneMask[:,:,z].astype('bool'), @@ -347,15 +353,16 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, return watershedMarker def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: - # create 3D images of 8bit - # https://stackoverflow.com/questions/25485886/how-to-convert-a - # -16-bit-to-an-8-bit-image-in-opencv + # cv2 only works in 3D images of 8bit. Make a 3D grayscale by + # using the same grayscale image in each of the rgb channels + # code below based on https://stackoverflow.com/questions/ + # 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv # invert image uint16Image = 2**16 - uint16Image # convert to uint8 - ratio = np.amax(uint16Image) / 256 ; + ratio = np.amax(uint16Image) / 256 uint8Image = (uint16Image / ratio).astype('uint8') rgbImage = np.zeros((2048,2048,3)) @@ -382,7 +389,34 @@ def _apply_watershed(self, fov: int, channelIndex: int, astype('int32')) return watershedOutput + def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) + -> np.ndarray: + """ + PSEUDECODE + + initialize empty array the same size as the watershedOutput array + + start loop from the section farthest from the coverslip (N) + + for each nuclei + get nuclei indexes in sections N - 1 and N + 1 (if applies) + + if there is nuclei in the projection of N into N-1 and N+1 + get the index of the overlaping nuclei in N+1 + + find the most frequent non-zero index in N+1, + + else if projection N -> N+1 has overlaping nuclei + get the indexes of nuclei in N+1 + find the index in N+1, different from zero, that is most frequent + + + relabel_segmentation_stack + clean_segmentation_stack + + """ + """ def _read_and_filter_image_stack(self, fov: int, channelIndex: int, filterSigma: float) -> np.ndarray: filterSize = int(2*np.ceil(2*filterSigma)+1) @@ -392,7 +426,8 @@ def _read_and_filter_image_stack(self, fov: int, channelIndex: int, warpTask.get_aligned_image(fov, channelIndex, z), (filterSize, filterSize), filterSigma) for z in range(len(self.dataSet.get_z_positions()))]) - + """ + class CleanCellBoundaries(analysistask.ParallelAnalysisTask): ''' From 8cb0a229a98d81dc9d46fa6f8c98725208aafa6d Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 24 Jan 2020 17:34:20 -0500 Subject: [PATCH 009/419] expanding _combine_watershed_z_positions and adding _get_overlapping_nuclei method --- merlin/analysis/segment.py | 91 +++++++++++++++++++++++--------------- 1 file changed, 56 insertions(+), 35 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 6a976c37..6f579730 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -383,51 +383,72 @@ def _apply_watershed(self, fov: int, channelIndex: int, watershedOutput = np.zeros(watershedMarkers.shape) for z in range(len(self.dataSet.get_z_positions())): - rgbImage = convert_grayscale_to_rgb(dapiStack[:,:,z]) + rgbImage = _convert_grayscale_to_rgb(dapiStack[:,:,z]) watershedOutput[:,:,z] = cv2.watershed(rgbImage, watershedMarkers[:,:,z]. astype('int32')) return watershedOutput - def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) - -> np.ndarray: - """ - PSEUDECODE + def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, + watershedZ1: np.ndarray, n0: int): + z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) + z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes>100] + + if z1NucleiIndexes.shape[0] > 0: + + # calculate overlap fraction + n0Area = np.count_nonzero(watershedZ0 == n0) + n1Area = np.zeros(len(z1NucleiIndexes)) + overlapArea = np.zeros(len(z1NucleiIndexes)) + + for ii in range(len(z1NucleiIndexes)): + n1 = z1NucleiIndexes[ii] + n1Area[ii] = np.count_nonzero(watershedZ1 == n1) + overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) + *(watershedZ1 == n1)) + + n0OverlapFraction = np.asarray(overlapArea/n0Area) + n1OverlapFraction = np.asarray(overlapArea/n1Area) + index = list(range(len(n0OverlapFraction))) + + # select the nuclei that has the highest fraction in n0 and n1 + r1, r2, indexSorted = zip(*sorted(zip(n0OverlapFraction, + n1OverlapFraction, + index), + reverse=True)) + + if n0OverlapFraction[indexSorted[0]] > 0.2 and + n1OverlapFraction[indexSorted[0]] > 0.5: + return m1NucleiIndexes[indexSorted[0]], + n0OverlapFraction[indexSorted[0]], + n1OverlapFraction[indexSorted[0]] + else: + return False, False, False + else: + return False, False, False - initialize empty array the same size as the watershedOutput array - start loop from the section farthest from the coverslip (N) + def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) + -> np.ndarray: - for each nuclei - get nuclei indexes in sections N - 1 and N + 1 (if applies) + # Initialize empty array with size as watershedOutput array + watershedCombinedZ = np.zeros(watershedOutput.shape) - if there is nuclei in the projection of N into N-1 and N+1 - get the index of the overlaping nuclei in N+1 - - find the most frequent non-zero index in N+1, - - - else if projection N -> N+1 has overlaping nuclei - get the indexes of nuclei in N+1 - find the index in N+1, different from zero, that is most frequent - - - relabel_segmentation_stack - clean_segmentation_stack - - """ - """ - def _read_and_filter_image_stack(self, fov: int, channelIndex: int, - filterSigma: float) -> np.ndarray: - filterSize = int(2*np.ceil(2*filterSigma)+1) - warpTask = self.dataSet.load_analysis_task( - self.parameters['warp_task']) - return np.array([cv2.GaussianBlur( - warpTask.get_aligned_image(fov, channelIndex, z), - (filterSize, filterSize), filterSigma) - for z in range(len(self.dataSet.get_z_positions()))]) - """ + # copy the mask of the section farthest to the coverslip + watershedCombinedZ[:,:,-1] = watershedOutput[:,:,-1] + # starting far from coverslip + for z in range(len(self.dataSet.get_z_positions())-1,0,-1): + zNucleiIndex = np.unique(watershedOutput[:,:,z])[ + np.unique(watershedOutput[:,:,z])>100] + + for n0 in zNucleiIndex: # for each nuclei N(Z) in Z + n1,f0,f1 = _get_overlapping_nuclei(watershedCombinedZ[:,:,z], + watershedOutput[:,:,z-1],n0) + if n1: + watershedCombinedZ[:,:,z-1][watershedOutput[:,:,z-1] == n1] + = n0 + return watershedCombinedZ class CleanCellBoundaries(analysistask.ParallelAnalysisTask): ''' From 26eb960dc512158fe702f02adb1189420ca9ddf7 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 24 Jan 2020 17:45:42 -0500 Subject: [PATCH 010/419] pep8 compliance --- merlin/analysis/segment.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 6f579730..b8c60549 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -234,7 +234,7 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: low_threshold=0.5, high_threshold=0.8) edgeMask[:,:,z] = binary_closing(edgeMask[:,:,z],selem.disk(5)) edgeMask[:,:,z] = remove_small_objects( - edgeMask[:,:,z].astype('bool'), + edgeMask[:,:,z].astype('bool'), min_size=100, connectivity=1) edgeMask[:,:,z] = skeletonize(edgeMask[:,:,z]) """ @@ -287,7 +287,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: # generate border mask, necessary to avoid making a single # connected component when using binary_fill_holes below borderMask = np.zeros((2048,2048)) - borderMask[25:2023,25:2023] = 1 + borderMask[25:2023,25:2023] = 1 # TODO - use the image size variable for borderMask @@ -394,7 +394,7 @@ def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes>100] - if z1NucleiIndexes.shape[0] > 0: + if z1NucleiIndexes.shape[0] > 0: # calculate overlap fraction n0Area = np.count_nonzero(watershedZ0 == n0) @@ -417,36 +417,38 @@ def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, index), reverse=True)) - if n0OverlapFraction[indexSorted[0]] > 0.2 and + if n0OverlapFraction[indexSorted[0]] > 0.2 and n1OverlapFraction[indexSorted[0]] > 0.5: - return m1NucleiIndexes[indexSorted[0]], - n0OverlapFraction[indexSorted[0]], + return m1NucleiIndexes[indexSorted[0]], + n0OverlapFraction[indexSorted[0]], n1OverlapFraction[indexSorted[0]] else: - return False, False, False + return False, False, False else: return False, False, False def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) -> np.ndarray: - + # TO DO: this implementation is very rough, needs to be improved. + # good just for testing purposes + # Initialize empty array with size as watershedOutput array watershedCombinedZ = np.zeros(watershedOutput.shape) - # copy the mask of the section farthest to the coverslip + # copy the mask of the section farthest to the coverslip watershedCombinedZ[:,:,-1] = watershedOutput[:,:,-1] # starting far from coverslip - for z in range(len(self.dataSet.get_z_positions())-1,0,-1): + for z in range(len(self.dataSet.get_z_positions())-1,0,-1): zNucleiIndex = np.unique(watershedOutput[:,:,z])[ np.unique(watershedOutput[:,:,z])>100] for n0 in zNucleiIndex: # for each nuclei N(Z) in Z n1,f0,f1 = _get_overlapping_nuclei(watershedCombinedZ[:,:,z], - watershedOutput[:,:,z-1],n0) + watershedOutput[:,:,z-1],n0) if n1: - watershedCombinedZ[:,:,z-1][watershedOutput[:,:,z-1] == n1] + watershedCombinedZ[:,:,z-1][watershedOutput[:,:,z-1] == n1] = n0 return watershedCombinedZ From a0e6f0ca999e13ce803edf582e72cd0e9c14e180 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 24 Jan 2020 17:55:30 -0500 Subject: [PATCH 011/419] pep8 compliance --- merlin/analysis/segment.py | 58 +++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index b8c60549..7f6db407 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -9,7 +9,7 @@ from shapely import geometry from typing import List, Dict from scipy.spatial import cKDTree -from scipy.ndimage.morphology import binary_fill_holes +from scipy.ndimage.morphology import binary_fill_holes from merlin.core import dataset from merlin.core import analysistask @@ -123,18 +123,19 @@ def _read_and_filter_image_stack(self, fov: int, channelIndex: int, (filterSize, filterSize), filterSigma) for z in range(len(self.dataSet.get_z_positions()))]) + class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): """ An analysis task that determines the boundaries of features in the - image data in each field of view using a watershed algorithm + image data in each field of view using a watershed algorithm implemented in CV2. - A tutorial explaining the general scheme of the method can be + A tutorial explaining the general scheme of the method can be found in https://opencv-python-tutroals.readthedocs.io/en/latest/ - py_tutorials/py_imgproc/py_watershed/py_watershed.html. + py_tutorials/py_imgproc/py_watershed/py_watershed.html. - The watershed segmentation is performed in each z-position + The watershed segmentation is performed in each z-position independently and combined into 3D objects in a later step Since each field of view is analyzed individually, the segmentation @@ -199,7 +200,8 @@ def _run_analysis(self, fragmentIndex): watershedIndex, 5) seeds = watershed.separate_merged_seeds( watershed.extract_seeds(seedImages)) - normalizedWatershed, watershedMask = watershed.prepare_watershed_images( + normalizedWatershed, watershedMask = + watershed.prepare_watershed_images( watershedImages) seeds[np.invert(watershedMask)] = 0 @@ -271,12 +273,12 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseBlockSize = 241 fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = imageStack[:,:,z] > - threshold_local(imageStack[:,:,z], + coarseThresholdingMask = imageStack[:,:,z] + >threshold_local(imageStack[:,:,z], coarseBlockSize, offset=0) - fineThresholdingMask = imageStack[:,:,z] > - threshold_local(imageStack[:,:,z], + fineThresholdingMask = imageStack[:,:,z] + > threshold_local(imageStack[:,:,z], fineBlockSize, offset=0) thresholdingMask[:,:,z] = coarseThresholdingMask* @@ -291,7 +293,6 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: # TODO - use the image size variable for borderMask - # generate nuclei mask from hessian, fine fineHessianMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): @@ -301,7 +302,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: selem.disk(5)) fineHessianMask[:,:,z] = fineHessianMask[:,:,z]*borderMask fineHessianMask[:,:,z] = binary_fill_holes(fineHessianMask[:,:,z]) - + # generate dapi mask from hessian, coarse coarseHessianMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): @@ -314,18 +315,17 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseHessianMask[:,:,z] = coarseHessianMask[:,:,z]*borderMask coarseHessianMask[:,:,z] = binary_fill_holes( coarseHessianMask[:,:,z]) - + # combine masks nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask return binary_fill_holes(nucleiMask) def _get_watershed_markers(self, nucleiMask: np.ndarray, membraneMask: np.ndarray) -> np.ndarray: - watershedMarker = np.zeros(nucleiMask.shape) for z in range(len(self.dataSet.get_z_positions())): - + # generate areas of sure bg and fg, as well as the area of # unknown classification background = sm.dilation(nucleiMask[:,:,z],sm.selem.disk(15)) @@ -334,11 +334,11 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, foreground = sm.erosion(nucleiMask[:,:,z]*~membraneDilated, sm.selem.disk(5)) unknown = background*~foreground - + background = np.uint8(background)*255 foreground = np.uint8(foreground)*255 unknown = np.uint8(unknown)*255 - + # Marker labelling ret, markers = cv2.connectedComponents(foreground) @@ -357,20 +357,20 @@ def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: # using the same grayscale image in each of the rgb channels # code below based on https://stackoverflow.com/questions/ # 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv - + # invert image uint16Image = 2**16 - uint16Image - + # convert to uint8 ratio = np.amax(uint16Image) / 256 uint8Image = (uint16Image / ratio).astype('uint8') - + rgbImage = np.zeros((2048,2048,3)) rgbImage[:,:,0] = uint8Image rgbImage[:,:,1] = uint8Image rgbImage[:,:,2] = uint8Image rgbImage = rgbImage.astype('uint8') - + return rgbImage def _apply_watershed(self, fov: int, channelIndex: int, @@ -393,30 +393,30 @@ def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, watershedZ1: np.ndarray, n0: int): z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes>100] - + if z1NucleiIndexes.shape[0] > 0: - + # calculate overlap fraction n0Area = np.count_nonzero(watershedZ0 == n0) n1Area = np.zeros(len(z1NucleiIndexes)) overlapArea = np.zeros(len(z1NucleiIndexes)) - + for ii in range(len(z1NucleiIndexes)): n1 = z1NucleiIndexes[ii] n1Area[ii] = np.count_nonzero(watershedZ1 == n1) overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) *(watershedZ1 == n1)) - + n0OverlapFraction = np.asarray(overlapArea/n0Area) n1OverlapFraction = np.asarray(overlapArea/n1Area) index = list(range(len(n0OverlapFraction))) - + # select the nuclei that has the highest fraction in n0 and n1 r1, r2, indexSorted = zip(*sorted(zip(n0OverlapFraction, n1OverlapFraction, index), reverse=True)) - + if n0OverlapFraction[indexSorted[0]] > 0.2 and n1OverlapFraction[indexSorted[0]] > 0.5: return m1NucleiIndexes[indexSorted[0]], @@ -430,7 +430,7 @@ def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) -> np.ndarray: - # TO DO: this implementation is very rough, needs to be improved. + # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes # Initialize empty array with size as watershedOutput array @@ -443,7 +443,7 @@ def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) for z in range(len(self.dataSet.get_z_positions())-1,0,-1): zNucleiIndex = np.unique(watershedOutput[:,:,z])[ np.unique(watershedOutput[:,:,z])>100] - + for n0 in zNucleiIndex: # for each nuclei N(Z) in Z n1,f0,f1 = _get_overlapping_nuclei(watershedCombinedZ[:,:,z], watershedOutput[:,:,z-1],n0) From 1eb718bcdaed916a0b8e2de4194d2059c6661791 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 24 Jan 2020 18:08:59 -0500 Subject: [PATCH 012/419] pep8 compliance --- merlin/analysis/segment.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 7f6db407..a3dc8230 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -137,7 +137,7 @@ class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): The watershed segmentation is performed in each z-position independently and combined into 3D objects in a later step - + Since each field of view is analyzed individually, the segmentation results should be cleaned in order to merge cells that cross the field of view boundary. @@ -181,12 +181,13 @@ def _run_analysis(self, fragmentIndex): watershedIndex = self.dataSet.get_data_organization() \ .get_data_channel_index(self.parameters['watershed_channel_name']) - + # Prepare masks for cv2 watershed membraneMask = self._get_membrane_mask(fragmentIndex, watershedIndex) nucleiMask = self._get_nuclei_mask(fragmentIndex, watershedIndex) - watershedMarkers = self._get_watershed_markers(nucleiMask,membraneMask) - + watershedMarkers = self._get_watershed_markers(nucleiMask, + membraneMask) + # perform watershed in individual z positions watershedOutput = self._apply_watershed(fragmentIndex, watershedIndex, watershedMarkers) From 89663c438070b909b7033169a6985bed0cca98ec Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 10:58:37 -0500 Subject: [PATCH 013/419] pep8 compliance --- merlin/analysis/segment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index a3dc8230..d9ff8b84 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -186,7 +186,7 @@ def _run_analysis(self, fragmentIndex): membraneMask = self._get_membrane_mask(fragmentIndex, watershedIndex) nucleiMask = self._get_nuclei_mask(fragmentIndex, watershedIndex) watershedMarkers = self._get_watershed_markers(nucleiMask, - membraneMask) + membraneMask) # perform watershed in individual z positions watershedOutput = self._apply_watershed(fragmentIndex, watershedIndex, From 984f73559722bfe451e0e69ddcedac405b696967 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:04:04 -0500 Subject: [PATCH 014/419] pep8 compliance --- merlin/analysis/segment.py | 98 +++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index d9ff8b84..145a80d7 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -226,36 +226,36 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) for z in range(len(self.dataSet.get_z_positions()))]) - + # generate mask based on edge detection """ edgeMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): - edgeMask[:,:,z] = canny( - white_tophat(imageStack[:,:,z], selem.disk(10)), + edgeMask[:, :, z] = canny( + white_tophat(imageStack[:, :, z], selem.disk(10)), sigma=2, use_quantiles=True, low_threshold=0.5, high_threshold=0.8) - edgeMask[:,:,z] = binary_closing(edgeMask[:,:,z],selem.disk(5)) - edgeMask[:,:,z] = remove_small_objects( - edgeMask[:,:,z].astype('bool'), + edgeMask[:, :, z] = binary_closing(edgeMask[:, :, z],selem.disk(5)) + edgeMask[:, :, z] = remove_small_objects( + edgeMask[:, :, z].astype('bool'), min_size=100, connectivity=1) - edgeMask[:,:,z] = skeletonize(edgeMask[:,:,z]) + edgeMask[:, :, z] = skeletonize(edgeMask[:, :, z]) """ # generate mask based on thresholding tresholdingMask = np.zeros(imageStack.shape) fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - tresholdingMask[:,:,z] = imageStack[:,:,z] > - threshold_local(imageStack[:,:,z], + tresholdingMask[:, :, z] = imageStack[:, :, z] > + threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) - tresholdingMask[:,:,z] = remove_small_objects( - imageStack[:,:,z].astype('bool'), + tresholdingMask[:, :, z] = remove_small_objects( + imageStack[:, :, z].astype('bool'), min_size=100, connectivity=1) - tresholdingMask[:,:,z] = binary_closing(imageStack[:,:,z], + tresholdingMask[:, :, z] = binary_closing(imageStack[:, :, z], selem.disk(5)) - tresholdingMask[:,:,z] = skeletonize(imageStack[:,:,z]) + tresholdingMask[:, :, z] = skeletonize(imageStack[:, :, z]) # combine masks # return edgeMask + thresholdingMask @@ -274,18 +274,18 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseBlockSize = 241 fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = imageStack[:,:,z] - >threshold_local(imageStack[:,:,z], + coarseThresholdingMask = imageStack[:, :, z] + >threshold_local(imageStack[:, :, z], coarseBlockSize, offset=0) - fineThresholdingMask = imageStack[:,:,z] - > threshold_local(imageStack[:,:,z], + fineThresholdingMask = imageStack[:, :, z] + > threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) - thresholdingMask[:,:,z] = coarseThresholdingMask* + thresholdingMask[:, :, z] = coarseThresholdingMask* fineThresholdingMask - thresholdingMask[:,:,z] = binary_fill_holes( - thresholdingMask[:,:,z]) + thresholdingMask[:, :, z] = binary_fill_holes( + thresholdingMask[:, :, z]) # generate border mask, necessary to avoid making a single # connected component when using binary_fill_holes below @@ -297,25 +297,25 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: # generate nuclei mask from hessian, fine fineHessianMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): - fineHessian = hessian(imageStack[:,:,z]) - fineHessianMask[:,:,z] = fineHessian == fineHessian.max() - fineHessianMask[:,:,z] = binary_closing(fineHessianMask[:,:,z], + fineHessian = hessian(imageStack[:, :, z]) + fineHessianMask[:, :, z] = fineHessian == fineHessian.max() + fineHessianMask[:, :, z] = binary_closing(fineHessianMask[:, :, z], selem.disk(5)) - fineHessianMask[:,:,z] = fineHessianMask[:,:,z]*borderMask - fineHessianMask[:,:,z] = binary_fill_holes(fineHessianMask[:,:,z]) + fineHessianMask[:, :, z] = fineHessianMask[:, :, z]*borderMask + fineHessianMask[:, :, z] = binary_fill_holes(fineHessianMask[:, :, z]) # generate dapi mask from hessian, coarse coarseHessianMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): - coarseHessian = hessian(imageStack[:,:,z] - - white_tophat(imageStack[:,:,z], + coarseHessian = hessian(imageStack[:, :, z] - + white_tophat(imageStack[:, :, z], selem.disk(20))) - coarseHessianMask[:,:,z] = coarseHessian == coarseHessian.max() - coarseHessianMask[:,:,z] = binary_closing(coarseHessianMask[:,:,z], + coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() + coarseHessianMask[:, :, z] = binary_closing(coarseHessianMask[:, :, z], selem.disk(5)) - coarseHessianMask[:,:,z] = coarseHessianMask[:,:,z]*borderMask - coarseHessianMask[:,:,z] = binary_fill_holes( - coarseHessianMask[:,:,z]) + coarseHessianMask[:, :, z] = coarseHessianMask[:, :, z]*borderMask + coarseHessianMask[:, :, z] = binary_fill_holes( + coarseHessianMask[:, :, z]) # combine masks nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask @@ -329,10 +329,10 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, # generate areas of sure bg and fg, as well as the area of # unknown classification - background = sm.dilation(nucleiMask[:,:,z],sm.selem.disk(15)) - membraneDilated = sm.dilation(membraneMask[:,:,z].astype('bool'), + background = sm.dilation(nucleiMask[:, :, z],sm.selem.disk(15)) + membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), sm.selem.disk(10)) - foreground = sm.erosion(nucleiMask[:,:,z]*~membraneDilated, + foreground = sm.erosion(nucleiMask[:, :, z]*~membraneDilated, sm.selem.disk(5)) unknown = background*~foreground @@ -349,7 +349,7 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, # Now, mark the region of unknown with zero markers[unknown==255] = 0 - watershedMarker[:,:,z] = markers + watershedMarker[:, :, z] = markers return watershedMarker @@ -367,9 +367,9 @@ def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: uint8Image = (uint16Image / ratio).astype('uint8') rgbImage = np.zeros((2048,2048,3)) - rgbImage[:,:,0] = uint8Image - rgbImage[:,:,1] = uint8Image - rgbImage[:,:,2] = uint8Image + rgbImage[:, :, 0] = uint8Image + rgbImage[:, :, 1] = uint8Image + rgbImage[:, :, 2] = uint8Image rgbImage = rgbImage.astype('uint8') return rgbImage @@ -384,9 +384,9 @@ def _apply_watershed(self, fov: int, channelIndex: int, watershedOutput = np.zeros(watershedMarkers.shape) for z in range(len(self.dataSet.get_z_positions())): - rgbImage = _convert_grayscale_to_rgb(dapiStack[:,:,z]) - watershedOutput[:,:,z] = cv2.watershed(rgbImage, - watershedMarkers[:,:,z]. + rgbImage = _convert_grayscale_to_rgb(dapiStack[:, :, z]) + watershedOutput[:, :, z] = cv2.watershed(rgbImage, + watershedMarkers[:, :, z]. astype('int32')) return watershedOutput @@ -438,18 +438,18 @@ def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) watershedCombinedZ = np.zeros(watershedOutput.shape) # copy the mask of the section farthest to the coverslip - watershedCombinedZ[:,:,-1] = watershedOutput[:,:,-1] + watershedCombinedZ[:, :, -1] = watershedOutput[:, :, -1] # starting far from coverslip - for z in range(len(self.dataSet.get_z_positions())-1,0,-1): - zNucleiIndex = np.unique(watershedOutput[:,:,z])[ - np.unique(watershedOutput[:,:,z])>100] + for z in range(len(self.dataSet.get_z_positions())-1, 0, -1): + zNucleiIndex = np.unique(watershedOutput[:, :, z])[ + np.unique(watershedOutput[:, :, z])>100] for n0 in zNucleiIndex: # for each nuclei N(Z) in Z - n1,f0,f1 = _get_overlapping_nuclei(watershedCombinedZ[:,:,z], - watershedOutput[:,:,z-1],n0) + n1,f0,f1 = _get_overlapping_nuclei(watershedCombinedZ[:, :, z], + watershedOutput[:, :, z-1],n0) if n1: - watershedCombinedZ[:,:,z-1][watershedOutput[:,:,z-1] == n1] + watershedCombinedZ[:, :, z-1][watershedOutput[:, :, z-1] == n1] = n0 return watershedCombinedZ From 4dcd8d851d0d222ed8b788fbb9c9da6ee963f849 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:09:06 -0500 Subject: [PATCH 015/419] pep8 compliance --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 145a80d7..bd79179b 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -225,8 +225,8 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: self.parameters['warp_task']) imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet.get_z_positions()))]) - + for z in range(len(self.dataSet. + get_z_positions()))]) # generate mask based on edge detection """ edgeMask = np.zeros(imageStack.shape) From e6114f5ac075c03137cac1ad4e51721f3f8ad752 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:11:58 -0500 Subject: [PATCH 016/419] pep8 compliance --- merlin/analysis/segment.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index bd79179b..d81a3871 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -225,7 +225,7 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: self.parameters['warp_task']) imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet. + for z in range(len(self.dataSet. get_z_positions()))]) # generate mask based on edge detection """ @@ -247,14 +247,12 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:, :, z] = imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], - fineBlockSize, - offset=0) + threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) tresholdingMask[:, :, z] = remove_small_objects( - imageStack[:, :, z].astype('bool'), - min_size=100, connectivity=1) + imageStack[:, :, z].astype('bool'), min_size=100, + connectivity=1) tresholdingMask[:, :, z] = binary_closing(imageStack[:, :, z], - selem.disk(5)) + selem.disk(5)) tresholdingMask[:, :, z] = skeletonize(imageStack[:, :, z]) # combine masks From a745adc79998b060dd2790fb85e65ab87c25ca75 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:12:48 -0500 Subject: [PATCH 017/419] pep8 compliance --- merlin/analysis/segment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index d81a3871..d93e0980 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -225,7 +225,7 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: self.parameters['warp_task']) imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet. + for z in range(len(self.dataSet. get_z_positions()))]) # generate mask based on edge detection """ From 05a9b7a8c581af2cd7b5f3dcb815db312cdc9ee1 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:13:39 -0500 Subject: [PATCH 018/419] pep8 compliance --- merlin/analysis/segment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index d93e0980..93105718 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -225,7 +225,7 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: self.parameters['warp_task']) imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet. + for z in range(len(self.dataSet. get_z_positions()))]) # generate mask based on edge detection """ From 19ea6c4dbe7c38de488674e058c43f79cf17a5bb Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:15:00 -0500 Subject: [PATCH 019/419] pep8 compliance --- merlin/analysis/segment.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 93105718..4a4db4f0 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -226,7 +226,7 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) for z in range(len(self.dataSet. - get_z_positions()))]) + get_z_positions()))]) # generate mask based on edge detection """ edgeMask = np.zeros(imageStack.shape) @@ -247,7 +247,9 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:, :, z] = imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) + threshold_local(imageStack[:, :, z], + fineBlockSize, + offset=0) tresholdingMask[:, :, z] = remove_small_objects( imageStack[:, :, z].astype('bool'), min_size=100, connectivity=1) From b0f75511c10bcd4dd71f683941093acf7411a3bf Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:15:52 -0500 Subject: [PATCH 020/419] pep8 compliance --- merlin/analysis/segment.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 4a4db4f0..11f4b7a7 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -247,9 +247,9 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:, :, z] = imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], - fineBlockSize, - offset=0) + threshold_local(imageStack[:, :, z], + fineBlockSize, + offset=0) tresholdingMask[:, :, z] = remove_small_objects( imageStack[:, :, z].astype('bool'), min_size=100, connectivity=1) From 5859780f79469fa71760d5f025f231565c3b364d Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:19:12 -0500 Subject: [PATCH 021/419] pep8 compliance --- merlin/analysis/segment.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 11f4b7a7..f021cc45 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -247,14 +247,14 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:, :, z] = imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], - fineBlockSize, - offset=0) + threshold_local(imageStack[:, :, z], + fineBlockSize, + offset=0) tresholdingMask[:, :, z] = remove_small_objects( - imageStack[:, :, z].astype('bool'), min_size=100, + imageStack[:, :, z].astype('bool'), min_size=100, connectivity=1) tresholdingMask[:, :, z] = binary_closing(imageStack[:, :, z], - selem.disk(5)) + selem.disk(5)) tresholdingMask[:, :, z] = skeletonize(imageStack[:, :, z]) # combine masks @@ -267,7 +267,8 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: self.parameters['warp_task']) imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet.get_z_positions()))]) + for z in range(len(self.dataSet. + get_z_positions()))]) # generate nuclei mask based on thresholding thresholdingMask = np.zeros(imageStack.shape) From a404474f8292903bed71b7adeaa147b3992f654b Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:27:50 -0500 Subject: [PATCH 022/419] pep8 compliance --- merlin/analysis/segment.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index f021cc45..680dada5 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -247,14 +247,15 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:, :, z] = imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], - fineBlockSize, - offset=0) + threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) + tresholdingMask[:, :, z] = remove_small_objects( imageStack[:, :, z].astype('bool'), min_size=100, connectivity=1) + tresholdingMask[:, :, z] = binary_closing(imageStack[:, :, z], - selem.disk(5)) + selem.disk(5)) + tresholdingMask[:, :, z] = skeletonize(imageStack[:, :, z]) # combine masks @@ -290,8 +291,8 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: # generate border mask, necessary to avoid making a single # connected component when using binary_fill_holes below - borderMask = np.zeros((2048,2048)) - borderMask[25:2023,25:2023] = 1 + borderMask = np.zeros((2048, 2048)) + borderMask[25:2023, 25:2023] = 1 # TODO - use the image size variable for borderMask @@ -330,7 +331,7 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, # generate areas of sure bg and fg, as well as the area of # unknown classification - background = sm.dilation(nucleiMask[:, :, z],sm.selem.disk(15)) + background = sm.dilation(nucleiMask[:, :, z], sm.selem.disk(15)) membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), sm.selem.disk(10)) foreground = sm.erosion(nucleiMask[:, :, z]*~membraneDilated, @@ -367,7 +368,7 @@ def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: ratio = np.amax(uint16Image) / 256 uint8Image = (uint16Image / ratio).astype('uint8') - rgbImage = np.zeros((2048,2048,3)) + rgbImage = np.zeros((2048, 2048, 3)) rgbImage[:, :, 0] = uint8Image rgbImage[:, :, 1] = uint8Image rgbImage[:, :, 2] = uint8Image From 88941c87c5a552b42989be21600963a2493dd966 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:30:30 -0500 Subject: [PATCH 023/419] pep8 compliance --- merlin/analysis/segment.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 680dada5..c2ef4b29 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -248,14 +248,14 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:, :, z] = imageStack[:, :, z] > threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) - + tresholdingMask[:, :, z] = remove_small_objects( imageStack[:, :, z].astype('bool'), min_size=100, connectivity=1) - + tresholdingMask[:, :, z] = binary_closing(imageStack[:, :, z], - selem.disk(5)) - + selem.disk(5)) + tresholdingMask[:, :, z] = skeletonize(imageStack[:, :, z]) # combine masks @@ -276,16 +276,15 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseBlockSize = 241 fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = imageStack[:, :, z] - >threshold_local(imageStack[:, :, z], - coarseBlockSize, - offset=0) - fineThresholdingMask = imageStack[:, :, z] - > threshold_local(imageStack[:, :, z], - fineBlockSize, - offset=0) + coarseThresholdingMask = imageStack[:, :, z] > + threshold_local(imageStack[:, :, z], coarseBlockSize, offset=0) + + fineThresholdingMask = imageStack[:, :, z] > + threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) + thresholdingMask[:, :, z] = coarseThresholdingMask* fineThresholdingMask + thresholdingMask[:, :, z] = binary_fill_holes( thresholdingMask[:, :, z]) From 378d358e4dcce5a36f7a271bef124179a0222b6b Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:40:24 -0500 Subject: [PATCH 024/419] pep8 compliance --- merlin/analysis/segment.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index c2ef4b29..3412da46 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -248,14 +248,11 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:, :, z] = imageStack[:, :, z] > threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) - tresholdingMask[:, :, z] = remove_small_objects( imageStack[:, :, z].astype('bool'), min_size=100, connectivity=1) - tresholdingMask[:, :, z] = binary_closing(imageStack[:, :, z], selem.disk(5)) - tresholdingMask[:, :, z] = skeletonize(imageStack[:, :, z]) # combine masks @@ -276,15 +273,12 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseBlockSize = 241 fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = imageStack[:, :, z] > + coarseThresholdingMask = imageStack[:, :, z] > threshold_local(imageStack[:, :, z], coarseBlockSize, offset=0) - - fineThresholdingMask = imageStack[:, :, z] > + fineThresholdingMask = imageStack[:, :, z] > threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) - - thresholdingMask[:, :, z] = coarseThresholdingMask* - fineThresholdingMask - + thresholdingMask[:, :, z] = coarseThresholdingMask * + fineThresholdingMask thresholdingMask[:, :, z] = binary_fill_holes( thresholdingMask[:, :, z]) @@ -301,9 +295,10 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: fineHessian = hessian(imageStack[:, :, z]) fineHessianMask[:, :, z] = fineHessian == fineHessian.max() fineHessianMask[:, :, z] = binary_closing(fineHessianMask[:, :, z], - selem.disk(5)) + selem.disk(5)) fineHessianMask[:, :, z] = fineHessianMask[:, :, z]*borderMask - fineHessianMask[:, :, z] = binary_fill_holes(fineHessianMask[:, :, z]) + fineHessianMask[:, :, z] = binary_fill_holes( + fineHessianMask[:, :, z]) # generate dapi mask from hessian, coarse coarseHessianMask = np.zeros(imageStack.shape) From 10fb2e0c77d142f5ad064b9099278e9106b794cc Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:44:45 -0500 Subject: [PATCH 025/419] pep8 compliance --- merlin/analysis/segment.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 3412da46..d5e81c49 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -277,7 +277,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: threshold_local(imageStack[:, :, z], coarseBlockSize, offset=0) fineThresholdingMask = imageStack[:, :, z] > threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) - thresholdingMask[:, :, z] = coarseThresholdingMask * + thresholdingMask[:, :, z] = coarseThresholdingMask * fineThresholdingMask thresholdingMask[:, :, z] = binary_fill_holes( thresholdingMask[:, :, z]) @@ -307,8 +307,8 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: white_tophat(imageStack[:, :, z], selem.disk(20))) coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() - coarseHessianMask[:, :, z] = binary_closing(coarseHessianMask[:, :, z], - selem.disk(5)) + coarseHessianMask[:, :, z] = binary_closing( + coarseHessianMask[:, :, z], selem.disk(5)) coarseHessianMask[:, :, z] = coarseHessianMask[:, :, z]*borderMask coarseHessianMask[:, :, z] = binary_fill_holes( coarseHessianMask[:, :, z]) @@ -326,10 +326,10 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, # generate areas of sure bg and fg, as well as the area of # unknown classification background = sm.dilation(nucleiMask[:, :, z], sm.selem.disk(15)) - membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), - sm.selem.disk(10)) + membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), + sm.selem.disk(10)) foreground = sm.erosion(nucleiMask[:, :, z]*~membraneDilated, - sm.selem.disk(5)) + sm.selem.disk(5)) unknown = background*~foreground background = np.uint8(background)*255 From f3865f30cd2fedd9a721b3a7acb7d2da14438d2a Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:48:35 -0500 Subject: [PATCH 026/419] pep8 compliance --- merlin/analysis/segment.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index d5e81c49..2ba09e9f 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -318,7 +318,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: return binary_fill_holes(nucleiMask) def _get_watershed_markers(self, nucleiMask: np.ndarray, - membraneMask: np.ndarray) -> np.ndarray: + membraneMask: np.ndarray) -> np.ndarray: watershedMarker = np.zeros(nucleiMask.shape) for z in range(len(self.dataSet.get_z_positions())): @@ -327,14 +327,14 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, # unknown classification background = sm.dilation(nucleiMask[:, :, z], sm.selem.disk(15)) membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), - sm.selem.disk(10)) + sm.selem.disk(10)) foreground = sm.erosion(nucleiMask[:, :, z]*~membraneDilated, - sm.selem.disk(5)) + sm.selem.disk(5)) unknown = background*~foreground background = np.uint8(background)*255 foreground = np.uint8(foreground)*255 - unknown = np.uint8(unknown)*255 + unknown = np.uint8(unknown)*255 # Marker labelling ret, markers = cv2.connectedComponents(foreground) @@ -343,7 +343,7 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, markers = markers+100 # Now, mark the region of unknown with zero - markers[unknown==255] = 0 + markers[unknown == 255] = 0 watershedMarker[:, :, z] = markers @@ -371,12 +371,13 @@ def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: return rgbImage def _apply_watershed(self, fov: int, channelIndex: int, - watershedMarkers: np.ndarray) -> np.ndarray: - warpTask = self.dataSet.load_analysis_task( + watershedMarkers: np.ndarray) -> np.ndarray: + warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet.get_z_positions()))]) + for z in range(len(self.dataSet. + get_z_positions()))]) watershedOutput = np.zeros(watershedMarkers.shape) for z in range(len(self.dataSet.get_z_positions())): From de7fa260d161a0aaaf272cf93ca10297587b9f2d Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:53:48 -0500 Subject: [PATCH 027/419] pep8 compliance --- merlin/analysis/segment.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 2ba09e9f..b643ab7f 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -318,7 +318,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: return binary_fill_holes(nucleiMask) def _get_watershed_markers(self, nucleiMask: np.ndarray, - membraneMask: np.ndarray) -> np.ndarray: + membraneMask: np.ndarray) -> np.ndarray: watershedMarker = np.zeros(nucleiMask.shape) for z in range(len(self.dataSet.get_z_positions())): @@ -371,7 +371,7 @@ def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: return rgbImage def _apply_watershed(self, fov: int, channelIndex: int, - watershedMarkers: np.ndarray) -> np.ndarray: + watershedMarkers: np.ndarray) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) @@ -383,18 +383,18 @@ def _apply_watershed(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions())): rgbImage = _convert_grayscale_to_rgb(dapiStack[:, :, z]) watershedOutput[:, :, z] = cv2.watershed(rgbImage, - watershedMarkers[:, :, z]. - astype('int32')) + watershedMarkers[:, :, z]. + astype('int32')) return watershedOutput - def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, - watershedZ1: np.ndarray, n0: int): + def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, + watershedZ1: np.ndarray, n0: int): z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) - z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes>100] + z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes > 100] if z1NucleiIndexes.shape[0] > 0: - # calculate overlap fraction + # calculate overlap fraction n0Area = np.count_nonzero(watershedZ0 == n0) n1Area = np.zeros(len(z1NucleiIndexes)) overlapArea = np.zeros(len(z1NucleiIndexes)) @@ -402,8 +402,8 @@ def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, for ii in range(len(z1NucleiIndexes)): n1 = z1NucleiIndexes[ii] n1Area[ii] = np.count_nonzero(watershedZ1 == n1) - overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) - *(watershedZ1 == n1)) + overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * + (watershedZ1 == n1)) n0OverlapFraction = np.asarray(overlapArea/n0Area) n1OverlapFraction = np.asarray(overlapArea/n1Area) @@ -415,11 +415,11 @@ def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, index), reverse=True)) - if n0OverlapFraction[indexSorted[0]] > 0.2 and + if n0OverlapFraction[indexSorted[0]] > 0.2 and n1OverlapFraction[indexSorted[0]] > 0.5: return m1NucleiIndexes[indexSorted[0]], - n0OverlapFraction[indexSorted[0]], - n1OverlapFraction[indexSorted[0]] + n0OverlapFraction[indexSorted[0]], + n1OverlapFraction[indexSorted[0]] else: return False, False, False else: From 0f01da6e38b285b36c1f1a995ab6f128c9ad7222 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:56:41 -0500 Subject: [PATCH 028/419] pep8 compliance --- merlin/analysis/segment.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index b643ab7f..441ecbb6 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -318,7 +318,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: return binary_fill_holes(nucleiMask) def _get_watershed_markers(self, nucleiMask: np.ndarray, - membraneMask: np.ndarray) -> np.ndarray: + membraneMask: np.ndarray) -> np.ndarray: watershedMarker = np.zeros(nucleiMask.shape) for z in range(len(self.dataSet.get_z_positions())): @@ -371,7 +371,7 @@ def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: return rgbImage def _apply_watershed(self, fov: int, channelIndex: int, - watershedMarkers: np.ndarray) -> np.ndarray: + watershedMarkers: np.ndarray) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) @@ -387,8 +387,8 @@ def _apply_watershed(self, fov: int, channelIndex: int, astype('int32')) return watershedOutput - def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, - watershedZ1: np.ndarray, n0: int): + def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, + watershedZ1: np.ndarray, n0: int): z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes > 100] @@ -402,7 +402,7 @@ def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, for ii in range(len(z1NucleiIndexes)): n1 = z1NucleiIndexes[ii] n1Area[ii] = np.count_nonzero(watershedZ1 == n1) - overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * + overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * (watershedZ1 == n1)) n0OverlapFraction = np.asarray(overlapArea/n0Area) @@ -415,8 +415,8 @@ def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, index), reverse=True)) - if n0OverlapFraction[indexSorted[0]] > 0.2 and - n1OverlapFraction[indexSorted[0]] > 0.5: + if n0OverlapFraction[indexSorted[0]] > 0.2 and + n1OverlapFraction[indexSorted[0]] > 0.5: return m1NucleiIndexes[indexSorted[0]], n0OverlapFraction[indexSorted[0]], n1OverlapFraction[indexSorted[0]] From 565cf9f23581d9ad80160878e236d40d23cdf635 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:59:20 -0500 Subject: [PATCH 029/419] pep8 compliance --- merlin/analysis/segment.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 441ecbb6..04136dff 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -418,8 +418,8 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, if n0OverlapFraction[indexSorted[0]] > 0.2 and n1OverlapFraction[indexSorted[0]] > 0.5: return m1NucleiIndexes[indexSorted[0]], - n0OverlapFraction[indexSorted[0]], - n1OverlapFraction[indexSorted[0]] + n0OverlapFraction[indexSorted[0]], + n1OverlapFraction[indexSorted[0]] else: return False, False, False else: @@ -427,7 +427,7 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) - -> np.ndarray: + -> np.ndarray: # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes From 4fb39c0bc6c2cbb753cd1d570fc0146e97c69cb4 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 12:00:52 -0500 Subject: [PATCH 030/419] pep8 compliance --- merlin/analysis/segment.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 04136dff..f36b4277 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -425,9 +425,8 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, else: return False, False, False - def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) - -> np.ndarray: + -> np.ndarray: # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes From e255c923c15cb2876b608bc743338f6c268ea8ae Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 12:02:24 -0500 Subject: [PATCH 031/419] pep8 compliance --- merlin/analysis/segment.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index f36b4277..d923d5ff 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -425,8 +425,9 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, else: return False, False, False - def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) - -> np.ndarray: + def _combine_watershed_z_positions(self, + watershedOutput: np.ndarray) + -> np.ndarray: # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes From 86f355d4118c5749501f5b96c9adbb2b44d7a67d Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 13:26:40 -0500 Subject: [PATCH 032/419] fixing invalid syntax --- merlin/analysis/segment.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index d923d5ff..d520ac2f 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -246,8 +246,10 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: tresholdingMask = np.zeros(imageStack.shape) fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - tresholdingMask[:, :, z] = imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) + tresholdingMask[:, :, z] = (imageStack[:, :, z] > + threshold_local(imageStack[:, :, z], + fineBlockSize, + offset=0)) tresholdingMask[:, :, z] = remove_small_objects( imageStack[:, :, z].astype('bool'), min_size=100, connectivity=1) @@ -425,9 +427,8 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, else: return False, False, False - def _combine_watershed_z_positions(self, - watershedOutput: np.ndarray) - -> np.ndarray: + def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) + -> np.ndarray: # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes @@ -440,11 +441,11 @@ def _combine_watershed_z_positions(self, # starting far from coverslip for z in range(len(self.dataSet.get_z_positions())-1, 0, -1): zNucleiIndex = np.unique(watershedOutput[:, :, z])[ - np.unique(watershedOutput[:, :, z])>100] + np.unique(watershedOutput[:, :, z]) > 100] for n0 in zNucleiIndex: # for each nuclei N(Z) in Z n1,f0,f1 = _get_overlapping_nuclei(watershedCombinedZ[:, :, z], - watershedOutput[:, :, z-1],n0) + watershedOutput[:, :, z-1], n0) if n1: watershedCombinedZ[:, :, z-1][watershedOutput[:, :, z-1] == n1] = n0 From 2ec94fc57bcfa1cdf60aa20d3868805f2e8ff966 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 13:38:23 -0500 Subject: [PATCH 033/419] fixing invalid syntax --- merlin/analysis/segment.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index d520ac2f..3c376d5e 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -275,10 +275,14 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseBlockSize = 241 fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], coarseBlockSize, offset=0) - fineThresholdingMask = imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) + coarseThresholdingMask = (imageStack[:, :, z] > + threshold_local(imageStack[:, :, z], + coarseBlockSize, + offset=0)) + fineThresholdingMask = (imageStack[:, :, z] > + threshold_local(imageStack[:, :, z], + fineBlockSize, + offset=0)) thresholdingMask[:, :, z] = coarseThresholdingMask * fineThresholdingMask thresholdingMask[:, :, z] = binary_fill_holes( @@ -407,8 +411,8 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * (watershedZ1 == n1)) - n0OverlapFraction = np.asarray(overlapArea/n0Area) - n1OverlapFraction = np.asarray(overlapArea/n1Area) + n0OverlapFraction = np.asarray(overlapArea / n0Area) + n1OverlapFraction = np.asarray(overlapArea / n1Area) index = list(range(len(n0OverlapFraction))) # select the nuclei that has the highest fraction in n0 and n1 From 1cf8607bd22d9ac08363b993165a7a5c81a79275 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 13:48:19 -0500 Subject: [PATCH 034/419] fixing invalid syntax --- merlin/analysis/segment.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 3c376d5e..7cc16956 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -283,8 +283,8 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: threshold_local(imageStack[:, :, z], fineBlockSize, offset=0)) - thresholdingMask[:, :, z] = coarseThresholdingMask * - fineThresholdingMask + thresholdingMask[:, :, z] = (coarseThresholdingMask * + fineThresholdingMask) thresholdingMask[:, :, z] = binary_fill_holes( thresholdingMask[:, :, z]) @@ -302,7 +302,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: fineHessianMask[:, :, z] = fineHessian == fineHessian.max() fineHessianMask[:, :, z] = binary_closing(fineHessianMask[:, :, z], selem.disk(5)) - fineHessianMask[:, :, z] = fineHessianMask[:, :, z]*borderMask + fineHessianMask[:, :, z] = fineHessianMask[:, :, z] * borderMask fineHessianMask[:, :, z] = binary_fill_holes( fineHessianMask[:, :, z]) @@ -315,7 +315,8 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() coarseHessianMask[:, :, z] = binary_closing( coarseHessianMask[:, :, z], selem.disk(5)) - coarseHessianMask[:, :, z] = coarseHessianMask[:, :, z]*borderMask + coarseHessianMask[:, :, z] = (coarseHessianMask[:, :, z] * + borderMask) coarseHessianMask[:, :, z] = binary_fill_holes( coarseHessianMask[:, :, z]) @@ -334,19 +335,19 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, background = sm.dilation(nucleiMask[:, :, z], sm.selem.disk(15)) membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), sm.selem.disk(10)) - foreground = sm.erosion(nucleiMask[:, :, z]*~membraneDilated, + foreground = sm.erosion(nucleiMask[:, :, z] *~ membraneDilated, sm.selem.disk(5)) - unknown = background*~foreground + unknown = background *~ foreground - background = np.uint8(background)*255 - foreground = np.uint8(foreground)*255 - unknown = np.uint8(unknown)*255 + background = np.uint8(background) * 255 + foreground = np.uint8(foreground) * 255 + unknown = np.uint8(unknown) * 255 # Marker labelling ret, markers = cv2.connectedComponents(foreground) # Add one to all labels so that sure background is not 0, but 1 - markers = markers+100 + markers = markers + 100 # Now, mark the region of unknown with zero markers[unknown == 255] = 0 From 95083377be56f34f42ae1457ffedbb083cd6d4eb Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 13:52:09 -0500 Subject: [PATCH 035/419] fixing invalid syntax --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 7cc16956..53205e55 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -422,8 +422,8 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, index), reverse=True)) - if n0OverlapFraction[indexSorted[0]] > 0.2 and - n1OverlapFraction[indexSorted[0]] > 0.5: + if (n0OverlapFraction[indexSorted[0]] > 0.2 and + n1OverlapFraction[indexSorted[0]] > 0.5): return m1NucleiIndexes[indexSorted[0]], n0OverlapFraction[indexSorted[0]], n1OverlapFraction[indexSorted[0]] From 32b1aa807a935b7b1942d3b6cc5d048d17d16973 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 13:56:13 -0500 Subject: [PATCH 036/419] fixing invalid syntax --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 53205e55..264db74c 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -432,8 +432,8 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, else: return False, False, False - def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) - -> np.ndarray: + def _combine_watershed_z_positions(self, + watershedOutput: np.ndarray) -> np.ndarray: # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes From f17a9dcdd413d51b954e6538325f49d93d9976e8 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 14:00:18 -0500 Subject: [PATCH 037/419] fixing invalid syntax --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 264db74c..ec7ee9b3 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -452,8 +452,8 @@ def _combine_watershed_z_positions(self, n1,f0,f1 = _get_overlapping_nuclei(watershedCombinedZ[:, :, z], watershedOutput[:, :, z-1], n0) if n1: - watershedCombinedZ[:, :, z-1][watershedOutput[:, :, z-1] == n1] - = n0 + watershedCombinedZ[:, :, z-1][(watershedOutput[:, :, z-1] == + n1)] = n0 return watershedCombinedZ class CleanCellBoundaries(analysistask.ParallelAnalysisTask): From e30d9efb98267c60aa516abf8b7b0e978c77e423 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 14:10:20 -0500 Subject: [PATCH 038/419] pep8 compliance --- merlin/analysis/segment.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index ec7ee9b3..bcf8698a 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -281,7 +281,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: offset=0)) fineThresholdingMask = (imageStack[:, :, z] > threshold_local(imageStack[:, :, z], - fineBlockSize, + fineBlockSize, offset=0)) thresholdingMask[:, :, z] = (coarseThresholdingMask * fineThresholdingMask) @@ -315,7 +315,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() coarseHessianMask[:, :, z] = binary_closing( coarseHessianMask[:, :, z], selem.disk(5)) - coarseHessianMask[:, :, z] = (coarseHessianMask[:, :, z] * + coarseHessianMask[:, :, z] = (coarseHessianMask[:, :, z] * borderMask) coarseHessianMask[:, :, z] = binary_fill_holes( coarseHessianMask[:, :, z]) @@ -335,9 +335,9 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, background = sm.dilation(nucleiMask[:, :, z], sm.selem.disk(15)) membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), sm.selem.disk(10)) - foreground = sm.erosion(nucleiMask[:, :, z] *~ membraneDilated, + foreground = sm.erosion(nucleiMask[:, :, z] * ~ membraneDilated, sm.selem.disk(5)) - unknown = background *~ foreground + unknown = background * ~ foreground background = np.uint8(background) * 255 foreground = np.uint8(foreground) * 255 @@ -433,7 +433,8 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, return False, False, False def _combine_watershed_z_positions(self, - watershedOutput: np.ndarray) -> np.ndarray: + watershedOutput: np.ndarray) -> np.ndarray: + # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes @@ -448,11 +449,12 @@ def _combine_watershed_z_positions(self, zNucleiIndex = np.unique(watershedOutput[:, :, z])[ np.unique(watershedOutput[:, :, z]) > 100] - for n0 in zNucleiIndex: # for each nuclei N(Z) in Z - n1,f0,f1 = _get_overlapping_nuclei(watershedCombinedZ[:, :, z], - watershedOutput[:, :, z-1], n0) + for n0 in zNucleiIndex: + n1, f0, f1 = _get_overlapping_nuclei(watershedCombinedZ[:, :, z], + watershedOutput[:, :, z-1], + n0) if n1: - watershedCombinedZ[:, :, z-1][(watershedOutput[:, :, z-1] == + watershedCombinedZ[:, :, z-1][(watershedOutput[:, :, z-1] == n1)] = n0 return watershedCombinedZ From 33ccf130f27b95dd982e90e162566aea89741bb7 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 14:12:19 -0500 Subject: [PATCH 039/419] pep8 compliance --- merlin/analysis/segment.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index bcf8698a..b0102e12 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -433,7 +433,8 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, return False, False, False def _combine_watershed_z_positions(self, - watershedOutput: np.ndarray) -> np.ndarray: + watershedOutput: + np.ndarray) ->np.ndarray: # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes From 5d68cddbc69d4f7702607eec99c8304aa35bcbd0 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 14:14:32 -0500 Subject: [PATCH 040/419] pep8 compliance --- merlin/analysis/segment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index b0102e12..da5ed5da 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -434,7 +434,7 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, def _combine_watershed_z_positions(self, watershedOutput: - np.ndarray) ->np.ndarray: + np.ndarray) -> np.ndarray: # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes From c69bd35eaa89c653bbd2e787a2f970474612bf7c Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 14:49:52 -0500 Subject: [PATCH 041/419] added _read_and_filter_image_stack to WatershedSegmentNucleiCV2 --- merlin/analysis/segment.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index da5ed5da..7aeaf01c 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -220,6 +220,16 @@ def _run_analysis(self, fragmentIndex): featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) + def _read_and_filter_image_stack(self, fov: int, channelIndex: int, + filterSigma: float) -> np.ndarray: + filterSize = int(2*np.ceil(2*filterSigma)+1) + warpTask = self.dataSet.load_analysis_task( + self.parameters['warp_task']) + return np.array([cv2.GaussianBlur( + warpTask.get_aligned_image(fov, channelIndex, z), + (filterSize, filterSize), filterSigma) + for z in range(len(self.dataSet.get_z_positions()))]) + def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) From dea1125300ebe66cd161ee892ee45aab843b7d07 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 16:38:22 -0500 Subject: [PATCH 042/419] reorganized Watershed...CV2 to remove repeated method calls --- merlin/analysis/segment.py | 156 +++++++++++++------------------------ 1 file changed, 53 insertions(+), 103 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 7aeaf01c..a238a420 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -146,10 +146,10 @@ class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): def __init__(self, dataSet, parameters=None, analysisName=None): super().__init__(dataSet, parameters, analysisName) - if 'seed_channel_name' not in self.parameters: - self.parameters['seed_channel_name'] = 'WGA' - if 'watershed_channel_name' not in self.parameters: - self.parameters['watershed_channel_name'] = 'DAPI' + if 'membrane_channel_name' not in self.parameters: + self.parameters['membrane_channel_name'] = 'ConA' + if 'nuclei_channel_name' not in self.parameters: + self.parameters['nucleichannel_name'] = 'DAPI' def fragment_count(self): return len(self.dataSet.get_fovs()) @@ -174,123 +174,75 @@ def _run_analysis(self, fragmentIndex): globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) - seedIndex = self.dataSet.get_data_organization().get_data_channel_index( - self.parameters['seed_channel_name']) - seedImages = self._read_and_filter_image_stack(fragmentIndex, - seedIndex, 5) - - watershedIndex = self.dataSet.get_data_organization() \ - .get_data_channel_index(self.parameters['watershed_channel_name']) + # read membrane (seed) and nuclei (watershed) indexes + membraneIndex = self.dataSet.get_data_organization(). + get_data_channel_index(self.parameters['membrane_channel_name']) + nucleiIndex = self.dataSet.get_data_organization(). + get_data_channel_index(self.parameters['nuclei_channel_name']) + + # read membrane (seed) and nuclei (watershed) images + membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) + nucleiImages = self._read_image_stack(fragmentIndex, nucleiIndex) # Prepare masks for cv2 watershed - membraneMask = self._get_membrane_mask(fragmentIndex, watershedIndex) - nucleiMask = self._get_nuclei_mask(fragmentIndex, watershedIndex) - watershedMarkers = self._get_watershed_markers(nucleiMask, - membraneMask) + watershedMarkers = self._get_watershed_markers(nucleiImages, + membraneImages) # perform watershed in individual z positions - watershedOutput = self._apply_watershed(fragmentIndex, watershedIndex, + watershedOutput = self._apply_watershed(nucleiImages, watershedMarkers) # combine all z positions in watershed watershedCombinedOutput = self._combine_watershed_z_positions( watershedOutput) - """ - watershedImages = self._read_and_filter_image_stack(fragmentIndex, - watershedIndex, 5) - seeds = watershed.separate_merged_seeds( - watershed.extract_seeds(seedImages)) - normalizedWatershed, watershedMask = - watershed.prepare_watershed_images( - watershedImages) - - seeds[np.invert(watershedMask)] = 0 - watershedOutput = segmentation.watershed( - normalizedWatershed, measure.label(seeds), mask=watershedMask, - connectivity=np.ones((3, 3, 3)), watershed_line=True) - """ - zPos = np.array(self.dataSet.get_data_organization().get_z_positions()) featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( - (watershedOutput == i), fragmentIndex, + (watershedCombinedOutput == i), fragmentIndex, globalTask.fov_to_global_transform(fragmentIndex), zPos) for i in np.unique(watershedOutput) if i != 0] featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) - def _read_and_filter_image_stack(self, fov: int, channelIndex: int, - filterSigma: float) -> np.ndarray: - filterSize = int(2*np.ceil(2*filterSigma)+1) + def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) - return np.array([cv2.GaussianBlur( - warpTask.get_aligned_image(fov, channelIndex, z), - (filterSize, filterSize), filterSigma) - for z in range(len(self.dataSet.get_z_positions()))]) - - def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: - warpTask = self.dataSet.load_analysis_task( - self.parameters['warp_task']) - - imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet. - get_z_positions()))]) - # generate mask based on edge detection - """ - edgeMask = np.zeros(imageStack.shape) - for z in range(len(self.dataSet.get_z_positions())): - edgeMask[:, :, z] = canny( - white_tophat(imageStack[:, :, z], selem.disk(10)), - sigma=2, use_quantiles=True, - low_threshold=0.5, high_threshold=0.8) - edgeMask[:, :, z] = binary_closing(edgeMask[:, :, z],selem.disk(5)) - edgeMask[:, :, z] = remove_small_objects( - edgeMask[:, :, z].astype('bool'), - min_size=100, connectivity=1) - edgeMask[:, :, z] = skeletonize(edgeMask[:, :, z]) - """ + return np.array([warpTask.get_aligned_image(fov, channelIndex, z) + for z in range(len(self.dataSet.get_z_positions()))]) + def _get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: # generate mask based on thresholding - tresholdingMask = np.zeros(imageStack.shape) + mask = np.zeros(membraneImages.shape) fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - tresholdingMask[:, :, z] = (imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], - fineBlockSize, - offset=0)) - tresholdingMask[:, :, z] = remove_small_objects( - imageStack[:, :, z].astype('bool'), min_size=100, - connectivity=1) - tresholdingMask[:, :, z] = binary_closing(imageStack[:, :, z], + mask[:, :, z] = (membraneImages[:, :, z] > + threshold_local(membraneImages[:, :, z], + fineBlockSize, + offset=0)) + mask[:, :, z] = remove_small_objects(membraneImages[:, :, z]. + astype('bool'), + min_size=100, + connectivity=1) + mask[:, :, z] = binary_closing(membraneImages[:, :, z], selem.disk(5)) - tresholdingMask[:, :, z] = skeletonize(imageStack[:, :, z]) + mask[:, :, z] = skeletonize(membraneImages[:, :, z]) # combine masks - # return edgeMask + thresholdingMask - return thresholdingMask - - def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: - - warpTask = self.dataSet.load_analysis_task( - self.parameters['warp_task']) - - imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet. - get_z_positions()))]) + return mask + def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: # generate nuclei mask based on thresholding - thresholdingMask = np.zeros(imageStack.shape) + thresholdingMask = np.zeros(nucleiImages.shape) coarseBlockSize = 241 fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = (imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], + coarseThresholdingMask = (nucleiImages[:, :, z] > + threshold_local(nucleiImages[:, :, z], coarseBlockSize, offset=0)) - fineThresholdingMask = (imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], + fineThresholdingMask = (nucleiImages[:, :, z] > + threshold_local(nucleiImages[:, :, z], fineBlockSize, offset=0)) thresholdingMask[:, :, z] = (coarseThresholdingMask * @@ -306,21 +258,21 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: # TODO - use the image size variable for borderMask # generate nuclei mask from hessian, fine - fineHessianMask = np.zeros(imageStack.shape) + fineHessianMask = np.zeros(nucleiImages.shape) for z in range(len(self.dataSet.get_z_positions())): - fineHessian = hessian(imageStack[:, :, z]) + fineHessian = hessian(nucleiImages[:, :, z]) fineHessianMask[:, :, z] = fineHessian == fineHessian.max() fineHessianMask[:, :, z] = binary_closing(fineHessianMask[:, :, z], selem.disk(5)) fineHessianMask[:, :, z] = fineHessianMask[:, :, z] * borderMask fineHessianMask[:, :, z] = binary_fill_holes( - fineHessianMask[:, :, z]) + fineHessianMask[:, :, z]) # generate dapi mask from hessian, coarse - coarseHessianMask = np.zeros(imageStack.shape) + coarseHessianMask = np.zeros(nucleiImages.shape) for z in range(len(self.dataSet.get_z_positions())): - coarseHessian = hessian(imageStack[:, :, z] - - white_tophat(imageStack[:, :, z], + coarseHessian = hessian(nucleiImages[:, :, z] - + white_tophat(nucleiImages[:, :, z], selem.disk(20))) coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() coarseHessianMask[:, :, z] = binary_closing( @@ -334,8 +286,12 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask return binary_fill_holes(nucleiMask) - def _get_watershed_markers(self, nucleiMask: np.ndarray, - membraneMask: np.ndarray) -> np.ndarray: + def _get_watershed_markers(self, nucleiImages: np.ndarray, + membraneImages: np.ndarray) -> np.ndarray: + + nucleiMask = self._get_nuclei_mask(nucleiImages) + membraneMask = self._get_membrane_mask(membraneImages) + watershedMarker = np.zeros(nucleiMask.shape) for z in range(len(self.dataSet.get_z_positions())): @@ -387,18 +343,12 @@ def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: return rgbImage - def _apply_watershed(self, fov: int, channelIndex: int, + def _apply_watershed(self, nucleiImages: np.ndarray, watershedMarkers: np.ndarray) -> np.ndarray: - warpTask = self.dataSet.load_analysis_task( - self.parameters['warp_task']) - - imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet. - get_z_positions()))]) watershedOutput = np.zeros(watershedMarkers.shape) for z in range(len(self.dataSet.get_z_positions())): - rgbImage = _convert_grayscale_to_rgb(dapiStack[:, :, z]) + rgbImage = _convert_grayscale_to_rgb(nucleiImages[:, :, z]) watershedOutput[:, :, z] = cv2.watershed(rgbImage, watershedMarkers[:, :, z]. astype('int32')) From b32332d2b84714fb208008de2c37baedfb93d986 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 16:44:01 -0500 Subject: [PATCH 043/419] cleaned watershed --- merlin/analysis/segment.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index a238a420..88cab7db 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -352,6 +352,8 @@ def _apply_watershed(self, nucleiImages: np.ndarray, watershedOutput[:, :, z] = cv2.watershed(rgbImage, watershedMarkers[:, :, z]. astype('int32')) + watershedOutput[:, :, z][watershedOutput[:, :, z] <= 100] = 0 + return watershedOutput def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, From d5de3e84ba3ad4c7c88e0b7fc09b973fc4e42f7c Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 16:47:37 -0500 Subject: [PATCH 044/419] pep8 compliance --- merlin/analysis/segment.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 88cab7db..36c472fd 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -176,9 +176,11 @@ def _run_analysis(self, fragmentIndex): # read membrane (seed) and nuclei (watershed) indexes membraneIndex = self.dataSet.get_data_organization(). - get_data_channel_index(self.parameters['membrane_channel_name']) + get_data_channel_index( + self.parameters['m brane_channel_name']) nucleiIndex = self.dataSet.get_data_organization(). - get_data_channel_index(self.parameters['nuclei_channel_name']) + get_data_channel_index( + self.parameters['nuclei_channel_name']) # read membrane (seed) and nuclei (watershed) images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) From 106a5d973457ca0e41798f8b592e4e3ed3c5cf60 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 16:49:19 -0500 Subject: [PATCH 045/419] pep8 compliance --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 36c472fd..9086c269 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -176,8 +176,8 @@ def _run_analysis(self, fragmentIndex): # read membrane (seed) and nuclei (watershed) indexes membraneIndex = self.dataSet.get_data_organization(). - get_data_channel_index( - self.parameters['m brane_channel_name']) + get_data_channel_index( + self.parameters['membrane_channel_name']) nucleiIndex = self.dataSet.get_data_organization(). get_data_channel_index( self.parameters['nuclei_channel_name']) From 69ac6a924abea36c279c582e706ce614801b4a9a Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 16:54:08 -0500 Subject: [PATCH 046/419] pep8 compliance --- merlin/analysis/segment.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 9086c269..72272b87 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -175,12 +175,12 @@ def _run_analysis(self, fragmentIndex): self.parameters['global_align_task']) # read membrane (seed) and nuclei (watershed) indexes - membraneIndex = self.dataSet.get_data_organization(). - get_data_channel_index( - self.parameters['membrane_channel_name']) + membraneIndex = self.dataSet \ + .get_data_organization() \ + .get_data_channel_index( + self.parameters['membrane_channel_name']) nucleiIndex = self.dataSet.get_data_organization(). - get_data_channel_index( - self.parameters['nuclei_channel_name']) + get_data_channel_index(self.parameters['nuclei_channel_name']) # read membrane (seed) and nuclei (watershed) images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) From d5d9f504559cb6f0f8179c0b9d0e6af9a3e8eaff Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 16:55:47 -0500 Subject: [PATCH 047/419] pep8 compliance --- merlin/analysis/segment.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 72272b87..698611c7 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -175,13 +175,11 @@ def _run_analysis(self, fragmentIndex): self.parameters['global_align_task']) # read membrane (seed) and nuclei (watershed) indexes - membraneIndex = self.dataSet \ - .get_data_organization() \ - .get_data_channel_index( - self.parameters['membrane_channel_name']) + membraneIndex = self.dataSet.get_data_organization(). + get_data_channel_index(self.parameters['membrane_channel_name']) nucleiIndex = self.dataSet.get_data_organization(). get_data_channel_index(self.parameters['nuclei_channel_name']) - + # read membrane (seed) and nuclei (watershed) images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) nucleiImages = self._read_image_stack(fragmentIndex, nucleiIndex) From a6107bf7002e2d9c9b156bac134d65b9e3f1109b Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 16:58:17 -0500 Subject: [PATCH 048/419] pep8 compliance --- merlin/analysis/segment.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 698611c7..9ec3ddd3 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -180,7 +180,7 @@ def _run_analysis(self, fragmentIndex): nucleiIndex = self.dataSet.get_data_organization(). get_data_channel_index(self.parameters['nuclei_channel_name']) - # read membrane (seed) and nuclei (watershed) images + # read membrane (seed) and nuclei (watershed) images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) nucleiImages = self._read_image_stack(fragmentIndex, nucleiIndex) @@ -221,11 +221,11 @@ def _get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: fineBlockSize, offset=0)) mask[:, :, z] = remove_small_objects(membraneImages[:, :, z]. - astype('bool'), - min_size=100, - connectivity=1) + astype('bool'), + min_size=100, + connectivity=1) mask[:, :, z] = binary_closing(membraneImages[:, :, z], - selem.disk(5)) + selem.disk(5)) mask[:, :, z] = skeletonize(membraneImages[:, :, z]) # combine masks @@ -288,10 +288,10 @@ def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: def _get_watershed_markers(self, nucleiImages: np.ndarray, membraneImages: np.ndarray) -> np.ndarray: - + nucleiMask = self._get_nuclei_mask(nucleiImages) membraneMask = self._get_membrane_mask(membraneImages) - + watershedMarker = np.zeros(nucleiMask.shape) for z in range(len(self.dataSet.get_z_positions())): @@ -352,8 +352,8 @@ def _apply_watershed(self, nucleiImages: np.ndarray, watershedOutput[:, :, z] = cv2.watershed(rgbImage, watershedMarkers[:, :, z]. astype('int32')) - watershedOutput[:, :, z][watershedOutput[:, :, z] <= 100] = 0 - + watershedOutput[:, :, z][watershedOutput[:, :, z] <= 100] = 0 + return watershedOutput def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, From fe00544c25273585c4fc415a848efc0133b2afad Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 17:05:54 -0500 Subject: [PATCH 049/419] fixing invalid syntax --- merlin/analysis/segment.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 9ec3ddd3..90a5ff8d 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -175,10 +175,14 @@ def _run_analysis(self, fragmentIndex): self.parameters['global_align_task']) # read membrane (seed) and nuclei (watershed) indexes - membraneIndex = self.dataSet.get_data_organization(). - get_data_channel_index(self.parameters['membrane_channel_name']) - nucleiIndex = self.dataSet.get_data_organization(). - get_data_channel_index(self.parameters['nuclei_channel_name']) + membraneIndex = self.dataSet \ + .get_data_organization() \ + .get_data_channel_index( + self.parameters['membrane_channel_name']) + nucleiIndex = self.dataSet \ + .get_data_organization() \ + .get_data_channel_index( + self.parameters['nuclei_channel_name']) # read membrane (seed) and nuclei (watershed) images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) From 2447f2fa98f1ecfe6058713388d1a87598623b29 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 17:53:10 -0500 Subject: [PATCH 050/419] correct skimage function names --- merlin/analysis/segment.py | 59 +++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 90a5ff8d..11449c8a 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -221,16 +221,16 @@ def _get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): mask[:, :, z] = (membraneImages[:, :, z] > - threshold_local(membraneImages[:, :, z], + filters.threshold_local(membraneImages[:, :, z], fineBlockSize, offset=0)) - mask[:, :, z] = remove_small_objects(membraneImages[:, :, z]. - astype('bool'), - min_size=100, - connectivity=1) - mask[:, :, z] = binary_closing(membraneImages[:, :, z], - selem.disk(5)) - mask[:, :, z] = skeletonize(membraneImages[:, :, z]) + mask[:, :, z] = morphology.remove_small_objects( + membraneImages[:, :, z].astype('bool'), + min_size=100, + connectivity=1) + mask[:, :, z] = morphology.binary_closing(membraneImages[:, :, z], + morphology.selem.disk(5)) + mask[:, :, z] = morphology.skeletonize(membraneImages[:, :, z]) # combine masks return mask @@ -242,13 +242,15 @@ def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): coarseThresholdingMask = (nucleiImages[:, :, z] > - threshold_local(nucleiImages[:, :, z], - coarseBlockSize, - offset=0)) + filters.threshold_local( + nucleiImages[:, :, z], + coarseBlockSize, + offset=0)) fineThresholdingMask = (nucleiImages[:, :, z] > - threshold_local(nucleiImages[:, :, z], - fineBlockSize, - offset=0)) + filters.threshold_local( + nucleiImages[:, :, z], + fineBlockSize, + offset=0)) thresholdingMask[:, :, z] = (coarseThresholdingMask * fineThresholdingMask) thresholdingMask[:, :, z] = binary_fill_holes( @@ -266,8 +268,9 @@ def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: for z in range(len(self.dataSet.get_z_positions())): fineHessian = hessian(nucleiImages[:, :, z]) fineHessianMask[:, :, z] = fineHessian == fineHessian.max() - fineHessianMask[:, :, z] = binary_closing(fineHessianMask[:, :, z], - selem.disk(5)) + fineHessianMask[:, :, z] = morphology.binary_closing( + fineHessianMask[:, :, z], + morphology.selem.disk(5)) fineHessianMask[:, :, z] = fineHessianMask[:, :, z] * borderMask fineHessianMask[:, :, z] = binary_fill_holes( fineHessianMask[:, :, z]) @@ -275,12 +278,13 @@ def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: # generate dapi mask from hessian, coarse coarseHessianMask = np.zeros(nucleiImages.shape) for z in range(len(self.dataSet.get_z_positions())): - coarseHessian = hessian(nucleiImages[:, :, z] - - white_tophat(nucleiImages[:, :, z], - selem.disk(20))) + coarseHessian = filters.hessian(nucleiImages[:, :, z] - + morphology.white_tophat( + nucleiImages[:, :, z], + morphology.selem.disk(20))) coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() - coarseHessianMask[:, :, z] = binary_closing( - coarseHessianMask[:, :, z], selem.disk(5)) + coarseHessianMask[:, :, z] = morphology.binary_closing( + coarseHessianMask[:, :, z], morphology.selem.disk(5)) coarseHessianMask[:, :, z] = (coarseHessianMask[:, :, z] * borderMask) coarseHessianMask[:, :, z] = binary_fill_holes( @@ -302,11 +306,14 @@ def _get_watershed_markers(self, nucleiImages: np.ndarray, # generate areas of sure bg and fg, as well as the area of # unknown classification - background = sm.dilation(nucleiMask[:, :, z], sm.selem.disk(15)) - membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), - sm.selem.disk(10)) - foreground = sm.erosion(nucleiMask[:, :, z] * ~ membraneDilated, - sm.selem.disk(5)) + background = morphology.dilation(nucleiMask[:, :, z], + morphology.selem.disk(15)) + membraneDilated = morphology.dilation( + membraneMask[:, :, z].astype('bool'), + morphology.selem.disk(10)) + foreground = morphology.erosion(nucleiMask[:, :, z] * ~ + membraneDilated, + morphology.selem.disk(5)) unknown = background * ~ foreground background = np.uint8(background) * 255 From 70d08a9d62bd19c4de263c9862bc9297a1c125d1 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 17:55:00 -0500 Subject: [PATCH 051/419] pep8 compliance --- merlin/analysis/segment.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 11449c8a..1c5ee6ba 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -222,14 +222,14 @@ def _get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: for z in range(len(self.dataSet.get_z_positions())): mask[:, :, z] = (membraneImages[:, :, z] > filters.threshold_local(membraneImages[:, :, z], - fineBlockSize, - offset=0)) + fineBlockSize, + offset=0)) mask[:, :, z] = morphology.remove_small_objects( membraneImages[:, :, z].astype('bool'), min_size=100, connectivity=1) mask[:, :, z] = morphology.binary_closing(membraneImages[:, :, z], - morphology.selem.disk(5)) + morphology.selem.disk(5)) mask[:, :, z] = morphology.skeletonize(membraneImages[:, :, z]) # combine masks @@ -279,7 +279,7 @@ def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: coarseHessianMask = np.zeros(nucleiImages.shape) for z in range(len(self.dataSet.get_z_positions())): coarseHessian = filters.hessian(nucleiImages[:, :, z] - - morphology.white_tophat( + morphology.white_tophat( nucleiImages[:, :, z], morphology.selem.disk(20))) coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() From 80efd1ebb5990dcb2b232abc8adff07bb0ae0910 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 18:06:02 -0500 Subject: [PATCH 052/419] correct skimage function names --- merlin/analysis/segment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 1c5ee6ba..fa0a95a3 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -266,7 +266,7 @@ def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: # generate nuclei mask from hessian, fine fineHessianMask = np.zeros(nucleiImages.shape) for z in range(len(self.dataSet.get_z_positions())): - fineHessian = hessian(nucleiImages[:, :, z]) + fineHessian = filters.hessian(nucleiImages[:, :, z]) fineHessianMask[:, :, z] = fineHessian == fineHessian.max() fineHessianMask[:, :, z] = morphology.binary_closing( fineHessianMask[:, :, z], From 56cb30618d6a21463ac00b73ec4460396b756d60 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sun, 26 Jan 2020 09:04:09 -0500 Subject: [PATCH 053/419] changed Image dimension order to fit MERlin's --- merlin/analysis/segment.py | 89 ++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 43 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index fa0a95a3..d96a877a 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -149,7 +149,7 @@ def __init__(self, dataSet, parameters=None, analysisName=None): if 'membrane_channel_name' not in self.parameters: self.parameters['membrane_channel_name'] = 'ConA' if 'nuclei_channel_name' not in self.parameters: - self.parameters['nucleichannel_name'] = 'DAPI' + self.parameters['nuclei_channel_name'] = 'DAPI' def fragment_count(self): return len(self.dataSet.get_fovs()) @@ -188,6 +188,9 @@ def _run_analysis(self, fragmentIndex): membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) nucleiImages = self._read_image_stack(fragmentIndex, nucleiIndex) + print('membraneImages = ' + str(membraneImages.shape)) + print('nucleiImages = ' + str(nucleiImages.shape)) + # Prepare masks for cv2 watershed watershedMarkers = self._get_watershed_markers(nucleiImages, membraneImages) @@ -220,17 +223,17 @@ def _get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: mask = np.zeros(membraneImages.shape) fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - mask[:, :, z] = (membraneImages[:, :, z] > - filters.threshold_local(membraneImages[:, :, z], + mask[z, :, :] = (membraneImages[z, :, :] > + filters.threshold_local(membraneImages[z, :, :], fineBlockSize, offset=0)) - mask[:, :, z] = morphology.remove_small_objects( - membraneImages[:, :, z].astype('bool'), + mask[z, :, :] = morphology.remove_small_objects( + membraneImages[z, :, :].astype('bool'), min_size=100, connectivity=1) - mask[:, :, z] = morphology.binary_closing(membraneImages[:, :, z], + mask[z, :, :] = morphology.binary_closing(membraneImages[z, :, :], morphology.selem.disk(5)) - mask[:, :, z] = morphology.skeletonize(membraneImages[:, :, z]) + mask[z, :, :] = morphology.skeletonize(membraneImages[z, :, :]) # combine masks return mask @@ -241,20 +244,20 @@ def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: coarseBlockSize = 241 fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = (nucleiImages[:, :, z] > + coarseThresholdingMask = (nucleiImages[z, :, :] > filters.threshold_local( - nucleiImages[:, :, z], + nucleiImages[z, :, :], coarseBlockSize, offset=0)) - fineThresholdingMask = (nucleiImages[:, :, z] > + fineThresholdingMask = (nucleiImages[z, :, :] > filters.threshold_local( - nucleiImages[:, :, z], + nucleiImages[z, :, :], fineBlockSize, offset=0)) - thresholdingMask[:, :, z] = (coarseThresholdingMask * + thresholdingMask[z, :, :] = (coarseThresholdingMask * fineThresholdingMask) - thresholdingMask[:, :, z] = binary_fill_holes( - thresholdingMask[:, :, z]) + thresholdingMask[z, :, :] = binary_fill_holes( + thresholdingMask[z, :, :]) # generate border mask, necessary to avoid making a single # connected component when using binary_fill_holes below @@ -266,29 +269,29 @@ def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: # generate nuclei mask from hessian, fine fineHessianMask = np.zeros(nucleiImages.shape) for z in range(len(self.dataSet.get_z_positions())): - fineHessian = filters.hessian(nucleiImages[:, :, z]) - fineHessianMask[:, :, z] = fineHessian == fineHessian.max() - fineHessianMask[:, :, z] = morphology.binary_closing( - fineHessianMask[:, :, z], + fineHessian = filters.hessian(nucleiImages[z, :, :]) + fineHessianMask[z, :, :] = fineHessian == fineHessian.max() + fineHessianMask[z, :, :] = morphology.binary_closing( + fineHessianMask[z, :, :], morphology.selem.disk(5)) - fineHessianMask[:, :, z] = fineHessianMask[:, :, z] * borderMask - fineHessianMask[:, :, z] = binary_fill_holes( - fineHessianMask[:, :, z]) + fineHessianMask[z, :, :] = fineHessianMask[z, :, :] * borderMask + fineHessianMask[z, :, :] = binary_fill_holes( + fineHessianMask[z, :, :]) # generate dapi mask from hessian, coarse coarseHessianMask = np.zeros(nucleiImages.shape) for z in range(len(self.dataSet.get_z_positions())): - coarseHessian = filters.hessian(nucleiImages[:, :, z] - + coarseHessian = filters.hessian(nucleiImages[z, :, :] - morphology.white_tophat( - nucleiImages[:, :, z], + nucleiImages[z, :, :], morphology.selem.disk(20))) - coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() - coarseHessianMask[:, :, z] = morphology.binary_closing( - coarseHessianMask[:, :, z], morphology.selem.disk(5)) - coarseHessianMask[:, :, z] = (coarseHessianMask[:, :, z] * + coarseHessianMask[z, :, :] = coarseHessian == coarseHessian.max() + coarseHessianMask[z, :, :] = morphology.binary_closing( + coarseHessianMask[z, :, :], morphology.selem.disk(5)) + coarseHessianMask[z, :, :] = (coarseHessianMask[z, :, :] * borderMask) - coarseHessianMask[:, :, z] = binary_fill_holes( - coarseHessianMask[:, :, z]) + coarseHessianMask[z, :, :] = binary_fill_holes( + coarseHessianMask[z, :, :]) # combine masks nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask @@ -306,12 +309,12 @@ def _get_watershed_markers(self, nucleiImages: np.ndarray, # generate areas of sure bg and fg, as well as the area of # unknown classification - background = morphology.dilation(nucleiMask[:, :, z], + background = morphology.dilation(nucleiMask[z, :, :], morphology.selem.disk(15)) membraneDilated = morphology.dilation( - membraneMask[:, :, z].astype('bool'), + membraneMask[z, :, :].astype('bool'), morphology.selem.disk(10)) - foreground = morphology.erosion(nucleiMask[:, :, z] * ~ + foreground = morphology.erosion(nucleiMask[z, :, :] * ~ membraneDilated, morphology.selem.disk(5)) unknown = background * ~ foreground @@ -329,7 +332,7 @@ def _get_watershed_markers(self, nucleiImages: np.ndarray, # Now, mark the region of unknown with zero markers[unknown == 255] = 0 - watershedMarker[:, :, z] = markers + watershedMarker[z, :, :] = markers return watershedMarker @@ -359,11 +362,11 @@ def _apply_watershed(self, nucleiImages: np.ndarray, watershedOutput = np.zeros(watershedMarkers.shape) for z in range(len(self.dataSet.get_z_positions())): - rgbImage = _convert_grayscale_to_rgb(nucleiImages[:, :, z]) - watershedOutput[:, :, z] = cv2.watershed(rgbImage, - watershedMarkers[:, :, z]. + rgbImage = _convert_grayscale_to_rgb(nucleiImages[z, :, :]) + watershedOutput[z, :, :] = cv2.watershed(rgbImage, + watershedMarkers[z, :, :]. astype('int32')) - watershedOutput[:, :, z][watershedOutput[:, :, z] <= 100] = 0 + watershedOutput[z, :, :][watershedOutput[z, :, :] <= 100] = 0 return watershedOutput @@ -416,19 +419,19 @@ def _combine_watershed_z_positions(self, watershedCombinedZ = np.zeros(watershedOutput.shape) # copy the mask of the section farthest to the coverslip - watershedCombinedZ[:, :, -1] = watershedOutput[:, :, -1] + watershedCombinedZ[-1, :, :] = watershedOutput[-1, :, :] # starting far from coverslip for z in range(len(self.dataSet.get_z_positions())-1, 0, -1): - zNucleiIndex = np.unique(watershedOutput[:, :, z])[ - np.unique(watershedOutput[:, :, z]) > 100] + zNucleiIndex = np.unique(watershedOutput[z, :, :])[ + np.unique(watershedOutput[z, :, :]) > 100] for n0 in zNucleiIndex: - n1, f0, f1 = _get_overlapping_nuclei(watershedCombinedZ[:, :, z], - watershedOutput[:, :, z-1], + n1, f0, f1 = _get_overlapping_nuclei(watershedCombinedZ[z, :, :], + watershedOutput[z-1, :, :], n0) if n1: - watershedCombinedZ[:, :, z-1][(watershedOutput[:, :, z-1] == + watershedCombinedZ[z-1, :, :][(watershedOutput[z-1, :, :] == n1)] = n0 return watershedCombinedZ From a6cc1a98489b55455f40f960dcedca08d350f291 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sun, 26 Jan 2020 09:32:42 -0500 Subject: [PATCH 054/419] change variable name --- merlin/analysis/segment.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index d96a877a..1394f6ec 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -228,12 +228,12 @@ def _get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: fineBlockSize, offset=0)) mask[z, :, :] = morphology.remove_small_objects( - membraneImages[z, :, :].astype('bool'), + mask[z, :, :].astype('bool'), min_size=100, connectivity=1) - mask[z, :, :] = morphology.binary_closing(membraneImages[z, :, :], + mask[z, :, :] = morphology.binary_closing(mask[z, :, :], morphology.selem.disk(5)) - mask[z, :, :] = morphology.skeletonize(membraneImages[z, :, :]) + mask[z, :, :] = morphology.skeletonize(mask[z, :, :]) # combine masks return mask From e9ee470d0bbda40cd11141ec5a234a98c191892d Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sun, 26 Jan 2020 09:51:46 -0500 Subject: [PATCH 055/419] added missing self. in method call --- merlin/analysis/segment.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 1394f6ec..74c4b323 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -362,7 +362,7 @@ def _apply_watershed(self, nucleiImages: np.ndarray, watershedOutput = np.zeros(watershedMarkers.shape) for z in range(len(self.dataSet.get_z_positions())): - rgbImage = _convert_grayscale_to_rgb(nucleiImages[z, :, :]) + rgbImage = self._convert_grayscale_to_rgb(nucleiImages[z, :, :]) watershedOutput[z, :, :] = cv2.watershed(rgbImage, watershedMarkers[z, :, :]. astype('int32')) @@ -427,9 +427,10 @@ def _combine_watershed_z_positions(self, np.unique(watershedOutput[z, :, :]) > 100] for n0 in zNucleiIndex: - n1, f0, f1 = _get_overlapping_nuclei(watershedCombinedZ[z, :, :], - watershedOutput[z-1, :, :], - n0) + n1, f0, f1 = self._get_overlapping_nuclei( + watershedCombinedZ[z, :, :], + watershedOutput[z-1, :, :], + n0) if n1: watershedCombinedZ[z-1, :, :][(watershedOutput[z-1, :, :] == n1)] = n0 From 15e430df78033182d121b3fabb74b43b398652c9 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sun, 26 Jan 2020 13:50:42 -0500 Subject: [PATCH 056/419] adding print statements for debugging --- merlin/analysis/segment.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 74c4b323..0de2fa43 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -17,6 +17,7 @@ from merlin.util import watershed import pandas import networkx as nx +import time class FeatureSavingAnalysisTask(analysistask.ParallelAnalysisTask): @@ -171,9 +172,14 @@ def get_cell_boundaries(self) -> List[spatialfeature.SpatialFeature]: return featureDB.read_features() def _run_analysis(self, fragmentIndex): + startTime = time.time() + globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) + + print('reading indexes for fov ' + str(fragmentIndex) ) + # read membrane (seed) and nuclei (watershed) indexes membraneIndex = self.dataSet \ .get_data_organization() \ @@ -184,25 +190,42 @@ def _run_analysis(self, fragmentIndex): .get_data_channel_index( self.parameters['nuclei_channel_name']) + endTime = time.time() + print("Indexes read, ET {:.2f} min"\ + .format((endTime - startTime) / 60)) + # read membrane (seed) and nuclei (watershed) images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) nucleiImages = self._read_image_stack(fragmentIndex, nucleiIndex) - print('membraneImages = ' + str(membraneImages.shape)) - print('nucleiImages = ' + str(nucleiImages.shape)) + endTime = time.time() + print("Images read, ET {:.2f} min" \ + .format((endTime - startTime) / 60)) # Prepare masks for cv2 watershed watershedMarkers = self._get_watershed_markers(nucleiImages, membraneImages) + endTime = time.time() + print("Markers calculated, ET {:.2f} min" \ + .format((endTime - startTime) / 60)) + # perform watershed in individual z positions watershedOutput = self._apply_watershed(nucleiImages, watershedMarkers) + endTime = time.time() + print("watershed calculated, ET {:.2f} min" \ + .format((endTime - startTime) / 60)) + # combine all z positions in watershed watershedCombinedOutput = self._combine_watershed_z_positions( watershedOutput) + endTime = time.time() + print("watershed z positions combined, ET {:.2f} min" \ + .format((endTime - startTime) / 60)) + zPos = np.array(self.dataSet.get_data_organization().get_z_positions()) featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( (watershedCombinedOutput == i), fragmentIndex, @@ -212,6 +235,10 @@ def _run_analysis(self, fragmentIndex): featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) + endTime = time.time() + print("features written, ET {:.2f} min" \ + .format((endTime - startTime) / 60)) + def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) From 2254b4c56f576e9a560245c93d5dd603364f7180 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 15:29:03 -0500 Subject: [PATCH 057/419] moving utility functions from segment.py to watershed.py --- merlin/analysis/segment.py | 2 + merlin/util/watershed.py | 233 +++++++++++++++++++++++++++++++++++++ 2 files changed, 235 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 0de2fa43..e62847ee 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -226,6 +226,8 @@ def _run_analysis(self, fragmentIndex): print("watershed z positions combined, ET {:.2f} min" \ .format((endTime - startTime) / 60)) + # get features from mask. This is the slowestart (6 min for the + # previous part, 15+ for the rest, for a 7 frame Image. zPos = np.array(self.dataSet.get_data_organization().get_z_positions()) featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( (watershedCombinedOutput == i), fragmentIndex, diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 809dfc5b..3b5563f3 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -1,6 +1,7 @@ import numpy as np import cv2 from scipy import ndimage +from scipy.ndimage.morphology import binary_fill_holes from skimage import morphology from skimage import filters from skimage import measure @@ -139,3 +140,235 @@ def prepare_watershed_images(watershedImageStack: np.ndarray normalizedWatershed[np.invert(watershedMask)] = 1 return normalizedWatershed, watershedMask + +def get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: + """Calculate binary mask with 1's in membrane pixels and 0 otherwise. + The images expected are some type of membrane label (WGA, ConA, + Lamin, Cadherins) + + Args: + membraneImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) + """ + mask = np.zeros(membraneImages.shape) + fineBlockSize = 61 + for z in range(len(self.dataSet.get_z_positions())): + mask[z, :, :] = (membraneImages[z, :, :] > + filters.threshold_local(membraneImages[z, :, :], + fineBlockSize, + offset=0)) + mask[z, :, :] = morphology.remove_small_objects( + mask[z, :, :].astype('bool'), + min_size=100, + connectivity=1) + mask[z, :, :] = morphology.binary_closing(mask[z, :, :], + morphology.selem.disk(5)) + mask[z, :, :] = morphology.skeletonize(mask[z, :, :]) + + # combine masks + return mask + +def get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: + + # TO DO: Add description + + # generate nuclei mask based on thresholding + thresholdingMask = np.zeros(nucleiImages.shape) + coarseBlockSize = 241 + fineBlockSize = 61 + for z in range(len(self.dataSet.get_z_positions())): + coarseThresholdingMask = (nucleiImages[z, :, :] > + filters.threshold_local( + nucleiImages[z, :, :], + coarseBlockSize, + offset=0)) + fineThresholdingMask = (nucleiImages[z, :, :] > + filters.threshold_local( + nucleiImages[z, :, :], + fineBlockSize, + offset=0)) + thresholdingMask[z, :, :] = (coarseThresholdingMask * + fineThresholdingMask) + thresholdingMask[z, :, :] = binary_fill_holes( + thresholdingMask[z, :, :]) + + # generate border mask, necessary to avoid making a single + # connected component when using binary_fill_holes below + borderMask = np.zeros((2048, 2048)) + borderMask[25:2023, 25:2023] = 1 + + # TODO - use the image size variable for borderMask + + # generate nuclei mask from hessian, fine + fineHessianMask = np.zeros(nucleiImages.shape) + for z in range(len(self.dataSet.get_z_positions())): + fineHessian = filters.hessian(nucleiImages[z, :, :]) + fineHessianMask[z, :, :] = fineHessian == fineHessian.max() + fineHessianMask[z, :, :] = morphology.binary_closing( + fineHessianMask[z, :, :], + morphology.selem.disk(5)) + fineHessianMask[z, :, :] = fineHessianMask[z, :, :] * borderMask + fineHessianMask[z, :, :] = binary_fill_holes( + fineHessianMask[z, :, :]) + + # generate dapi mask from hessian, coarse + coarseHessianMask = np.zeros(nucleiImages.shape) + for z in range(len(self.dataSet.get_z_positions())): + coarseHessian = filters.hessian(nucleiImages[z, :, :] - + morphology.white_tophat( + nucleiImages[z, :, :], + morphology.selem.disk(20))) + coarseHessianMask[z, :, :] = coarseHessian == coarseHessian.max() + coarseHessianMask[z, :, :] = morphology.binary_closing( + coarseHessianMask[z, :, :], morphology.selem.disk(5)) + coarseHessianMask[z, :, :] = (coarseHessianMask[z, :, :] * + borderMask) + coarseHessianMask[z, :, :] = binary_fill_holes( + coarseHessianMask[z, :, :]) + + # combine masks + nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask + return binary_fill_holes(nucleiMask) + +def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, + membraneImages: np.ndarray) -> np.ndarray: + # TO DO: Add description + + nucleiMask = self.get_nuclei_mask(nucleiImages) + membraneMask = self.get_membrane_mask(membraneImages) + + watershedMarker = np.zeros(nucleiMask.shape) + + for z in range(len(self.dataSet.get_z_positions())): + + # generate areas of sure bg and fg, as well as the area of + # unknown classification + background = morphology.dilation(nucleiMask[z, :, :], + morphology.selem.disk(15)) + membraneDilated = morphology.dilation( + membraneMask[z, :, :].astype('bool'), + morphology.selem.disk(10)) + foreground = morphology.erosion(nucleiMask[z, :, :] * ~ + membraneDilated, + morphology.selem.disk(5)) + unknown = background * ~ foreground + + background = np.uint8(background) * 255 + foreground = np.uint8(foreground) * 255 + unknown = np.uint8(unknown) * 255 + + # Marker labelling + ret, markers = cv2.connectedComponents(foreground) + + # Add one to all labels so that sure background is not 0, but 1 + markers = markers + 100 + + # Now, mark the region of unknown with zero + markers[unknown == 255] = 0 + + watershedMarker[z, :, :] = markers + + return watershedMarker + +def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: + # cv2 only works in 3D images of 8bit. Make a 3D grayscale by + # using the same grayscale image in each of the rgb channels + # code below based on https://stackoverflow.com/questions/ + # 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv + + # invert image + uint16Image = 2**16 - uint16Image + + # convert to uint8 + ratio = np.amax(uint16Image) / 256 + uint8Image = (uint16Image / ratio).astype('uint8') + + rgbImage = np.zeros((2048, 2048, 3)) + rgbImage[:, :, 0] = uint8Image + rgbImage[:, :, 1] = uint8Image + rgbImage[:, :, 2] = uint8Image + rgbImage = rgbImage.astype('uint8') + + return rgbImage + +def apply_watershed(self, nucleiImages: np.ndarray, + watershedMarkers: np.ndarray) -> np.ndarray: + + watershedOutput = np.zeros(watershedMarkers.shape) + for z in range(len(self.dataSet.get_z_positions())): + rgbImage = self.convert_grayscale_to_rgb(nucleiImages[z, :, :]) + watershedOutput[z, :, :] = cv2.watershed(rgbImage, + watershedMarkers[z, :, :]. + astype('int32')) + watershedOutput[z, :, :][watershedOutput[z, :, :] <= 100] = 0 + + return watershedOutput + +def get_overlapping_nuclei(self, watershedZ0: np.ndarray, + watershedZ1: np.ndarray, n0: int): + z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) + z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes > 100] + + if z1NucleiIndexes.shape[0] > 0: + + # calculate overlap fraction + n0Area = np.count_nonzero(watershedZ0 == n0) + n1Area = np.zeros(len(z1NucleiIndexes)) + overlapArea = np.zeros(len(z1NucleiIndexes)) + + for ii in range(len(z1NucleiIndexes)): + n1 = z1NucleiIndexes[ii] + n1Area[ii] = np.count_nonzero(watershedZ1 == n1) + overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * + (watershedZ1 == n1)) + + n0OverlapFraction = np.asarray(overlapArea / n0Area) + n1OverlapFraction = np.asarray(overlapArea / n1Area) + index = list(range(len(n0OverlapFraction))) + + # select the nuclei that has the highest fraction in n0 and n1 + r1, r2, indexSorted = zip(*sorted(zip(n0OverlapFraction, + n1OverlapFraction, + index), + reverse=True)) + + if (n0OverlapFraction[indexSorted[0]] > 0.2 and + n1OverlapFraction[indexSorted[0]] > 0.5): + return m1NucleiIndexes[indexSorted[0]], + n0OverlapFraction[indexSorted[0]], + n1OverlapFraction[indexSorted[0]] + else: + return False, False, False + else: + return False, False, False + +def combine_2d_segmentation_masks_into_3d(self, + watershedOutput: + np.ndarray) -> np.ndarray: + # TO DO: Add description + + # TO DO: this implementation is very rough, needs to be improved. + # good just for testing purposes + + # Initialize empty array with size as watershedOutput array + watershedCombinedZ = np.zeros(watershedOutput.shape) + + # copy the mask of the section farthest to the coverslip + watershedCombinedZ[-1, :, :] = watershedOutput[-1, :, :] + + # starting far from coverslip + for z in range(len(self.dataSet.get_z_positions())-1, 0, -1): + zNucleiIndex = np.unique(watershedOutput[z, :, :])[ + np.unique(watershedOutput[z, :, :]) > 100] + + for n0 in zNucleiIndex: + n1, f0, f1 = self.get_overlapping_nuclei( + watershedCombinedZ[z, :, :], + watershedOutput[z-1, :, :], + n0) + if n1: + watershedCombinedZ[z-1, :, :][(watershedOutput[z-1, :, :] == + n1)] = n0 + return watershedCombinedZ From 589f7c73cac02400f106deaaec7fd8c4c590c2b0 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 15:30:14 -0500 Subject: [PATCH 058/419] removing utility functions from segment.py --- merlin/analysis/segment.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index e62847ee..5cb69525 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -9,7 +9,6 @@ from shapely import geometry from typing import List, Dict from scipy.spatial import cKDTree -from scipy.ndimage.morphology import binary_fill_holes from merlin.core import dataset from merlin.core import analysistask @@ -174,11 +173,13 @@ def get_cell_boundaries(self) -> List[spatialfeature.SpatialFeature]: def _run_analysis(self, fragmentIndex): startTime = time.time() + print('Entered the _run_analysis method, FOV ' + str(fragmentIndex) ) + globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) - print('reading indexes for fov ' + str(fragmentIndex) ) + print(' globalTask loaded') # read membrane (seed) and nuclei (watershed) indexes membraneIndex = self.dataSet \ @@ -191,7 +192,7 @@ def _run_analysis(self, fragmentIndex): self.parameters['nuclei_channel_name']) endTime = time.time() - print("Indexes read, ET {:.2f} min"\ + print(" image indexes read, ET {:.2f} min"\ .format((endTime - startTime) / 60)) # read membrane (seed) and nuclei (watershed) images @@ -199,31 +200,31 @@ def _run_analysis(self, fragmentIndex): nucleiImages = self._read_image_stack(fragmentIndex, nucleiIndex) endTime = time.time() - print("Images read, ET {:.2f} min" \ + print(" images read, ET {:.2f} min" \ .format((endTime - startTime) / 60)) # Prepare masks for cv2 watershed - watershedMarkers = self._get_watershed_markers(nucleiImages, - membraneImages) + watershedMarkers = watershed.get_cv2_watershed_markers(nucleiImages, + membraneImages) endTime = time.time() - print("Markers calculated, ET {:.2f} min" \ + print(" markers calculated, ET {:.2f} min" \ .format((endTime - startTime) / 60)) # perform watershed in individual z positions - watershedOutput = self._apply_watershed(nucleiImages, - watershedMarkers) + watershedOutput = watershed.apply_cv2_watershed(nucleiImages, + watershedMarkers) endTime = time.time() - print("watershed calculated, ET {:.2f} min" \ + print(" watershed calculated, ET {:.2f} min" \ .format((endTime - startTime) / 60)) # combine all z positions in watershed - watershedCombinedOutput = self._combine_watershed_z_positions( - watershedOutput) + watershedCombinedOutput = watershed \ + .combine_2d_segmentation_masks_into_3d(watershedOutput) endTime = time.time() - print("watershed z positions combined, ET {:.2f} min" \ + print(" watershed z positions combined, ET {:.2f} min" \ .format((endTime - startTime) / 60)) # get features from mask. This is the slowestart (6 min for the @@ -238,7 +239,7 @@ def _run_analysis(self, fragmentIndex): featureDB.write_features(featureList, fragmentIndex) endTime = time.time() - print("features written, ET {:.2f} min" \ + print(" features written, ET {:.2f} min" \ .format((endTime - startTime) / 60)) def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: @@ -246,7 +247,7 @@ def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: self.parameters['warp_task']) return np.array([warpTask.get_aligned_image(fov, channelIndex, z) for z in range(len(self.dataSet.get_z_positions()))]) - +""" def _get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: # generate mask based on thresholding mask = np.zeros(membraneImages.shape) @@ -464,6 +465,7 @@ def _combine_watershed_z_positions(self, watershedCombinedZ[z-1, :, :][(watershedOutput[z-1, :, :] == n1)] = n0 return watershedCombinedZ +""" class CleanCellBoundaries(analysistask.ParallelAnalysisTask): ''' From 4756d0f38308b1057e383870b65a31b67231fc0a Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 16:39:26 -0500 Subject: [PATCH 059/419] add comments to the header of multiple methods. --- merlin/util/watershed.py | 83 ++++++++++++++++++++++++++++++++++------ 1 file changed, 71 insertions(+), 12 deletions(-) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 3b5563f3..d9a820ff 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -171,8 +171,15 @@ def get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: return mask def get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: - - # TO DO: Add description + """Calculate binary mask with 1's in membrane pixels and 0 otherwise. + The images expected are some type of Nuclei label (e.g. DAPI) + + Args: + membraneImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) + """ # generate nuclei mask based on thresholding thresholdingMask = np.zeros(nucleiImages.shape) @@ -234,7 +241,18 @@ def get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, membraneImages: np.ndarray) -> np.ndarray: - # TO DO: Add description + """Combine membrane and nuclei markers into a single multilabel mask + for CV2 watershed + + Args: + nucleiImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + membraneImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) of + cv2-compatible watershed markers + """ nucleiMask = self.get_nuclei_mask(nucleiImages) membraneMask = self.get_membrane_mask(membraneImages) @@ -273,10 +291,16 @@ def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, return watershedMarker def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: - # cv2 only works in 3D images of 8bit. Make a 3D grayscale by - # using the same grayscale image in each of the rgb channels - # code below based on https://stackoverflow.com/questions/ - # 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv + """Convert a 16 bit 2D grayscale image into a 3D 8-bit RGB image. + cv2 only works in 8-bit. Based on https://stackoverflow.com/questions/ + 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv3D + + Args: + uint16Image: a 2 dimensional numpy array containing the 16-bit + image + Returns: + ndarray containing a 3 dimensional 8-bit image stack + """ # invert image uint16Image = 2**16 - uint16Image @@ -293,8 +317,20 @@ def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: return rgbImage -def apply_watershed(self, nucleiImages: np.ndarray, +def apply_cv2_watershed(self, nucleiImages: np.ndarray, watershedMarkers: np.ndarray) -> np.ndarray: + """Perform watershed using cv2 + + Args: + nucleiImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + watershedMarkers: a 3 dimensional numpy array containing the cv2 + markers arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) of + segmented cells. masks in different z positions are + independent + """ watershedOutput = np.zeros(watershedMarkers.shape) for z in range(len(self.dataSet.get_z_positions())): @@ -308,6 +344,22 @@ def apply_watershed(self, nucleiImages: np.ndarray, def get_overlapping_nuclei(self, watershedZ0: np.ndarray, watershedZ1: np.ndarray, n0: int): + """Perform watershed using cv2 + + Args: + watershedZ0: a 2 dimensional numpy array containing a + segmentation mask + watershedZ1: a 2 dimensional numpy array containing a + segmentation mask adjacent to watershedZ1 + n0: an integer with the index of the cell/nuclei to be compared + between the provided watershed segmentation masks + Returns: + a tuple (n1, f0, f1) containing the label of the cell in Z1 + overlapping n0 (n1), the fraction of n0 overlaping n1 (f0) and + the fraction of n1 overlapping n0 (f1) + + """ + z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes > 100] @@ -336,7 +388,7 @@ def get_overlapping_nuclei(self, watershedZ0: np.ndarray, if (n0OverlapFraction[indexSorted[0]] > 0.2 and n1OverlapFraction[indexSorted[0]] > 0.5): - return m1NucleiIndexes[indexSorted[0]], + return z1NucleiIndexes[indexSorted[0]], n0OverlapFraction[indexSorted[0]], n1OverlapFraction[indexSorted[0]] else: @@ -347,10 +399,17 @@ def get_overlapping_nuclei(self, watershedZ0: np.ndarray, def combine_2d_segmentation_masks_into_3d(self, watershedOutput: np.ndarray) -> np.ndarray: - # TO DO: Add description + """Take a 3 dimensional watershed masks and relabel them so that + nuclei in adjacent sections have the same label if the area their + overlap surpases certain threshold - # TO DO: this implementation is very rough, needs to be improved. - # good just for testing purposes + Args: + watershedOutput: a 3 dimensional numpy array containing the + segmentation masks arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) of + relabeled segmented cells + """ # Initialize empty array with size as watershedOutput array watershedCombinedZ = np.zeros(watershedOutput.shape) From dc52adaed018adf942826a4915135bdd741d05e6 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 16:44:13 -0500 Subject: [PATCH 060/419] remove commented methods --- merlin/analysis/segment.py | 220 +------------------------------------ 1 file changed, 1 insertion(+), 219 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 5cb69525..9f27e36a 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -247,225 +247,7 @@ def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: self.parameters['warp_task']) return np.array([warpTask.get_aligned_image(fov, channelIndex, z) for z in range(len(self.dataSet.get_z_positions()))]) -""" - def _get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: - # generate mask based on thresholding - mask = np.zeros(membraneImages.shape) - fineBlockSize = 61 - for z in range(len(self.dataSet.get_z_positions())): - mask[z, :, :] = (membraneImages[z, :, :] > - filters.threshold_local(membraneImages[z, :, :], - fineBlockSize, - offset=0)) - mask[z, :, :] = morphology.remove_small_objects( - mask[z, :, :].astype('bool'), - min_size=100, - connectivity=1) - mask[z, :, :] = morphology.binary_closing(mask[z, :, :], - morphology.selem.disk(5)) - mask[z, :, :] = morphology.skeletonize(mask[z, :, :]) - - # combine masks - return mask - - def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: - # generate nuclei mask based on thresholding - thresholdingMask = np.zeros(nucleiImages.shape) - coarseBlockSize = 241 - fineBlockSize = 61 - for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = (nucleiImages[z, :, :] > - filters.threshold_local( - nucleiImages[z, :, :], - coarseBlockSize, - offset=0)) - fineThresholdingMask = (nucleiImages[z, :, :] > - filters.threshold_local( - nucleiImages[z, :, :], - fineBlockSize, - offset=0)) - thresholdingMask[z, :, :] = (coarseThresholdingMask * - fineThresholdingMask) - thresholdingMask[z, :, :] = binary_fill_holes( - thresholdingMask[z, :, :]) - - # generate border mask, necessary to avoid making a single - # connected component when using binary_fill_holes below - borderMask = np.zeros((2048, 2048)) - borderMask[25:2023, 25:2023] = 1 - - # TODO - use the image size variable for borderMask - - # generate nuclei mask from hessian, fine - fineHessianMask = np.zeros(nucleiImages.shape) - for z in range(len(self.dataSet.get_z_positions())): - fineHessian = filters.hessian(nucleiImages[z, :, :]) - fineHessianMask[z, :, :] = fineHessian == fineHessian.max() - fineHessianMask[z, :, :] = morphology.binary_closing( - fineHessianMask[z, :, :], - morphology.selem.disk(5)) - fineHessianMask[z, :, :] = fineHessianMask[z, :, :] * borderMask - fineHessianMask[z, :, :] = binary_fill_holes( - fineHessianMask[z, :, :]) - - # generate dapi mask from hessian, coarse - coarseHessianMask = np.zeros(nucleiImages.shape) - for z in range(len(self.dataSet.get_z_positions())): - coarseHessian = filters.hessian(nucleiImages[z, :, :] - - morphology.white_tophat( - nucleiImages[z, :, :], - morphology.selem.disk(20))) - coarseHessianMask[z, :, :] = coarseHessian == coarseHessian.max() - coarseHessianMask[z, :, :] = morphology.binary_closing( - coarseHessianMask[z, :, :], morphology.selem.disk(5)) - coarseHessianMask[z, :, :] = (coarseHessianMask[z, :, :] * - borderMask) - coarseHessianMask[z, :, :] = binary_fill_holes( - coarseHessianMask[z, :, :]) - - # combine masks - nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask - return binary_fill_holes(nucleiMask) - - def _get_watershed_markers(self, nucleiImages: np.ndarray, - membraneImages: np.ndarray) -> np.ndarray: - - nucleiMask = self._get_nuclei_mask(nucleiImages) - membraneMask = self._get_membrane_mask(membraneImages) - - watershedMarker = np.zeros(nucleiMask.shape) - - for z in range(len(self.dataSet.get_z_positions())): - - # generate areas of sure bg and fg, as well as the area of - # unknown classification - background = morphology.dilation(nucleiMask[z, :, :], - morphology.selem.disk(15)) - membraneDilated = morphology.dilation( - membraneMask[z, :, :].astype('bool'), - morphology.selem.disk(10)) - foreground = morphology.erosion(nucleiMask[z, :, :] * ~ - membraneDilated, - morphology.selem.disk(5)) - unknown = background * ~ foreground - - background = np.uint8(background) * 255 - foreground = np.uint8(foreground) * 255 - unknown = np.uint8(unknown) * 255 - - # Marker labelling - ret, markers = cv2.connectedComponents(foreground) - - # Add one to all labels so that sure background is not 0, but 1 - markers = markers + 100 - - # Now, mark the region of unknown with zero - markers[unknown == 255] = 0 - - watershedMarker[z, :, :] = markers - - return watershedMarker - - def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: - # cv2 only works in 3D images of 8bit. Make a 3D grayscale by - # using the same grayscale image in each of the rgb channels - # code below based on https://stackoverflow.com/questions/ - # 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv - - # invert image - uint16Image = 2**16 - uint16Image - - # convert to uint8 - ratio = np.amax(uint16Image) / 256 - uint8Image = (uint16Image / ratio).astype('uint8') - - rgbImage = np.zeros((2048, 2048, 3)) - rgbImage[:, :, 0] = uint8Image - rgbImage[:, :, 1] = uint8Image - rgbImage[:, :, 2] = uint8Image - rgbImage = rgbImage.astype('uint8') - - return rgbImage - - def _apply_watershed(self, nucleiImages: np.ndarray, - watershedMarkers: np.ndarray) -> np.ndarray: - - watershedOutput = np.zeros(watershedMarkers.shape) - for z in range(len(self.dataSet.get_z_positions())): - rgbImage = self._convert_grayscale_to_rgb(nucleiImages[z, :, :]) - watershedOutput[z, :, :] = cv2.watershed(rgbImage, - watershedMarkers[z, :, :]. - astype('int32')) - watershedOutput[z, :, :][watershedOutput[z, :, :] <= 100] = 0 - - return watershedOutput - - def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, - watershedZ1: np.ndarray, n0: int): - z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) - z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes > 100] - - if z1NucleiIndexes.shape[0] > 0: - - # calculate overlap fraction - n0Area = np.count_nonzero(watershedZ0 == n0) - n1Area = np.zeros(len(z1NucleiIndexes)) - overlapArea = np.zeros(len(z1NucleiIndexes)) - - for ii in range(len(z1NucleiIndexes)): - n1 = z1NucleiIndexes[ii] - n1Area[ii] = np.count_nonzero(watershedZ1 == n1) - overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * - (watershedZ1 == n1)) - - n0OverlapFraction = np.asarray(overlapArea / n0Area) - n1OverlapFraction = np.asarray(overlapArea / n1Area) - index = list(range(len(n0OverlapFraction))) - - # select the nuclei that has the highest fraction in n0 and n1 - r1, r2, indexSorted = zip(*sorted(zip(n0OverlapFraction, - n1OverlapFraction, - index), - reverse=True)) - - if (n0OverlapFraction[indexSorted[0]] > 0.2 and - n1OverlapFraction[indexSorted[0]] > 0.5): - return m1NucleiIndexes[indexSorted[0]], - n0OverlapFraction[indexSorted[0]], - n1OverlapFraction[indexSorted[0]] - else: - return False, False, False - else: - return False, False, False - - def _combine_watershed_z_positions(self, - watershedOutput: - np.ndarray) -> np.ndarray: - - # TO DO: this implementation is very rough, needs to be improved. - # good just for testing purposes - - # Initialize empty array with size as watershedOutput array - watershedCombinedZ = np.zeros(watershedOutput.shape) - - # copy the mask of the section farthest to the coverslip - watershedCombinedZ[-1, :, :] = watershedOutput[-1, :, :] - - # starting far from coverslip - for z in range(len(self.dataSet.get_z_positions())-1, 0, -1): - zNucleiIndex = np.unique(watershedOutput[z, :, :])[ - np.unique(watershedOutput[z, :, :]) > 100] - - for n0 in zNucleiIndex: - n1, f0, f1 = self._get_overlapping_nuclei( - watershedCombinedZ[z, :, :], - watershedOutput[z-1, :, :], - n0) - if n1: - watershedCombinedZ[z-1, :, :][(watershedOutput[z-1, :, :] == - n1)] = n0 - return watershedCombinedZ -""" + class CleanCellBoundaries(analysistask.ParallelAnalysisTask): ''' From b83f5601d80095923927c49ddab1a651e7c637d5 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 16:59:49 -0500 Subject: [PATCH 061/419] pep8 compliance --- merlin/analysis/segment.py | 23 +++++++++++------------ merlin/util/watershed.py | 28 ++++++++++++++-------------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 9f27e36a..f09e7e97 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -16,7 +16,7 @@ from merlin.util import watershed import pandas import networkx as nx -import time +import time class FeatureSavingAnalysisTask(analysistask.ParallelAnalysisTask): @@ -173,12 +173,11 @@ def get_cell_boundaries(self) -> List[spatialfeature.SpatialFeature]: def _run_analysis(self, fragmentIndex): startTime = time.time() - print('Entered the _run_analysis method, FOV ' + str(fragmentIndex) ) + print('Entered the _run_analysis method, FOV ' + str(fragmentIndex)) globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) - print(' globalTask loaded') # read membrane (seed) and nuclei (watershed) indexes @@ -192,8 +191,8 @@ def _run_analysis(self, fragmentIndex): self.parameters['nuclei_channel_name']) endTime = time.time() - print(" image indexes read, ET {:.2f} min"\ - .format((endTime - startTime) / 60)) + print(" image indexes read, ET {:.2f} min" \ + .format((endTime - startTime) / 60)) # read membrane (seed) and nuclei (watershed) images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) @@ -201,7 +200,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" images read, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + .format((endTime - startTime) / 60)) # Prepare masks for cv2 watershed watershedMarkers = watershed.get_cv2_watershed_markers(nucleiImages, @@ -209,7 +208,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" markers calculated, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + .format((endTime - startTime) / 60)) # perform watershed in individual z positions watershedOutput = watershed.apply_cv2_watershed(nucleiImages, @@ -217,7 +216,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" watershed calculated, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + .format((endTime - startTime) / 60)) # combine all z positions in watershed watershedCombinedOutput = watershed \ @@ -225,10 +224,10 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" watershed z positions combined, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + .format((endTime - startTime) / 60)) - # get features from mask. This is the slowestart (6 min for the - # previous part, 15+ for the rest, for a 7 frame Image. + # get features from mask. This is the slowestart (6 min for the + # previous part, 15+ for the rest, for a 7 frame Image. zPos = np.array(self.dataSet.get_data_organization().get_z_positions()) featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( (watershedCombinedOutput == i), fragmentIndex, @@ -240,7 +239,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" features written, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + .format((endTime - startTime) / 60)) def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index d9a820ff..d6d4be07 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -143,13 +143,13 @@ def prepare_watershed_images(watershedImageStack: np.ndarray def get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: """Calculate binary mask with 1's in membrane pixels and 0 otherwise. - The images expected are some type of membrane label (WGA, ConA, + The images expected are some type of membrane label (WGA, ConA, Lamin, Cadherins) Args: membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). - Returns: + Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) """ mask = np.zeros(membraneImages.shape) @@ -177,7 +177,7 @@ def get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: Args: membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). - Returns: + Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) """ @@ -291,15 +291,15 @@ def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, return watershedMarker def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: - """Convert a 16 bit 2D grayscale image into a 3D 8-bit RGB image. + """Convert a 16 bit 2D grayscale image into a 3D 8-bit RGB image. cv2 only works in 8-bit. Based on https://stackoverflow.com/questions/ - 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv3D + 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv3D Args: - uint16Image: a 2 dimensional numpy array containing the 16-bit + uint16Image: a 2 dimensional numpy array containing the 16-bit image Returns: - ndarray containing a 3 dimensional 8-bit image stack + ndarray containing a 3 dimensional 8-bit image stack """ # invert image @@ -347,15 +347,15 @@ def get_overlapping_nuclei(self, watershedZ0: np.ndarray, """Perform watershed using cv2 Args: - watershedZ0: a 2 dimensional numpy array containing a + watershedZ0: a 2 dimensional numpy array containing a segmentation mask watershedZ1: a 2 dimensional numpy array containing a segmentation mask adjacent to watershedZ1 n0: an integer with the index of the cell/nuclei to be compared between the provided watershed segmentation masks Returns: - a tuple (n1, f0, f1) containing the label of the cell in Z1 - overlapping n0 (n1), the fraction of n0 overlaping n1 (f0) and + a tuple (n1, f0, f1) containing the label of the cell in Z1 + overlapping n0 (n1), the fraction of n0 overlaping n1 (f0) and the fraction of n1 overlapping n0 (f1) """ @@ -399,12 +399,12 @@ def get_overlapping_nuclei(self, watershedZ0: np.ndarray, def combine_2d_segmentation_masks_into_3d(self, watershedOutput: np.ndarray) -> np.ndarray: - """Take a 3 dimensional watershed masks and relabel them so that - nuclei in adjacent sections have the same label if the area their - overlap surpases certain threshold + """Take a 3 dimensional watershed masks and relabel them so that + nuclei in adjacent sections have the same label if the area their + overlap surpases certain threshold Args: - watershedOutput: a 3 dimensional numpy array containing the + watershedOutput: a 3 dimensional numpy array containing the segmentation masks arranged as (z, x, y). Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) of From 31c4b69851b253446110ce9d44e1f44ae7497d9d Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 17:04:07 -0500 Subject: [PATCH 062/419] pep8 compliance --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index f09e7e97..9af13987 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -191,8 +191,8 @@ def _run_analysis(self, fragmentIndex): self.parameters['nuclei_channel_name']) endTime = time.time() - print(" image indexes read, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + print(" image indexes read, ET {:.2f} min".format( + (endTime - startTime) / 60)) # read membrane (seed) and nuclei (watershed) images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) From a4e37221504bd281909819c1c7cb652bd701551f Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 17:11:46 -0500 Subject: [PATCH 063/419] pep8 compliance --- merlin/analysis/segment.py | 20 ++++++++++---------- merlin/util/watershed.py | 18 ++++++++++++------ 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 9af13987..2d93fcfa 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -199,32 +199,32 @@ def _run_analysis(self, fragmentIndex): nucleiImages = self._read_image_stack(fragmentIndex, nucleiIndex) endTime = time.time() - print(" images read, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + print(" images read, ET {:.2f} min".format( + (endTime - startTime) / 60)) # Prepare masks for cv2 watershed watershedMarkers = watershed.get_cv2_watershed_markers(nucleiImages, membraneImages) endTime = time.time() - print(" markers calculated, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + print(" markers calculated, ET {:.2f} min".format( + (endTime - startTime) / 60)) # perform watershed in individual z positions watershedOutput = watershed.apply_cv2_watershed(nucleiImages, watershedMarkers) endTime = time.time() - print(" watershed calculated, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + print(" watershed calculated, ET {:.2f} min".format( + (endTime - startTime) / 60)) # combine all z positions in watershed watershedCombinedOutput = watershed \ .combine_2d_segmentation_masks_into_3d(watershedOutput) endTime = time.time() - print(" watershed z positions combined, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + print(" watershed z positions combined, ET {:.2f} min".format( + (endTime - startTime) / 60)) # get features from mask. This is the slowestart (6 min for the # previous part, 15+ for the rest, for a 7 frame Image. @@ -238,8 +238,8 @@ def _run_analysis(self, fragmentIndex): featureDB.write_features(featureList, fragmentIndex) endTime = time.time() - print(" features written, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + print(" features written, ET {:.2f} min".format( + (endTime - startTime) / 60)) def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index d6d4be07..a79f25cd 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -141,6 +141,7 @@ def prepare_watershed_images(watershedImageStack: np.ndarray return normalizedWatershed, watershedMask + def get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: """Calculate binary mask with 1's in membrane pixels and 0 otherwise. The images expected are some type of membrane label (WGA, ConA, @@ -170,6 +171,7 @@ def get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: # combine masks return mask + def get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: """Calculate binary mask with 1's in membrane pixels and 0 otherwise. The images expected are some type of Nuclei label (e.g. DAPI) @@ -239,6 +241,7 @@ def get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask return binary_fill_holes(nucleiMask) + def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, membraneImages: np.ndarray) -> np.ndarray: """Combine membrane and nuclei markers into a single multilabel mask @@ -290,6 +293,7 @@ def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, return watershedMarker + def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: """Convert a 16 bit 2D grayscale image into a 3D 8-bit RGB image. cv2 only works in 8-bit. Based on https://stackoverflow.com/questions/ @@ -298,7 +302,7 @@ def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: Args: uint16Image: a 2 dimensional numpy array containing the 16-bit image - Returns: + Returns: ndarray containing a 3 dimensional 8-bit image stack """ @@ -317,8 +321,9 @@ def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: return rgbImage + def apply_cv2_watershed(self, nucleiImages: np.ndarray, - watershedMarkers: np.ndarray) -> np.ndarray: + watershedMarkers: np.ndarray) -> np.ndarray: """Perform watershed using cv2 Args: @@ -326,9 +331,9 @@ def apply_cv2_watershed(self, nucleiImages: np.ndarray, arranged as (z, x, y). watershedMarkers: a 3 dimensional numpy array containing the cv2 markers arranged as (z, x, y). - Returns: + Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) of - segmented cells. masks in different z positions are + segmented cells. masks in different z positions are independent """ @@ -342,6 +347,7 @@ def apply_cv2_watershed(self, nucleiImages: np.ndarray, return watershedOutput + def get_overlapping_nuclei(self, watershedZ0: np.ndarray, watershedZ1: np.ndarray, n0: int): """Perform watershed using cv2 @@ -357,7 +363,6 @@ def get_overlapping_nuclei(self, watershedZ0: np.ndarray, a tuple (n1, f0, f1) containing the label of the cell in Z1 overlapping n0 (n1), the fraction of n0 overlaping n1 (f0) and the fraction of n1 overlapping n0 (f1) - """ z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) @@ -396,6 +401,7 @@ def get_overlapping_nuclei(self, watershedZ0: np.ndarray, else: return False, False, False + def combine_2d_segmentation_masks_into_3d(self, watershedOutput: np.ndarray) -> np.ndarray: @@ -406,7 +412,7 @@ def combine_2d_segmentation_masks_into_3d(self, Args: watershedOutput: a 3 dimensional numpy array containing the segmentation masks arranged as (z, x, y). - Returns: + Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) of relabeled segmented cells """ From ab0a4799940b2f0b4fa5599d77a52cc5c314df87 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 17:12:35 -0500 Subject: [PATCH 064/419] pep8 compliance --- merlin/util/watershed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index a79f25cd..9e40d7bf 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -252,7 +252,7 @@ def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, arranged as (z, x, y). membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). - Returns: + Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) of cv2-compatible watershed markers """ From 1424f1f37ece87118878bce9b8e4d2c14a58c270 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 12 Feb 2020 13:03:26 -0500 Subject: [PATCH 065/419] add george's modifications to dataportal --- requirements.txt | 2 +- test/test_dataportal.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index e84c4226..9b6a77a0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ scipy>=1.2 matplotlib networkx rtree -shapely!=1.7a2 +shapely<1.7a2 seaborn>=0.9.0 pyqt5 Sphinx diff --git a/test/test_dataportal.py b/test/test_dataportal.py index e6a235ff..d5c1449f 100644 --- a/test/test_dataportal.py +++ b/test/test_dataportal.py @@ -23,7 +23,7 @@ def local_data_portal(): def s3_data_portal(): - yield dataportal.S3DataPortal('s3://merlin-test-bucket/test-files', + yield dataportal.S3DataPortal('s3://merlin-test-bucket-vg/test-files', region_name='us-east-2', config=Config(signature_version=UNSIGNED)) From 60acd7246a1a06562dd50a0147ccfb3d810dd35e Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 15 Feb 2020 09:38:38 -0500 Subject: [PATCH 066/419] adding additional modifications to aws --- merlin/util/dataportal.py | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/merlin/util/dataportal.py b/merlin/util/dataportal.py index e3d0f6e8..9fcd4631 100644 --- a/merlin/util/dataportal.py +++ b/merlin/util/dataportal.py @@ -2,9 +2,11 @@ import boto3 import botocore from google.cloud import storage +from google.cloud import exceptions from urllib import parse from abc import abstractmethod, ABC from typing import List +from time import sleep class DataPortal(ABC): @@ -340,12 +342,41 @@ def get_sibling_with_extension(self, newExtension: str): return GCloudFilePortal( self._exchange_extension(newExtension), self._client) + def _error_tolerant_access(self, request, attempts = 5, wait = 60): + while attempts > 0: + try: + file = self._fileHandle.download_as_string() + return file + except (exceptions.GatewayTimeout, exceptions.ServiceUnavailable): + attempts -= 1 + sleep(60) + if attempts == 0: + raise + def read_as_text(self): - return self._fileHandle.download_as_string().decode('utf-8') + backoffSeries= [1,2,4,8,16,32,64,128,256] + for sleepDuration in backoffSeries: + try: + file = self._fileHandle.download_as_string().decode('utf-8') + return file + except (exceptions.GatewayTimeout, exceptions.ServiceUnavailable): + if sleepDuration == backoffSeries[-1]: + raise + else: + sleep(sleepDuration) def read_file_bytes(self, startByte, endByte): - return self._fileHandle.download_as_string( - start=startByte, end=endByte-1) + backoffSeries= [1,2,4,8,16,32,64,128,256] + for sleepDuration in backoffSeries: + try: + file = self._fileHandle.download_as_string(start=startByte, + end=endByte-1) + return file + except (exceptions.GatewayTimeout, exceptions.ServiceUnavailable): + if sleepDuration == backoffSeries[-1]: + raise + else: + sleep(sleepDuration) def close(self) -> None: pass From db1d48f3d5b7aa4e014898d19a87b68e83cc8b86 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 18 Feb 2020 14:45:04 -0500 Subject: [PATCH 067/419] fixing indentation --- merlin/util/dataportal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/util/dataportal.py b/merlin/util/dataportal.py index 9fcd4631..41e9634f 100644 --- a/merlin/util/dataportal.py +++ b/merlin/util/dataportal.py @@ -354,7 +354,7 @@ def _error_tolerant_access(self, request, attempts = 5, wait = 60): raise def read_as_text(self): - backoffSeries= [1,2,4,8,16,32,64,128,256] + backoffSeries = [1,2,4,8,16,32,64,128,256] for sleepDuration in backoffSeries: try: file = self._fileHandle.download_as_string().decode('utf-8') @@ -366,7 +366,7 @@ def read_as_text(self): sleep(sleepDuration) def read_file_bytes(self, startByte, endByte): - backoffSeries= [1,2,4,8,16,32,64,128,256] + backoffSeries = [1,2,4,8,16,32,64,128,256] for sleepDuration in backoffSeries: try: file = self._fileHandle.download_as_string(start=startByte, From 97698224a12bbf4b8b9177c8cdd58e303d02b5c0 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 18 Feb 2020 14:53:18 -0500 Subject: [PATCH 068/419] pep8 compliance --- merlin/util/dataportal.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/merlin/util/dataportal.py b/merlin/util/dataportal.py index 41e9634f..2af7641f 100644 --- a/merlin/util/dataportal.py +++ b/merlin/util/dataportal.py @@ -342,7 +342,7 @@ def get_sibling_with_extension(self, newExtension: str): return GCloudFilePortal( self._exchange_extension(newExtension), self._client) - def _error_tolerant_access(self, request, attempts = 5, wait = 60): + def _error_tolerant_access(self, request, attempts=5, wait=60): while attempts > 0: try: file = self._fileHandle.download_as_string() @@ -354,7 +354,7 @@ def _error_tolerant_access(self, request, attempts = 5, wait = 60): raise def read_as_text(self): - backoffSeries = [1,2,4,8,16,32,64,128,256] + backoffSeries = [1, 2, 4, 8, 16, 32, 64, 128, 256] for sleepDuration in backoffSeries: try: file = self._fileHandle.download_as_string().decode('utf-8') @@ -366,11 +366,11 @@ def read_as_text(self): sleep(sleepDuration) def read_file_bytes(self, startByte, endByte): - backoffSeries = [1,2,4,8,16,32,64,128,256] - for sleepDuration in backoffSeries: + backoffSeries = [1, 2, 4, 8, 16, 32, 64, 128, 256] + for sleepDuration in backoffSeries: try: file = self._fileHandle.download_as_string(start=startByte, - end=endByte-1) + end=endByte-1) return file except (exceptions.GatewayTimeout, exceptions.ServiceUnavailable): if sleepDuration == backoffSeries[-1]: From 331206340eab30268fc46cc4997487f90a451023 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 18 Feb 2020 14:54:41 -0500 Subject: [PATCH 069/419] pep8 compliance --- merlin/util/dataportal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/util/dataportal.py b/merlin/util/dataportal.py index 2af7641f..ad250e4e 100644 --- a/merlin/util/dataportal.py +++ b/merlin/util/dataportal.py @@ -370,7 +370,7 @@ def read_file_bytes(self, startByte, endByte): for sleepDuration in backoffSeries: try: file = self._fileHandle.download_as_string(start=startByte, - end=endByte-1) + end=endByte-1) return file except (exceptions.GatewayTimeout, exceptions.ServiceUnavailable): if sleepDuration == backoffSeries[-1]: From 0b0df8b3d0a94a335765f9dfde5c583591708d5c Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 18 Feb 2020 14:55:14 -0500 Subject: [PATCH 070/419] pep8 compliance --- merlin/util/dataportal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/util/dataportal.py b/merlin/util/dataportal.py index ad250e4e..bbbc2cb0 100644 --- a/merlin/util/dataportal.py +++ b/merlin/util/dataportal.py @@ -370,7 +370,7 @@ def read_file_bytes(self, startByte, endByte): for sleepDuration in backoffSeries: try: file = self._fileHandle.download_as_string(start=startByte, - end=endByte-1) + end=endByte-1) return file except (exceptions.GatewayTimeout, exceptions.ServiceUnavailable): if sleepDuration == backoffSeries[-1]: From 8917fb3332e1be3a4e4571f7fe6de4ba60aed996 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Mon, 2 Mar 2020 14:03:44 -0500 Subject: [PATCH 071/419] added printing for debuging --- merlin/analysis/segment.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 2d93fcfa..3ee7b950 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -201,6 +201,8 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) + print(" membraneImages Type: " + type(membraneImages)) + print(" nucleiImages Type: " + type(nucleiImages)) # Prepare masks for cv2 watershed watershedMarkers = watershed.get_cv2_watershed_markers(nucleiImages, From 0cec2eb57fbcfff4b124327f2913dc91eb688e65 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Mon, 2 Mar 2020 14:26:51 -0500 Subject: [PATCH 072/419] added printing for debuging --- merlin/analysis/segment.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 3ee7b950..416e75e8 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -201,8 +201,16 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) - print(" membraneImages Type: " + type(membraneImages)) - print(" nucleiImages Type: " + type(nucleiImages)) + print(" membraneImages Type: " + str(type(membraneImages))) + print(" membraneImages Size: [" + + str(membraneImages.shape[0]) + + "," + str(membraneImages.shape[1]) + + "," + str(membraneImages.shape[2]) "]" ) + print(" nucleiImages Type: " + str(type(nucleiImages))) + print(" nucleiImages Size: [" + + str(nucleiImages.shape[0]) + + "," + str(nucleiImages.shape[1]) + + "," + str(nucleiImages.shape[2]) "]" ) # Prepare masks for cv2 watershed watershedMarkers = watershed.get_cv2_watershed_markers(nucleiImages, From 46af097b93d2b99d31ede12f4640150ee8ec4af7 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Mon, 2 Mar 2020 14:31:30 -0500 Subject: [PATCH 073/419] added printing for debuging --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 416e75e8..f1858aa4 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -205,12 +205,12 @@ def _run_analysis(self, fragmentIndex): print(" membraneImages Size: [" + str(membraneImages.shape[0]) + "," + str(membraneImages.shape[1]) - + "," + str(membraneImages.shape[2]) "]" ) + + "," + str(membraneImages.shape[2]) + "]" ) print(" nucleiImages Type: " + str(type(nucleiImages))) print(" nucleiImages Size: [" + str(nucleiImages.shape[0]) + "," + str(nucleiImages.shape[1]) - + "," + str(nucleiImages.shape[2]) "]" ) + + "," + str(nucleiImages.shape[2]) + "]" ) # Prepare masks for cv2 watershed watershedMarkers = watershed.get_cv2_watershed_markers(nucleiImages, From 5391cf1d5b81f102f351cd5afcc50d39f9bd8b30 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Mon, 2 Mar 2020 16:22:34 -0500 Subject: [PATCH 074/419] removing self calls --- merlin/util/watershed.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 9e40d7bf..0ccac583 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -142,7 +142,7 @@ def prepare_watershed_images(watershedImageStack: np.ndarray return normalizedWatershed, watershedMask -def get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: +def get_membrane_mask(membraneImages: np.ndarray) -> np.ndarray: """Calculate binary mask with 1's in membrane pixels and 0 otherwise. The images expected are some type of membrane label (WGA, ConA, Lamin, Cadherins) @@ -172,7 +172,7 @@ def get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: return mask -def get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: +def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: """Calculate binary mask with 1's in membrane pixels and 0 otherwise. The images expected are some type of Nuclei label (e.g. DAPI) @@ -242,7 +242,7 @@ def get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: return binary_fill_holes(nucleiMask) -def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, +def get_cv2_watershed_markers(nucleiImages: np.ndarray, membraneImages: np.ndarray) -> np.ndarray: """Combine membrane and nuclei markers into a single multilabel mask for CV2 watershed @@ -294,7 +294,7 @@ def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, return watershedMarker -def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: +def convert_grayscale_to_rgb(uint16Image: np.ndarray) -> np.ndarray: """Convert a 16 bit 2D grayscale image into a 3D 8-bit RGB image. cv2 only works in 8-bit. Based on https://stackoverflow.com/questions/ 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv3D @@ -322,7 +322,7 @@ def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: return rgbImage -def apply_cv2_watershed(self, nucleiImages: np.ndarray, +def apply_cv2_watershed(nucleiImages: np.ndarray, watershedMarkers: np.ndarray) -> np.ndarray: """Perform watershed using cv2 @@ -348,7 +348,7 @@ def apply_cv2_watershed(self, nucleiImages: np.ndarray, return watershedOutput -def get_overlapping_nuclei(self, watershedZ0: np.ndarray, +def get_overlapping_nuclei(watershedZ0: np.ndarray, watershedZ1: np.ndarray, n0: int): """Perform watershed using cv2 @@ -402,8 +402,7 @@ def get_overlapping_nuclei(self, watershedZ0: np.ndarray, return False, False, False -def combine_2d_segmentation_masks_into_3d(self, - watershedOutput: +def combine_2d_segmentation_masks_into_3d(watershedOutput: np.ndarray) -> np.ndarray: """Take a 3 dimensional watershed masks and relabel them so that nuclei in adjacent sections have the same label if the area their From 972bfed74602398f7502a7c95ab527a389c4c719 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 3 Mar 2020 09:02:53 -0500 Subject: [PATCH 075/419] remove self calls --- merlin/util/watershed.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 0ccac583..60268345 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -11,7 +11,7 @@ from merlin.util import matlab """ -This module contains utility functions for preparing imagmes for +This module contains utility functions for preparing imagmes for watershed segmentation. """ @@ -155,7 +155,7 @@ def get_membrane_mask(membraneImages: np.ndarray) -> np.ndarray: """ mask = np.zeros(membraneImages.shape) fineBlockSize = 61 - for z in range(len(self.dataSet.get_z_positions())): + for z in range(membraneImages.shape[0]): mask[z, :, :] = (membraneImages[z, :, :] > filters.threshold_local(membraneImages[z, :, :], fineBlockSize, @@ -187,7 +187,7 @@ def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: thresholdingMask = np.zeros(nucleiImages.shape) coarseBlockSize = 241 fineBlockSize = 61 - for z in range(len(self.dataSet.get_z_positions())): + for z in range(nucleiImages.shape[0]): coarseThresholdingMask = (nucleiImages[z, :, :] > filters.threshold_local( nucleiImages[z, :, :], @@ -212,7 +212,7 @@ def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: # generate nuclei mask from hessian, fine fineHessianMask = np.zeros(nucleiImages.shape) - for z in range(len(self.dataSet.get_z_positions())): + for z in range(nucleiImages.shape[0]): fineHessian = filters.hessian(nucleiImages[z, :, :]) fineHessianMask[z, :, :] = fineHessian == fineHessian.max() fineHessianMask[z, :, :] = morphology.binary_closing( @@ -224,7 +224,7 @@ def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: # generate dapi mask from hessian, coarse coarseHessianMask = np.zeros(nucleiImages.shape) - for z in range(len(self.dataSet.get_z_positions())): + for z in range(nucleiImages.shape[0]): coarseHessian = filters.hessian(nucleiImages[z, :, :] - morphology.white_tophat( nucleiImages[z, :, :], @@ -257,12 +257,12 @@ def get_cv2_watershed_markers(nucleiImages: np.ndarray, cv2-compatible watershed markers """ - nucleiMask = self.get_nuclei_mask(nucleiImages) - membraneMask = self.get_membrane_mask(membraneImages) + nucleiMask = get_nuclei_mask(nucleiImages) + membraneMask = get_membrane_mask(membraneImages) watershedMarker = np.zeros(nucleiMask.shape) - for z in range(len(self.dataSet.get_z_positions())): + for z in range(nucleiImages.shape[0]): # generate areas of sure bg and fg, as well as the area of # unknown classification @@ -338,8 +338,8 @@ def apply_cv2_watershed(nucleiImages: np.ndarray, """ watershedOutput = np.zeros(watershedMarkers.shape) - for z in range(len(self.dataSet.get_z_positions())): - rgbImage = self.convert_grayscale_to_rgb(nucleiImages[z, :, :]) + for z in range(nucleiImages.shape[0]): + rgbImage = convert_grayscale_to_rgb(nucleiImages[z, :, :]) watershedOutput[z, :, :] = cv2.watershed(rgbImage, watershedMarkers[z, :, :]. astype('int32')) @@ -423,13 +423,12 @@ def combine_2d_segmentation_masks_into_3d(watershedOutput: watershedCombinedZ[-1, :, :] = watershedOutput[-1, :, :] # starting far from coverslip - for z in range(len(self.dataSet.get_z_positions())-1, 0, -1): + for z in range(watershedOutput.shape[0]-1, 0, -1): zNucleiIndex = np.unique(watershedOutput[z, :, :])[ np.unique(watershedOutput[z, :, :]) > 100] for n0 in zNucleiIndex: - n1, f0, f1 = self.get_overlapping_nuclei( - watershedCombinedZ[z, :, :], + n1, f0, f1 = get_overlapping_nuclei(watershedCombinedZ[z, :, :], watershedOutput[z-1, :, :], n0) if n1: From 10ae1bc20d78462d603617fed5a69e4b696412d6 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 3 Mar 2020 10:50:52 -0500 Subject: [PATCH 076/419] pep8 compliance --- merlin/analysis/segment.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index f1858aa4..9ed4e4f2 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -202,15 +202,15 @@ def _run_analysis(self, fragmentIndex): print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) print(" membraneImages Type: " + str(type(membraneImages))) - print(" membraneImages Size: [" - + str(membraneImages.shape[0]) - + "," + str(membraneImages.shape[1]) - + "," + str(membraneImages.shape[2]) + "]" ) + print(" membraneImages Size: [" + + str(membraneImages.shape[0]) + + "," + str(membraneImages.shape[1]) + + "," + str(membraneImages.shape[2]) + "]") print(" nucleiImages Type: " + str(type(nucleiImages))) - print(" nucleiImages Size: [" - + str(nucleiImages.shape[0]) - + "," + str(nucleiImages.shape[1]) - + "," + str(nucleiImages.shape[2]) + "]" ) + print(" nucleiImages Size: [" + + str(nucleiImages.shape[0]) + + "," + str(nucleiImages.shape[1]) + + "," + str(nucleiImages.shape[2]) + "]") # Prepare masks for cv2 watershed watershedMarkers = watershed.get_cv2_watershed_markers(nucleiImages, From 91f6ed4e1a15653c61472925f240d988d79f9be1 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 22 Apr 2020 21:44:41 -0400 Subject: [PATCH 077/419] change WatershedSegmentNucleiCV2 to WatershedSegmentCV2, modify accordingly to allow for cytoplasmic or nuclei segmentation --- merlin/analysis/segment.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 9ed4e4f2..7c2dbcde 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -124,7 +124,7 @@ def _read_and_filter_image_stack(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions()))]) -class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): +class WatershedSegmentCV2(FeatureSavingAnalysisTask): """ An analysis task that determines the boundaries of features in the @@ -136,7 +136,12 @@ class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): py_tutorials/py_imgproc/py_watershed/py_watershed.html. The watershed segmentation is performed in each z-position - independently and combined into 3D objects in a later step + independently and combined into 3D objects in a later step + + The class can be used to segment either nuclear or cytoplasmic + compartments. If both the compartment and membrane channels are the + same, the membrane channel is calculated from the edge transform of + the provided channel. Since each field of view is analyzed individually, the segmentation results should be cleaned in order to merge cells that cross the @@ -145,11 +150,11 @@ class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): def __init__(self, dataSet, parameters=None, analysisName=None): super().__init__(dataSet, parameters, analysisName) - + if 'membrane_channel_name' not in self.parameters: - self.parameters['membrane_channel_name'] = 'ConA' - if 'nuclei_channel_name' not in self.parameters: - self.parameters['nuclei_channel_name'] = 'DAPI' + self.parameters['membrane_channel_name'] = 'DAPI' + if 'compartment_channel_name' not in self.parameters: + self.parameters['compartment_channel_name'] = 'DAPI' def fragment_count(self): return len(self.dataSet.get_fovs()) From 9cc36a5f71aed726b85686af0f691fb7afe4873c Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 22 Apr 2020 21:57:33 -0400 Subject: [PATCH 078/419] change nuclei to compartment --- merlin/analysis/segment.py | 43 ++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 7c2dbcde..be944eb2 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -138,9 +138,9 @@ class WatershedSegmentCV2(FeatureSavingAnalysisTask): The watershed segmentation is performed in each z-position independently and combined into 3D objects in a later step - The class can be used to segment either nuclear or cytoplasmic - compartments. If both the compartment and membrane channels are the - same, the membrane channel is calculated from the edge transform of + The class can be used to segment either nuclear or cytoplasmic + compartments. If both the compartment and membrane channels are the + same, the membrane channel is calculated from the edge transform of the provided channel. Since each field of view is analyzed individually, the segmentation @@ -185,23 +185,30 @@ def _run_analysis(self, fragmentIndex): print(' globalTask loaded') - # read membrane (seed) and nuclei (watershed) indexes + # read membrane and compartment indexes membraneIndex = self.dataSet \ .get_data_organization() \ .get_data_channel_index( self.parameters['membrane_channel_name']) - nucleiIndex = self.dataSet \ - .get_data_organization() \ - .get_data_channel_index( - self.parameters['nuclei_channel_name']) + compartmentIndex = self.dataSet \ + .get_data_organization() \ + .get_data_channel_index( + self.parameters['compartment_channel_name']) + + if self.parameters['membrane_channel_name'] == + self.parameters['compartment_channel_name']: + membraneFlag = 0 + else: + membraneFlag = 1 endTime = time.time() print(" image indexes read, ET {:.2f} min".format( (endTime - startTime) / 60)) - # read membrane (seed) and nuclei (watershed) images + # read membrane and compartment images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) - nucleiImages = self._read_image_stack(fragmentIndex, nucleiIndex) + compartmentImages = self._read_image_stack(fragmentIndex, + compartmentIndex) endTime = time.time() print(" images read, ET {:.2f} min".format( @@ -211,22 +218,22 @@ def _run_analysis(self, fragmentIndex): + str(membraneImages.shape[0]) + "," + str(membraneImages.shape[1]) + "," + str(membraneImages.shape[2]) + "]") - print(" nucleiImages Type: " + str(type(nucleiImages))) - print(" nucleiImages Size: [" - + str(nucleiImages.shape[0]) - + "," + str(nucleiImages.shape[1]) - + "," + str(nucleiImages.shape[2]) + "]") + print(" compartmentImages Type: " + str(type(compartmentImages))) + print(" compartmentImages Size: [" + + str(compartmentImages.shape[0]) + + "," + str(compartmentImages.shape[1]) + + "," + str(compartmentImages.shape[2]) + "]") # Prepare masks for cv2 watershed - watershedMarkers = watershed.get_cv2_watershed_markers(nucleiImages, - membraneImages) + watershedMarkers = watershed.get_cv2_watershed_markers( + compartmentImages, membraneImages) endTime = time.time() print(" markers calculated, ET {:.2f} min".format( (endTime - startTime) / 60)) # perform watershed in individual z positions - watershedOutput = watershed.apply_cv2_watershed(nucleiImages, + watershedOutput = watershed.apply_cv2_watershed(compartmentImages, watershedMarkers) endTime = time.time() From 86f919c0b8275225d2e984cb80be455352e952ca Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 22 Apr 2020 22:08:40 -0400 Subject: [PATCH 079/419] change nuclei to compartment --- merlin/analysis/segment.py | 4 ++- merlin/util/watershed.py | 56 ++++++++++++++++++++------------------ 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index be944eb2..0b8de5ff 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -226,7 +226,9 @@ def _run_analysis(self, fragmentIndex): # Prepare masks for cv2 watershed watershedMarkers = watershed.get_cv2_watershed_markers( - compartmentImages, membraneImages) + compartmentImages, + membraneImages, + membraneFlag) endTime = time.time() print(" markers calculated, ET {:.2f} min".format( diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 60268345..16bf72bd 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -242,36 +242,39 @@ def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: return binary_fill_holes(nucleiMask) -def get_cv2_watershed_markers(nucleiImages: np.ndarray, - membraneImages: np.ndarray) -> np.ndarray: - """Combine membrane and nuclei markers into a single multilabel mask +def get_cv2_watershed_markers(compartmentImages: np.ndarray, + membraneImages: np.ndarray, + membraneFlag: int) -> np.ndarray: + """Combine membrane and compartment markers into a single multilabel mask for CV2 watershed Args: - nucleiImages: a 3 dimensional numpy array containing the images + compartmentImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). + membraneFlag: 0 if compartment and membrane images are the same, 1 + otherwise Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) of cv2-compatible watershed markers """ - nucleiMask = get_nuclei_mask(nucleiImages) + compartmentMask = get_nuclei_mask(compartmentImages) membraneMask = get_membrane_mask(membraneImages) - watershedMarker = np.zeros(nucleiMask.shape) + watershedMarker = np.zeros(compartmentMask.shape) - for z in range(nucleiImages.shape[0]): + for z in range(compartmentImages.shape[0]): # generate areas of sure bg and fg, as well as the area of # unknown classification - background = morphology.dilation(nucleiMask[z, :, :], + background = morphology.dilation(compartmentMask[z, :, :], morphology.selem.disk(15)) membraneDilated = morphology.dilation( membraneMask[z, :, :].astype('bool'), morphology.selem.disk(10)) - foreground = morphology.erosion(nucleiMask[z, :, :] * ~ + foreground = morphology.erosion(compartmentMask[z, :, :] * ~ membraneDilated, morphology.selem.disk(5)) unknown = background * ~ foreground @@ -322,12 +325,12 @@ def convert_grayscale_to_rgb(uint16Image: np.ndarray) -> np.ndarray: return rgbImage -def apply_cv2_watershed(nucleiImages: np.ndarray, +def apply_cv2_watershed(compartmentImages: np.ndarray, watershedMarkers: np.ndarray) -> np.ndarray: """Perform watershed using cv2 Args: - nucleiImages: a 3 dimensional numpy array containing the images + compartmentImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). watershedMarkers: a 3 dimensional numpy array containing the cv2 markers arranged as (z, x, y). @@ -339,7 +342,7 @@ def apply_cv2_watershed(nucleiImages: np.ndarray, watershedOutput = np.zeros(watershedMarkers.shape) for z in range(nucleiImages.shape[0]): - rgbImage = convert_grayscale_to_rgb(nucleiImages[z, :, :]) + rgbImage = convert_grayscale_to_rgb(compartmentImages[z, :, :]) watershedOutput[z, :, :] = cv2.watershed(rgbImage, watershedMarkers[z, :, :]. astype('int32')) @@ -348,7 +351,7 @@ def apply_cv2_watershed(nucleiImages: np.ndarray, return watershedOutput -def get_overlapping_nuclei(watershedZ0: np.ndarray, +def get_overlapping_objects(watershedZ0: np.ndarray, watershedZ1: np.ndarray, n0: int): """Perform watershed using cv2 @@ -357,26 +360,27 @@ def get_overlapping_nuclei(watershedZ0: np.ndarray, segmentation mask watershedZ1: a 2 dimensional numpy array containing a segmentation mask adjacent to watershedZ1 - n0: an integer with the index of the cell/nuclei to be compared - between the provided watershed segmentation masks + n0: an integer with the index of the object (cell/nuclei) + to be compared between the provided watershed + segmentation masks Returns: a tuple (n1, f0, f1) containing the label of the cell in Z1 overlapping n0 (n1), the fraction of n0 overlaping n1 (f0) and the fraction of n1 overlapping n0 (f1) """ - z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) - z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes > 100] + z1Indexes = np.unique(watershedZ1[watershedZ0 == n0]) + z1Indexes = z1Indexes[z1NucleiIndexes > 100] - if z1NucleiIndexes.shape[0] > 0: + if z1Indexes.shape[0] > 0: # calculate overlap fraction n0Area = np.count_nonzero(watershedZ0 == n0) - n1Area = np.zeros(len(z1NucleiIndexes)) - overlapArea = np.zeros(len(z1NucleiIndexes)) + n1Area = np.zeros(len(z1Indexes)) + overlapArea = np.zeros(len(z1Indexes)) - for ii in range(len(z1NucleiIndexes)): - n1 = z1NucleiIndexes[ii] + for ii in range(len(z1Indexes)): + n1 = z1Indexes[ii] n1Area[ii] = np.count_nonzero(watershedZ1 == n1) overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * (watershedZ1 == n1)) @@ -393,7 +397,7 @@ def get_overlapping_nuclei(watershedZ0: np.ndarray, if (n0OverlapFraction[indexSorted[0]] > 0.2 and n1OverlapFraction[indexSorted[0]] > 0.5): - return z1NucleiIndexes[indexSorted[0]], + return z1Indexes[indexSorted[0]], n0OverlapFraction[indexSorted[0]], n1OverlapFraction[indexSorted[0]] else: @@ -428,9 +432,9 @@ def combine_2d_segmentation_masks_into_3d(watershedOutput: np.unique(watershedOutput[z, :, :]) > 100] for n0 in zNucleiIndex: - n1, f0, f1 = get_overlapping_nuclei(watershedCombinedZ[z, :, :], - watershedOutput[z-1, :, :], - n0) + n1, f0, f1 = get_overlapping_objects(watershedCombinedZ[z, :, :], + watershedOutput[z-1, :, :], + n0) if n1: watershedCombinedZ[z-1, :, :][(watershedOutput[z-1, :, :] == n1)] = n0 From 560219be81a3fb3d25464e8129d123055af8bba0 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 22 Apr 2020 22:10:53 -0400 Subject: [PATCH 080/419] added framework for MachineLearningSegment class --- merlin/analysis/segment.py | 147 +++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 0b8de5ff..883df062 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -271,6 +271,153 @@ def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: return np.array([warpTask.get_aligned_image(fov, channelIndex, z) for z in range(len(self.dataSet.get_z_positions()))]) +class MachineLearningSegment(FeatureSavingAnalysisTask): + + """ + An analysis task that determines the boundaries of features in the + image data in each field of view using a watershed algorithm + implemented in CV2. + + A tutorial explaining the general scheme of the method can be + found in https://opencv-python-tutroals.readthedocs.io/en/latest/ + py_tutorials/py_imgproc/py_watershed/py_watershed.html. + + The watershed segmentation is performed in each z-position + independently and combined into 3D objects in a later step + + The class can be used to segment either nuclear or cytoplasmic + compartments. If both the compartment and membrane channels are the + same, the membrane channel is calculated from the edge transform of + the provided channel. + + Since each field of view is analyzed individually, the segmentation + results should be cleaned in order to merge cells that cross the + field of view boundary. + """ + + def __init__(self, dataSet, parameters=None, analysisName=None): + super().__init__(dataSet, parameters, analysisName) + + if 'membrane_channel_name' not in self.parameters: + self.parameters['membrane_channel_name'] = 'DAPI' + if 'compartment_channel_name' not in self.parameters: + self.parameters['compartment_channel_name'] = 'DAPI' + + def fragment_count(self): + return len(self.dataSet.get_fovs()) + + def get_estimated_memory(self): + # TODO - refine estimate + return 2048 + + def get_estimated_time(self): + # TODO - refine estimate + return 5 + + def get_dependencies(self): + return [self.parameters['warp_task'], + self.parameters['global_align_task']] + + def get_cell_boundaries(self) -> List[spatialfeature.SpatialFeature]: + featureDB = self.get_feature_database() + return featureDB.read_features() + + def _run_analysis(self, fragmentIndex): + startTime = time.time() + + print('Entered the _run_analysis method, FOV ' + str(fragmentIndex)) + + globalTask = self.dataSet.load_analysis_task( + self.parameters['global_align_task']) + + print(' globalTask loaded') + + # read membrane and compartment indexes + membraneIndex = self.dataSet \ + .get_data_organization() \ + .get_data_channel_index( + self.parameters['membrane_channel_name']) + compartmentIndex = self.dataSet \ + .get_data_organization() \ + .get_data_channel_index( + self.parameters['compartment_channel_name']) + + if self.parameters['membrane_channel_name'] == + self.parameters['compartment_channel_name']: + membraneFlag = 0 + else: + membraneFlag = 1 + + endTime = time.time() + print(" image indexes read, ET {:.2f} min".format( + (endTime - startTime) / 60)) + + # read membrane and compartment images + membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) + compartmentImages = self._read_image_stack(fragmentIndex, + compartmentIndex) + + endTime = time.time() + print(" images read, ET {:.2f} min".format( + (endTime - startTime) / 60)) + print(" membraneImages Type: " + str(type(membraneImages))) + print(" membraneImages Size: [" + + str(membraneImages.shape[0]) + + "," + str(membraneImages.shape[1]) + + "," + str(membraneImages.shape[2]) + "]") + print(" compartmentImages Type: " + str(type(compartmentImages))) + print(" compartmentImages Size: [" + + str(compartmentImages.shape[0]) + + "," + str(compartmentImages.shape[1]) + + "," + str(compartmentImages.shape[2]) + "]") + + # Prepare masks for cv2 watershed + watershedMarkers = watershed.get_cv2_watershed_markers( + compartmentImages, + membraneImages, + membraneFlag) + + endTime = time.time() + print(" markers calculated, ET {:.2f} min".format( + (endTime - startTime) / 60)) + + # perform watershed in individual z positions + watershedOutput = watershed.apply_cv2_watershed(compartmentImages, + watershedMarkers) + + endTime = time.time() + print(" watershed calculated, ET {:.2f} min".format( + (endTime - startTime) / 60)) + + # combine all z positions in watershed + watershedCombinedOutput = watershed \ + .combine_2d_segmentation_masks_into_3d(watershedOutput) + + endTime = time.time() + print(" watershed z positions combined, ET {:.2f} min".format( + (endTime - startTime) / 60)) + + # get features from mask. This is the slowestart (6 min for the + # previous part, 15+ for the rest, for a 7 frame Image. + zPos = np.array(self.dataSet.get_data_organization().get_z_positions()) + featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( + (watershedCombinedOutput == i), fragmentIndex, + globalTask.fov_to_global_transform(fragmentIndex), zPos) + for i in np.unique(watershedOutput) if i != 0] + + featureDB = self.get_feature_database() + featureDB.write_features(featureList, fragmentIndex) + + endTime = time.time() + print(" features written, ET {:.2f} min".format( + (endTime - startTime) / 60)) + + def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: + warpTask = self.dataSet.load_analysis_task( + self.parameters['warp_task']) + return np.array([warpTask.get_aligned_image(fov, channelIndex, z) + for z in range(len(self.dataSet.get_z_positions()))]) + class CleanCellBoundaries(analysistask.ParallelAnalysisTask): ''' From ce8be7a14c7402d3094d73e715967e3f1cb0bdab Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Apr 2020 12:02:57 -0400 Subject: [PATCH 081/419] adding mls utils function --- merlin/analysis/segment.py | 43 ++++++++++++++-------- merlin/util/machinelearningsegmentation.py | 11 ++++++ 2 files changed, 38 insertions(+), 16 deletions(-) create mode 100755 merlin/util/machinelearningsegmentation.py diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 883df062..ab2120c4 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -275,8 +275,15 @@ class MachineLearningSegment(FeatureSavingAnalysisTask): """ An analysis task that determines the boundaries of features in the - image data in each field of view using a watershed algorithm - implemented in CV2. + image data in each field of view using a the specified machine learning + method. The available methods are: + + unet: + + ilastik: + + cellpose: + A tutorial explaining the general scheme of the method can be found in https://opencv-python-tutroals.readthedocs.io/en/latest/ @@ -298,8 +305,8 @@ class MachineLearningSegment(FeatureSavingAnalysisTask): def __init__(self, dataSet, parameters=None, analysisName=None): super().__init__(dataSet, parameters, analysisName) - if 'membrane_channel_name' not in self.parameters: - self.parameters['membrane_channel_name'] = 'DAPI' + if 'method' not in self.parameters: + self.parameters['method'] = 'ilastik' if 'compartment_channel_name' not in self.parameters: self.parameters['compartment_channel_name'] = 'DAPI' @@ -326,6 +333,7 @@ def _run_analysis(self, fragmentIndex): startTime = time.time() print('Entered the _run_analysis method, FOV ' + str(fragmentIndex)) + print('Using ' + self.parameters['method'] + ' method.') globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) @@ -333,30 +341,32 @@ def _run_analysis(self, fragmentIndex): print(' globalTask loaded') # read membrane and compartment indexes - membraneIndex = self.dataSet \ - .get_data_organization() \ - .get_data_channel_index( - self.parameters['membrane_channel_name']) compartmentIndex = self.dataSet \ .get_data_organization() \ .get_data_channel_index( self.parameters['compartment_channel_name']) - if self.parameters['membrane_channel_name'] == - self.parameters['compartment_channel_name']: - membraneFlag = 0 - else: - membraneFlag = 1 - endTime = time.time() print(" image indexes read, ET {:.2f} min".format( (endTime - startTime) / 60)) - # read membrane and compartment images - membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) + # Read images and perform segmentation compartmentImages = self._read_image_stack(fragmentIndex, compartmentIndex) + endTime = time.time() + print(" images read, ET {:.2f} min".format( + (endTime - startTime) / 60)) + + segmentationOutput = machinelearningsegmentation. + apply_machine_learning_segmentation( + compartmentImages, + self.parameters['method']) + + endTime = time.time() + print(" Segmentation finished, ET {:.2f} min".format( + (endTime - startTime) / 60)) +""" endTime = time.time() print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) @@ -396,6 +406,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" watershed z positions combined, ET {:.2f} min".format( (endTime - startTime) / 60)) +""" # get features from mask. This is the slowestart (6 min for the # previous part, 15+ for the rest, for a 7 frame Image. diff --git a/merlin/util/machinelearningsegmentation.py b/merlin/util/machinelearningsegmentation.py new file mode 100755 index 00000000..f882a06e --- /dev/null +++ b/merlin/util/machinelearningsegmentation.py @@ -0,0 +1,11 @@ +import numpy as np +import cv2 +from scipy import ndimage +from scipy.ndimage.morphology import binary_fill_holes +from skimage import morphology +from skimage import filters +from skimage import measure +from pyclustering.cluster import kmedoids +from typing import Tuple + +from merlin.util import matlab \ No newline at end of file From 535f81388b30a4d0ecaf7e8d970a3bc3c2597073 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Apr 2020 12:19:53 -0400 Subject: [PATCH 082/419] add function definitions to mls.py --- merlin/util/machinelearningsegmentation.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/merlin/util/machinelearningsegmentation.py b/merlin/util/machinelearningsegmentation.py index f882a06e..fbc05f0e 100755 --- a/merlin/util/machinelearningsegmentation.py +++ b/merlin/util/machinelearningsegmentation.py @@ -8,4 +8,19 @@ from pyclustering.cluster import kmedoids from typing import Tuple -from merlin.util import matlab \ No newline at end of file +from merlin.util import matlab + +""" +This module contains utility functions for preparing imagmes for performing +segmentation using machine learning approaches +""" + +def apply_machine_learning_segmentation(imageStackIn: np.ndarray, + method: str) -> np.ndarray: + +def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: + +def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: + +def segment_using_cellpose(imageStackIn: np.ndarray) -> np.ndarray: + From ec91b780c32659b2659c88e497ef7168b1de2dc7 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Apr 2020 22:16:23 -0400 Subject: [PATCH 083/419] add body of apply_machine_learning_segmentation function --- merlin/analysis/segment.py | 16 +------------- merlin/util/machinelearningsegmentation.py | 25 +++++++++++++++++++--- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index ab2120c4..ea250c24 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -284,22 +284,8 @@ class MachineLearningSegment(FeatureSavingAnalysisTask): cellpose: + TODO: ADD FLAT FIELD CORRECTION TASK - A tutorial explaining the general scheme of the method can be - found in https://opencv-python-tutroals.readthedocs.io/en/latest/ - py_tutorials/py_imgproc/py_watershed/py_watershed.html. - - The watershed segmentation is performed in each z-position - independently and combined into 3D objects in a later step - - The class can be used to segment either nuclear or cytoplasmic - compartments. If both the compartment and membrane channels are the - same, the membrane channel is calculated from the edge transform of - the provided channel. - - Since each field of view is analyzed individually, the segmentation - results should be cleaned in order to merge cells that cross the - field of view boundary. """ def __init__(self, dataSet, parameters=None, analysisName=None): diff --git a/merlin/util/machinelearningsegmentation.py b/merlin/util/machinelearningsegmentation.py index fbc05f0e..9a0076c4 100755 --- a/merlin/util/machinelearningsegmentation.py +++ b/merlin/util/machinelearningsegmentation.py @@ -13,14 +13,33 @@ """ This module contains utility functions for preparing imagmes for performing segmentation using machine learning approaches +MAYBE COMBINE WITH WATERSHED.PY INTO A SINGLE FILE, SEGMENTATION.PY """ +def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: + +def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: + +def segment_using_cellpose(imageStackIn: np.ndarray) -> np.ndarray: + def apply_machine_learning_segmentation(imageStackIn: np.ndarray, method: str) -> np.ndarray: + """Calculate binary mask with 1's in membrane pixels and 0 otherwise. + The images expected are some type of Nuclei label (e.g. DAPI) -def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: + Args: + membraneImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) + """ + if method == 'ilastik': + segmentOutput = segment_using_ilastik(imageStackIn) + elif method == 'cellpose': + segmentOutput = segment_using_cellpose(imageStackIn) + elif method == 'unet' + segmentOutput = segment_using_unet(imageStackIn) -def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: + return segmentOutput -def segment_using_cellpose(imageStackIn: np.ndarray) -> np.ndarray: From d0f0a1866efd7c00bc5936e4420b909f5523cec7 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Apr 2020 16:48:14 -0400 Subject: [PATCH 084/419] change nuclei to compartment in cv2 segmentation functions --- merlin/util/machinelearningsegmentation.py | 5 +- merlin/util/watershed.py | 55 +++++++++++----------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/merlin/util/machinelearningsegmentation.py b/merlin/util/machinelearningsegmentation.py index 9a0076c4..e11ab397 100755 --- a/merlin/util/machinelearningsegmentation.py +++ b/merlin/util/machinelearningsegmentation.py @@ -24,11 +24,10 @@ def segment_using_cellpose(imageStackIn: np.ndarray) -> np.ndarray: def apply_machine_learning_segmentation(imageStackIn: np.ndarray, method: str) -> np.ndarray: - """Calculate binary mask with 1's in membrane pixels and 0 otherwise. - The images expected are some type of Nuclei label (e.g. DAPI) + """Calculate Args: - membraneImages: a 3 dimensional numpy array containing the images + imageStackIn: a 3 dimensional numpy array containing the images arranged as (z, x, y). Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 16bf72bd..6e57d1e5 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -172,30 +172,31 @@ def get_membrane_mask(membraneImages: np.ndarray) -> np.ndarray: return mask -def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: - """Calculate binary mask with 1's in membrane pixels and 0 otherwise. - The images expected are some type of Nuclei label (e.g. DAPI) +def get_compartment_mask(compartmentImages: np.ndarray) -> np.ndarray: + """Calculate binary mask with 1's in compartment (nuclei or cytoplasm) + pixels and 0 otherwise. The images expected are some type of compartment + label (e.g. Nuclei: DAPI, Cytoplasm: PolyT, CD45, etc) Args: - membraneImages: a 3 dimensional numpy array containing the images + compartmentImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) """ - # generate nuclei mask based on thresholding - thresholdingMask = np.zeros(nucleiImages.shape) + # generate compartment mask based on thresholding + thresholdingMask = np.zeros(compartmentImages.shape) coarseBlockSize = 241 fineBlockSize = 61 - for z in range(nucleiImages.shape[0]): - coarseThresholdingMask = (nucleiImages[z, :, :] > + for z in range(compartmentImages.shape[0]): + coarseThresholdingMask = (compartmentImages[z, :, :] > filters.threshold_local( - nucleiImages[z, :, :], + compartmentImages[z, :, :], coarseBlockSize, offset=0)) - fineThresholdingMask = (nucleiImages[z, :, :] > + fineThresholdingMask = (compartmentImages[z, :, :] > filters.threshold_local( - nucleiImages[z, :, :], + compartmentImages[z, :, :], fineBlockSize, offset=0)) thresholdingMask[z, :, :] = (coarseThresholdingMask * @@ -205,15 +206,15 @@ def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: # generate border mask, necessary to avoid making a single # connected component when using binary_fill_holes below - borderMask = np.zeros((2048, 2048)) - borderMask[25:2023, 25:2023] = 1 + borderMask = np.zeros((compartmentImages.shape[1], + compartmentImages.shape[2])) + borderMask[25:(compartmentImages.shape[1]-25), + 25:(compartmentImages.shape[2]-25)] = 1 - # TODO - use the image size variable for borderMask - - # generate nuclei mask from hessian, fine - fineHessianMask = np.zeros(nucleiImages.shape) - for z in range(nucleiImages.shape[0]): - fineHessian = filters.hessian(nucleiImages[z, :, :]) + # generate compartment mask from hessian, fine + fineHessianMask = np.zeros(compartmentImages.shape) + for z in range(compartmentImages.shape[0]): + fineHessian = filters.hessian(compartmentImages[z, :, :]) fineHessianMask[z, :, :] = fineHessian == fineHessian.max() fineHessianMask[z, :, :] = morphology.binary_closing( fineHessianMask[z, :, :], @@ -222,12 +223,12 @@ def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: fineHessianMask[z, :, :] = binary_fill_holes( fineHessianMask[z, :, :]) - # generate dapi mask from hessian, coarse - coarseHessianMask = np.zeros(nucleiImages.shape) - for z in range(nucleiImages.shape[0]): - coarseHessian = filters.hessian(nucleiImages[z, :, :] - + # generate compartment mask from hessian, coarse + coarseHessianMask = np.zeros(comapartImages.shape) + for z in range(compartmentImages.shape[0]): + coarseHessian = filters.hessian(compartmentImages[z, :, :] - morphology.white_tophat( - nucleiImages[z, :, :], + compartmentImages[z, :, :], morphology.selem.disk(20))) coarseHessianMask[z, :, :] = coarseHessian == coarseHessian.max() coarseHessianMask[z, :, :] = morphology.binary_closing( @@ -238,8 +239,8 @@ def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: coarseHessianMask[z, :, :]) # combine masks - nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask - return binary_fill_holes(nucleiMask) + compartmentMask = thresholdingMask + fineHessianMask + coarseHessianMask + return binary_fill_holes(compartmentMask) def get_cv2_watershed_markers(compartmentImages: np.ndarray, @@ -260,7 +261,7 @@ def get_cv2_watershed_markers(compartmentImages: np.ndarray, cv2-compatible watershed markers """ - compartmentMask = get_nuclei_mask(compartmentImages) + compartmentMask = get_compartment_mask(compartmentImages) membraneMask = get_membrane_mask(membraneImages) watershedMarker = np.zeros(compartmentMask.shape) From 27908380c3210181ee902fc8142ef14609c86b02 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Apr 2020 17:34:33 -0400 Subject: [PATCH 085/419] adding edge memebrane calculation to CV2 segmenetation --- merlin/analysis/segment.py | 8 +-- merlin/util/machinelearningsegmentation.py | 3 +- merlin/util/watershed.py | 78 ++++++++++++++++------ 3 files changed, 59 insertions(+), 30 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index ea250c24..5a5bb1aa 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -195,12 +195,6 @@ def _run_analysis(self, fragmentIndex): .get_data_channel_index( self.parameters['compartment_channel_name']) - if self.parameters['membrane_channel_name'] == - self.parameters['compartment_channel_name']: - membraneFlag = 0 - else: - membraneFlag = 1 - endTime = time.time() print(" image indexes read, ET {:.2f} min".format( (endTime - startTime) / 60)) @@ -228,7 +222,7 @@ def _run_analysis(self, fragmentIndex): watershedMarkers = watershed.get_cv2_watershed_markers( compartmentImages, membraneImages, - membraneFlag) + self.parameters['membrane_channel_name']) endTime = time.time() print(" markers calculated, ET {:.2f} min".format( diff --git a/merlin/util/machinelearningsegmentation.py b/merlin/util/machinelearningsegmentation.py index e11ab397..085fa4df 100755 --- a/merlin/util/machinelearningsegmentation.py +++ b/merlin/util/machinelearningsegmentation.py @@ -24,8 +24,7 @@ def segment_using_cellpose(imageStackIn: np.ndarray) -> np.ndarray: def apply_machine_learning_segmentation(imageStackIn: np.ndarray, method: str) -> np.ndarray: - """Calculate - + """Select segmentation algorithm to use Args: imageStackIn: a 3 dimensional numpy array containing the images arranged as (z, x, y). diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 6e57d1e5..bdfd2d1b 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -5,6 +5,7 @@ from skimage import morphology from skimage import filters from skimage import measure +from skimage import feature from pyclustering.cluster import kmedoids from typing import Tuple @@ -142,33 +143,68 @@ def prepare_watershed_images(watershedImageStack: np.ndarray return normalizedWatershed, watershedMask -def get_membrane_mask(membraneImages: np.ndarray) -> np.ndarray: +def get_membrane_mask(membraneImages: np.ndarray, + membraneChannelName: str, + compartmentChannelName: str) -> np.ndarray: """Calculate binary mask with 1's in membrane pixels and 0 otherwise. The images expected are some type of membrane label (WGA, ConA, - Lamin, Cadherins) + Lamin, Cadherins) or compartment images (DAPI, CD45, polyT) Args: membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). + membraneChannelName: A string with the name of a membrane channel. + compartmentChannelName: A string with the name of the compartment + channel Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) """ mask = np.zeros(membraneImages.shape) - fineBlockSize = 61 - for z in range(membraneImages.shape[0]): - mask[z, :, :] = (membraneImages[z, :, :] > - filters.threshold_local(membraneImages[z, :, :], - fineBlockSize, - offset=0)) - mask[z, :, :] = morphology.remove_small_objects( - mask[z, :, :].astype('bool'), - min_size=100, - connectivity=1) - mask[z, :, :] = morphology.binary_closing(mask[z, :, :], - morphology.selem.disk(5)) - mask[z, :, :] = morphology.skeletonize(mask[z, :, :]) - - # combine masks + if membraneChannelName != compartmentChannelName: + fineBlockSize = 61 + for z in range(membraneImages.shape[0]): + mask[z, :, :] = (membraneImages[z, :, :] > + filters.threshold_local(membraneImages[z, :, :], + fineBlockSize, + offset=0)) + mask[z, :, :] = morphology.remove_small_objects( + mask[z, :, :].astype('bool'), + min_size=100, + connectivity=1) + mask[z, :, :] = morphology.binary_closing(mask[z, :, :], + morphology.selem.disk(5)) + mask[z, :, :] = morphology.skeletonize(mask[z, :, :]) + else: + filterSigma2 = 5 + filterSize2 = int(2*np.ceil(2*filterSigma2)+1) + edgeSigma = 2 #1 #2 + lowThresh = 0.1 #0.5 #0.2 + hiThresh = 0.5 #0.7 #0.6 + for z in range(membraneImages.shape[0]): + blurredImage = cv2.GaussianBlur(img[:,:,1], + (filterSize2,filterSize2), + filterSigma2) + edge0 = feature.canny(membraneImages[z, :, :], + sigma=edgeSigma, + use_quantiles=True, + low_threshold=lowThresh, + high_threshold=hiThresh) + edge0 = morphology.remove_small_objects( + mask[z, :, :].astype('bool'), + min_size=20, + connectivity=1) + edge1 = feature.canny(blurredImage, + sigma=edgeSigma, + use_quantiles=True, + low_threshold=lowThresh, + high_threshold=hiThresh) + edge1 = morphology.remove_small_objects( + mask[z, :, :].astype('bool'), + min_size=20, + connectivity=1) + + mask[z, :, :] = edge0 + edge1 + return mask @@ -262,7 +298,7 @@ def get_cv2_watershed_markers(compartmentImages: np.ndarray, """ compartmentMask = get_compartment_mask(compartmentImages) - membraneMask = get_membrane_mask(membraneImages) + membraneMask = get_membrane_mask(membraneImages, membraneFlag) watershedMarker = np.zeros(compartmentMask.shape) @@ -371,7 +407,7 @@ def get_overlapping_objects(watershedZ0: np.ndarray, """ z1Indexes = np.unique(watershedZ1[watershedZ0 == n0]) - z1Indexes = z1Indexes[z1NucleiIndexes > 100] + z1Indexes = z1Indexes[z1Indexes > 100] if z1Indexes.shape[0] > 0: @@ -429,10 +465,10 @@ def combine_2d_segmentation_masks_into_3d(watershedOutput: # starting far from coverslip for z in range(watershedOutput.shape[0]-1, 0, -1): - zNucleiIndex = np.unique(watershedOutput[z, :, :])[ + zIndex = np.unique(watershedOutput[z, :, :])[ np.unique(watershedOutput[z, :, :]) > 100] - for n0 in zNucleiIndex: + for n0 in zIndex: n1, f0, f1 = get_overlapping_objects(watershedCombinedZ[z, :, :], watershedOutput[z-1, :, :], n0) From 1d35fb8ba7a0aac5201c92e56f1c52a899f75aa2 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sun, 26 Apr 2020 10:55:13 -0400 Subject: [PATCH 086/419] modify get_membrane_mask function --- merlin/util/watershed.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index bdfd2d1b..53b0c7cf 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -181,30 +181,27 @@ def get_membrane_mask(membraneImages: np.ndarray, lowThresh = 0.1 #0.5 #0.2 hiThresh = 0.5 #0.7 #0.6 for z in range(membraneImages.shape[0]): - blurredImage = cv2.GaussianBlur(img[:,:,1], + blurredImage = cv2.GaussianBlur(membraneImages[z, :, :], (filterSize2,filterSize2), filterSigma2) - edge0 = feature.canny(membraneImages[z, :, :], + edge0 = feature.canny(membraneImages[z, :, :], sigma=edgeSigma, use_quantiles=True, low_threshold=lowThresh, high_threshold=hiThresh) - edge0 = morphology.remove_small_objects( - mask[z, :, :].astype('bool'), - min_size=20, - connectivity=1) + edge0 = morphology.dilation(edge0,morphology.selem.disk(15)) + edge1 = feature.canny(blurredImage, sigma=edgeSigma, use_quantiles=True, low_threshold=lowThresh, high_threshold=hiThresh) - edge1 = morphology.remove_small_objects( - mask[z, :, :].astype('bool'), - min_size=20, - connectivity=1) + edge1 = morphology.dilation(edge1,morphology.selem.disk(15)) mask[z, :, :] = edge0 + edge1 - + + mask[z, :, :] = morphology.skeletonize(mask[z, :, :]) + return mask @@ -260,7 +257,7 @@ def get_compartment_mask(compartmentImages: np.ndarray) -> np.ndarray: fineHessianMask[z, :, :]) # generate compartment mask from hessian, coarse - coarseHessianMask = np.zeros(comapartImages.shape) + coarseHessianMask = np.zeros(compartmentImages.shape) for z in range(compartmentImages.shape[0]): coarseHessian = filters.hessian(compartmentImages[z, :, :] - morphology.white_tophat( From 403ec44d88e4e4a37069c4fe751770e72140a4c4 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sun, 26 Apr 2020 17:41:25 -0400 Subject: [PATCH 087/419] change watershed.py to segmentation.py, put contents of mls.py into segmentation.py --- merlin/analysis/segment.py | 33 +- merlin/util/machinelearningsegmentation.py | 43 -- merlin/util/segmentation.py | 518 +++++++++++++++++++++ merlin/util/watershed.py | 4 +- 4 files changed, 538 insertions(+), 60 deletions(-) delete mode 100755 merlin/util/machinelearningsegmentation.py create mode 100755 merlin/util/segmentation.py diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 5a5bb1aa..366cd7ae 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -1,7 +1,7 @@ import cv2 import numpy as np from skimage import measure -from skimage import segmentation +from skimage import segmentation as skiseg from skimage import morphology from skimage import feature from skimage import filters @@ -13,7 +13,7 @@ from merlin.core import dataset from merlin.core import analysistask from merlin.util import spatialfeature -from merlin.util import watershed +from merlin.util import segmentation import pandas import networkx as nx import time @@ -94,13 +94,13 @@ def _run_analysis(self, fragmentIndex): .get_data_channel_index(self.parameters['watershed_channel_name']) watershedImages = self._read_and_filter_image_stack(fragmentIndex, watershedIndex, 5) - seeds = watershed.separate_merged_seeds( - watershed.extract_seeds(seedImages)) - normalizedWatershed, watershedMask = watershed.prepare_watershed_images( + seeds = segmentation.separate_merged_seeds( + segmentation.extract_seeds(seedImages)) + normalizedWatershed, watershedMask = segmentation.prepare_watershed_images( watershedImages) seeds[np.invert(watershedMask)] = 0 - watershedOutput = segmentation.watershed( + watershedOutput = skiseg.watershed( normalizedWatershed, measure.label(seeds), mask=watershedMask, connectivity=np.ones((3, 3, 3)), watershed_line=True) @@ -219,7 +219,7 @@ def _run_analysis(self, fragmentIndex): + "," + str(compartmentImages.shape[2]) + "]") # Prepare masks for cv2 watershed - watershedMarkers = watershed.get_cv2_watershed_markers( + watershedMarkers = segmentation.get_cv2_watershed_markers( compartmentImages, membraneImages, self.parameters['membrane_channel_name']) @@ -229,15 +229,15 @@ def _run_analysis(self, fragmentIndex): (endTime - startTime) / 60)) # perform watershed in individual z positions - watershedOutput = watershed.apply_cv2_watershed(compartmentImages, - watershedMarkers) + watershedOutput = segmentation.apply_cv2_watershed(compartmentImages, + watershedMarkers) endTime = time.time() print(" watershed calculated, ET {:.2f} min".format( (endTime - startTime) / 60)) # combine all z positions in watershed - watershedCombinedOutput = watershed \ + watershedCombinedOutput = segmentation \ .combine_2d_segmentation_masks_into_3d(watershedOutput) endTime = time.time() @@ -286,9 +286,12 @@ def __init__(self, dataSet, parameters=None, analysisName=None): super().__init__(dataSet, parameters, analysisName) if 'method' not in self.parameters: - self.parameters['method'] = 'ilastik' + self.parameters['method'] = 'cellpose' if 'compartment_channel_name' not in self.parameters: self.parameters['compartment_channel_name'] = 'DAPI' + if 'compartment_channel_type' not in self.parameters: + self.parameters['compartment_channel_type'] = 'cytoplasm' # nuclei + def fragment_count(self): return len(self.dataSet.get_fovs()) @@ -338,10 +341,10 @@ def _run_analysis(self, fragmentIndex): print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) - segmentationOutput = machinelearningsegmentation. - apply_machine_learning_segmentation( - compartmentImages, - self.parameters['method']) + segmentationOutput = segmentation.apply_machine_learning_segmentation( + compartmentImages, + self.parameters['method'], + self.parameters['compartment_channel_name']) endTime = time.time() print(" Segmentation finished, ET {:.2f} min".format( diff --git a/merlin/util/machinelearningsegmentation.py b/merlin/util/machinelearningsegmentation.py deleted file mode 100755 index 085fa4df..00000000 --- a/merlin/util/machinelearningsegmentation.py +++ /dev/null @@ -1,43 +0,0 @@ -import numpy as np -import cv2 -from scipy import ndimage -from scipy.ndimage.morphology import binary_fill_holes -from skimage import morphology -from skimage import filters -from skimage import measure -from pyclustering.cluster import kmedoids -from typing import Tuple - -from merlin.util import matlab - -""" -This module contains utility functions for preparing imagmes for performing -segmentation using machine learning approaches -MAYBE COMBINE WITH WATERSHED.PY INTO A SINGLE FILE, SEGMENTATION.PY -""" -def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: - -def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: - -def segment_using_cellpose(imageStackIn: np.ndarray) -> np.ndarray: - - -def apply_machine_learning_segmentation(imageStackIn: np.ndarray, - method: str) -> np.ndarray: - """Select segmentation algorithm to use - Args: - imageStackIn: a 3 dimensional numpy array containing the images - arranged as (z, x, y). - Returns: - ndarray containing a 3 dimensional mask arranged as (z, x, y) - """ - if method == 'ilastik': - segmentOutput = segment_using_ilastik(imageStackIn) - elif method == 'cellpose': - segmentOutput = segment_using_cellpose(imageStackIn) - elif method == 'unet' - segmentOutput = segment_using_unet(imageStackIn) - - return segmentOutput - - diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py new file mode 100755 index 00000000..bf8d5d1c --- /dev/null +++ b/merlin/util/segmentation.py @@ -0,0 +1,518 @@ +import numpy as np +import cv2 +from scipy import ndimage +from scipy.ndimage.morphology import binary_fill_holes +from skimage import morphology +from skimage import filters +from skimage import measure +from skimage import feature +from pyclustering.cluster import kmedoids +from typing import Tuple + +from merlin.util import matlab + +from cellpose import models + + +""" +This module contains utility functions for preparing images for +watershed segmentation, as well as functions to perform segmentation +using macnine learning approaches +""" + +# To match Matlab's strel('disk', 20) +diskStruct = morphology.diamond(28)[9:48, 9:48] + + +def extract_seeds(seedImageStackIn: np.ndarray) -> np.ndarray: + """Determine seed positions from the input images. + + The initial seeds are determined by finding the regional intensity maximums + after erosion and filtering with an adaptive threshold. These initial + seeds are then expanded by dilation. + + Args: + seedImageStackIn: a 3 dimensional numpy array arranged as (z,x,y) + Returns: a boolean numpy array with the same dimensions as seedImageStackIn + where a given (z,x,y) coordinate is True if it corresponds to a seed + position and false otherwise. + """ + seedImages = seedImageStackIn.copy() + + seedImages = ndimage.grey_erosion( + seedImages, + footprint=ndimage.morphology.generate_binary_structure(3, 1)) + seedImages = np.array([cv2.erode(x, diskStruct, + borderType=cv2.BORDER_REFLECT) + for x in seedImages]) + + thresholdFilterSize = int(2 * np.floor(seedImages.shape[1] / 16) + 1) + seedMask = np.array([x < 1.1 * filters.threshold_local( + x, thresholdFilterSize, method='mean', mode='nearest') + for x in seedImages]) + + seedImages[seedMask] = 0 + + seeds = morphology.local_maxima(seedImages, allow_borders=True) + + seeds = ndimage.morphology.binary_dilation( + seeds, structure=ndimage.morphology.generate_binary_structure(3, 1)) + seeds = np.array([ndimage.morphology.binary_dilation( + x, structure=morphology.diamond(28)[9:48, 9:48]) for x in seeds]) + + return seeds + + +def separate_merged_seeds(seedsIn: np.ndarray) -> np.ndarray: + """Separate seeds that are merged in 3 dimensions but are separated + in some 2 dimensional slices. + + Args: + seedsIn: a 3 dimensional binary numpy array arranged as (z,x,y) where + True indicates the pixel corresponds with a seed. + Returns: a 3 dimensional binary numpy array of the same size as seedsIn + indicating the positions of seeds after processing. + """ + + def create_region_image(shape, c): + region = np.zeros(shape) + for x in c.coords: + region[x[0], x[1], x[2]] = 1 + return region + + components = measure.regionprops(measure.label(seedsIn)) + seeds = np.zeros(seedsIn.shape) + for c in components: + seedImage = create_region_image(seeds.shape, c) + localProps = [measure.regionprops(measure.label(x)) for x in seedImage] + seedCounts = [len(x) for x in localProps] + + if all([x < 2 for x in seedCounts]): + goodFrames = [i for i, x in enumerate(seedCounts) if x == 1] + goodProperties = [y for x in goodFrames for y in localProps[x]] + seedPositions = np.round([np.median( + [x.centroid for x in goodProperties], axis=0)]).astype(int) + else: + goodFrames = [i for i, x in enumerate(seedCounts) if x > 1] + goodProperties = [y for x in goodFrames for y in localProps[x]] + goodCentroids = [x.centroid for x in goodProperties] + km = kmedoids.kmedoids( + goodCentroids, + np.random.choice(np.arange(len(goodCentroids)), + size=np.max(seedCounts))) + km.process() + seedPositions = np.round( + [goodCentroids[x] for x in km.get_medoids()]).astype(int) + + for s in seedPositions: + for f in goodFrames: + seeds[f, s[0], s[1]] = 1 + + seeds = ndimage.morphology.binary_dilation( + seeds, structure=ndimage.morphology.generate_binary_structure(3, 1)) + seeds = np.array([ndimage.morphology.binary_dilation( + x, structure=diskStruct) for x in seeds]) + + return seeds + + +def prepare_watershed_images(watershedImageStack: np.ndarray + ) -> Tuple[np.ndarray, np.ndarray]: + """Prepare the given images as the input image for watershedding. + + A watershed mask is determined using an adaptive threshold and the watershed + images are inverted so the largest values in the watershed images become + minima and then the image stack is normalized to have values between 0 + and 1. + + Args: + watershedImageStack: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + Returns: a tuple containing the normalized watershed images and the + calculated watershed mask + """ + filterSize = int(2 * np.floor(watershedImageStack.shape[1] / 16) + 1) + + watershedMask = np.array([ndimage.morphology.binary_fill_holes( + x > 1.1 * filters.threshold_local(x, filterSize, method='mean', + mode='nearest')) + for x in watershedImageStack]) + + normalizedWatershed = 1 - (watershedImageStack + - np.min(watershedImageStack)) / \ + (np.max(watershedImageStack) + - np.min(watershedImageStack)) + normalizedWatershed[np.invert(watershedMask)] = 1 + + return normalizedWatershed, watershedMask + + +def get_membrane_mask(membraneImages: np.ndarray, + membraneChannelName: str, + compartmentChannelName: str) -> np.ndarray: + """Calculate binary mask with 1's in membrane pixels and 0 otherwise. + The images expected are some type of membrane label (WGA, ConA, + Lamin, Cadherins) or compartment images (DAPI, CD45, polyT) + + Args: + membraneImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + membraneChannelName: A string with the name of a membrane channel. + compartmentChannelName: A string with the name of the compartment + channel + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) + """ + mask = np.zeros(membraneImages.shape) + if membraneChannelName != compartmentChannelName: + fineBlockSize = 61 + for z in range(membraneImages.shape[0]): + mask[z, :, :] = (membraneImages[z, :, :] > + filters.threshold_local(membraneImages[z, :, :], + fineBlockSize, + offset=0)) + mask[z, :, :] = morphology.remove_small_objects( + mask[z, :, :].astype('bool'), + min_size=100, + connectivity=1) + mask[z, :, :] = morphology.binary_closing(mask[z, :, :], + morphology.selem.disk(5)) + mask[z, :, :] = morphology.skeletonize(mask[z, :, :]) + else: + filterSigma2 = 5 + filterSize2 = int(2*np.ceil(2*filterSigma2)+1) + edgeSigma = 2 #1 #2 + lowThresh = 0.1 #0.5 #0.2 + hiThresh = 0.5 #0.7 #0.6 + for z in range(membraneImages.shape[0]): + blurredImage = cv2.GaussianBlur(membraneImages[z, :, :], + (filterSize2,filterSize2), + filterSigma2) + edge0 = feature.canny(membraneImages[z, :, :], + sigma=edgeSigma, + use_quantiles=True, + low_threshold=lowThresh, + high_threshold=hiThresh) + edge0 = morphology.dilation(edge0,morphology.selem.disk(10)) + + edge1 = feature.canny(blurredImage, + sigma=edgeSigma, + use_quantiles=True, + low_threshold=lowThresh, + high_threshold=hiThresh) + edge1 = morphology.dilation(edge1,morphology.selem.disk(10)) + + mask[z, :, :] = edge0 + edge1 + + mask[z, :, :] = morphology.skeletonize(mask[z, :, :]) + + return mask + + +def get_compartment_mask(compartmentImages: np.ndarray) -> np.ndarray: + """Calculate binary mask with 1's in compartment (nuclei or cytoplasm) + pixels and 0 otherwise. The images expected are some type of compartment + label (e.g. Nuclei: DAPI, Cytoplasm: PolyT, CD45, etc) + + Args: + compartmentImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) + """ + + # generate compartment mask based on thresholding + thresholdingMask = np.zeros(compartmentImages.shape) + coarseBlockSize = 241 + fineBlockSize = 61 + for z in range(compartmentImages.shape[0]): + coarseThresholdingMask = (compartmentImages[z, :, :] > + filters.threshold_local( + compartmentImages[z, :, :], + coarseBlockSize, + offset=0)) + fineThresholdingMask = (compartmentImages[z, :, :] > + filters.threshold_local( + compartmentImages[z, :, :], + fineBlockSize, + offset=0)) + thresholdingMask[z, :, :] = (coarseThresholdingMask * + fineThresholdingMask) + thresholdingMask[z, :, :] = binary_fill_holes( + thresholdingMask[z, :, :]) + + # generate border mask, necessary to avoid making a single + # connected component when using binary_fill_holes below + borderMask = np.zeros((compartmentImages.shape[1], + compartmentImages.shape[2])) + borderMask[25:(compartmentImages.shape[1]-25), + 25:(compartmentImages.shape[2]-25)] = 1 + + # generate compartment mask from hessian, fine + fineHessianMask = np.zeros(compartmentImages.shape) + for z in range(compartmentImages.shape[0]): + fineHessian = filters.hessian(compartmentImages[z, :, :]) + fineHessianMask[z, :, :] = fineHessian == fineHessian.max() + fineHessianMask[z, :, :] = morphology.binary_closing( + fineHessianMask[z, :, :], + morphology.selem.disk(5)) + fineHessianMask[z, :, :] = fineHessianMask[z, :, :] * borderMask + fineHessianMask[z, :, :] = binary_fill_holes( + fineHessianMask[z, :, :]) + + # generate compartment mask from hessian, coarse + coarseHessianMask = np.zeros(compartmentImages.shape) + for z in range(compartmentImages.shape[0]): + coarseHessian = filters.hessian(compartmentImages[z, :, :] - + morphology.white_tophat( + compartmentImages[z, :, :], + morphology.selem.disk(20))) + coarseHessianMask[z, :, :] = coarseHessian == coarseHessian.max() + coarseHessianMask[z, :, :] = morphology.binary_closing( + coarseHessianMask[z, :, :], morphology.selem.disk(5)) + coarseHessianMask[z, :, :] = (coarseHessianMask[z, :, :] * + borderMask) + coarseHessianMask[z, :, :] = binary_fill_holes( + coarseHessianMask[z, :, :]) + + # combine masks + compartmentMask = thresholdingMask + fineHessianMask + coarseHessianMask + return binary_fill_holes(compartmentMask) + + +def get_cv2_watershed_markers(compartmentImages: np.ndarray, + membraneImages: np.ndarray, + membraneFlag: int) -> np.ndarray: + """Combine membrane and compartment markers into a single multilabel mask + for CV2 watershed + + Args: + compartmentImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + membraneImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + membraneFlag: 0 if compartment and membrane images are the same, 1 + otherwise + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) of + cv2-compatible watershed markers + """ + + compartmentMask = get_compartment_mask(compartmentImages) + membraneMask = get_membrane_mask(membraneImages, membraneFlag) + + watershedMarker = np.zeros(compartmentMask.shape) + + for z in range(compartmentImages.shape[0]): + + # generate areas of sure bg and fg, as well as the area of + # unknown classification + background = morphology.dilation(compartmentMask[z, :, :], + morphology.selem.disk(15)) + membraneDilated = morphology.dilation( + membraneMask[z, :, :].astype('bool'), + morphology.selem.disk(10)) + foreground = morphology.erosion(compartmentMask[z, :, :] * ~ + membraneDilated, + morphology.selem.disk(5)) + unknown = background * ~ foreground + + background = np.uint8(background) * 255 + foreground = np.uint8(foreground) * 255 + unknown = np.uint8(unknown) * 255 + + # Marker labelling + ret, markers = cv2.connectedComponents(foreground) + + # Add one to all labels so that sure background is not 0, but 1 + markers = markers + 100 + + # Now, mark the region of unknown with zero + markers[unknown == 255] = 0 + + watershedMarker[z, :, :] = markers + + return watershedMarker + + +def convert_grayscale_to_rgb(uint16Image: np.ndarray) -> np.ndarray: + """Convert a 16 bit 2D grayscale image into a 3D 8-bit RGB image. + cv2 only works in 8-bit. Based on https://stackoverflow.com/questions/ + 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv3D + + Args: + uint16Image: a 2 dimensional numpy array containing the 16-bit + image + Returns: + ndarray containing a 3 dimensional 8-bit image stack + """ + + # invert image + uint16Image = 2**16 - uint16Image + + # convert to uint8 + ratio = np.amax(uint16Image) / 256 + uint8Image = (uint16Image / ratio).astype('uint8') + + rgbImage = np.zeros((2048, 2048, 3)) + rgbImage[:, :, 0] = uint8Image + rgbImage[:, :, 1] = uint8Image + rgbImage[:, :, 2] = uint8Image + rgbImage = rgbImage.astype('uint8') + + return rgbImage + + +def apply_cv2_watershed(compartmentImages: np.ndarray, + watershedMarkers: np.ndarray) -> np.ndarray: + """Perform watershed using cv2 + + Args: + compartmentImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + watershedMarkers: a 3 dimensional numpy array containing the cv2 + markers arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) of + segmented cells. masks in different z positions are + independent + """ + + watershedOutput = np.zeros(watershedMarkers.shape) + for z in range(nucleiImages.shape[0]): + rgbImage = convert_grayscale_to_rgb(compartmentImages[z, :, :]) + watershedOutput[z, :, :] = cv2.watershed(rgbImage, + watershedMarkers[z, :, :]. + astype('int32')) + watershedOutput[z, :, :][watershedOutput[z, :, :] <= 100] = 0 + + return watershedOutput + + +def get_overlapping_objects(watershedZ0: np.ndarray, + watershedZ1: np.ndarray, n0: int): + """Perform watershed using cv2 + + Args: + watershedZ0: a 2 dimensional numpy array containing a + segmentation mask + watershedZ1: a 2 dimensional numpy array containing a + segmentation mask adjacent to watershedZ1 + n0: an integer with the index of the object (cell/nuclei) + to be compared between the provided watershed + segmentation masks + Returns: + a tuple (n1, f0, f1) containing the label of the cell in Z1 + overlapping n0 (n1), the fraction of n0 overlaping n1 (f0) and + the fraction of n1 overlapping n0 (f1) + """ + + z1Indexes = np.unique(watershedZ1[watershedZ0 == n0]) + z1Indexes = z1Indexes[z1Indexes > 100] + + if z1Indexes.shape[0] > 0: + + # calculate overlap fraction + n0Area = np.count_nonzero(watershedZ0 == n0) + n1Area = np.zeros(len(z1Indexes)) + overlapArea = np.zeros(len(z1Indexes)) + + for ii in range(len(z1Indexes)): + n1 = z1Indexes[ii] + n1Area[ii] = np.count_nonzero(watershedZ1 == n1) + overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * + (watershedZ1 == n1)) + + n0OverlapFraction = np.asarray(overlapArea / n0Area) + n1OverlapFraction = np.asarray(overlapArea / n1Area) + index = list(range(len(n0OverlapFraction))) + + # select the nuclei that has the highest fraction in n0 and n1 + r1, r2, indexSorted = zip(*sorted(zip(n0OverlapFraction, + n1OverlapFraction, + index), + reverse=True)) + + if (n0OverlapFraction[indexSorted[0]] > 0.2 and + n1OverlapFraction[indexSorted[0]] > 0.5): + return z1Indexes[indexSorted[0]], + n0OverlapFraction[indexSorted[0]], + n1OverlapFraction[indexSorted[0]] + else: + return False, False, False + else: + return False, False, False + + +def combine_2d_segmentation_masks_into_3d(watershedOutput: + np.ndarray) -> np.ndarray: + """Take a 3 dimensional watershed masks and relabel them so that + nuclei in adjacent sections have the same label if the area their + overlap surpases certain threshold + + Args: + watershedOutput: a 3 dimensional numpy array containing the + segmentation masks arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) of + relabeled segmented cells + """ + + # Initialize empty array with size as watershedOutput array + watershedCombinedZ = np.zeros(watershedOutput.shape) + + # copy the mask of the section farthest to the coverslip + watershedCombinedZ[-1, :, :] = watershedOutput[-1, :, :] + + # starting far from coverslip + for z in range(watershedOutput.shape[0]-1, 0, -1): + zIndex = np.unique(watershedOutput[z, :, :])[ + np.unique(watershedOutput[z, :, :]) > 100] + + for n0 in zIndex: + n1, f0, f1 = get_overlapping_objects(watershedCombinedZ[z, :, :], + watershedOutput[z-1, :, :], + n0) + if n1: + watershedCombinedZ[z-1, :, :][(watershedOutput[z-1, :, :] == + n1)] = n0 + return watershedCombinedZ + +def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: + +def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: + +def segment_using_cellpose(imageStackIn: np.ndarray, + channelName: str) -> np.ndarray: + """perform segmentation using cellpose + Args: + imageStackIn: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + channelName: a string with the channel name + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) + """ + + # DEFINE CELLPOSE MODEL + # model_type='cyto' or model_type='nuclei' + if channelName == + model = models.Cellpose(gpu=False, model_type='cyto') + +def apply_machine_learning_segmentation(imageStackIn: np.ndarray, + method: str, + channelName: str) -> np.ndarray: + """Select segmentation algorithm to use + Args: + imageStackIn: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) + """ + if method == 'ilastik': + segmentOutput = segment_using_ilastik(imageStackIn) + elif method == 'cellpose': + segmentOutput = segment_using_cellpose(imageStackIn, channelName) + elif method == 'unet' + segmentOutput = segment_using_unet(imageStackIn) + + return segmentOutput diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 53b0c7cf..f41a404b 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -189,14 +189,14 @@ def get_membrane_mask(membraneImages: np.ndarray, use_quantiles=True, low_threshold=lowThresh, high_threshold=hiThresh) - edge0 = morphology.dilation(edge0,morphology.selem.disk(15)) + edge0 = morphology.dilation(edge0,morphology.selem.disk(10)) edge1 = feature.canny(blurredImage, sigma=edgeSigma, use_quantiles=True, low_threshold=lowThresh, high_threshold=hiThresh) - edge1 = morphology.dilation(edge1,morphology.selem.disk(15)) + edge1 = morphology.dilation(edge1,morphology.selem.disk(10)) mask[z, :, :] = edge0 + edge1 From f6f5cd4f6f7a42b8660461ccc4abddc35dd7775f Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 6 May 2020 10:48:45 -0400 Subject: [PATCH 088/419] consolidate functions into segment_using_cellpose --- merlin/analysis/segment.py | 3 --- merlin/util/segmentation.py | 48 ++++++++++++++++++++++++++++++++----- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 366cd7ae..b0369f49 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -289,9 +289,6 @@ def __init__(self, dataSet, parameters=None, analysisName=None): self.parameters['method'] = 'cellpose' if 'compartment_channel_name' not in self.parameters: self.parameters['compartment_channel_name'] = 'DAPI' - if 'compartment_channel_type' not in self.parameters: - self.parameters['compartment_channel_type'] = 'cytoplasm' # nuclei - def fragment_count(self): return len(self.dataSet.get_fovs()) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index bf8d5d1c..e1970b56 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -479,12 +479,16 @@ def combine_2d_segmentation_masks_into_3d(watershedOutput: return watershedCombinedZ def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: + return None def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: + return None def segment_using_cellpose(imageStackIn: np.ndarray, channelName: str) -> np.ndarray: - """perform segmentation using cellpose + """Perform segmentation using cellpose. Code adapted from + https://nbviewer.jupyter.org/github/MouseLand/cellpose/blob/ + master/notebooks/run_cellpose.ipynb Args: imageStackIn: a 3 dimensional numpy array containing the images arranged as (z, x, y). @@ -492,11 +496,43 @@ def segment_using_cellpose(imageStackIn: np.ndarray, Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) """ + channelName = channelName.lower() + + # Define cellpose model + if any([channelName == 'dapi', channelName == 'lamin']): + model = models.Cellpose(gpu=False, model_type='nuclei') + if any([channelName == 'polyt', channelName == 'polya', + channelName == 'ecadherin',channelName == 'cd45', + channelName == 'wga', channelName == 'cona']): + model = models.Cellpose(gpu=False, model_type='cyto') + + # define CHANNELS to run segementation on + # grayscale=0, R=1, G=2, B=3 + # channels = [cytoplasm, nucleus] + # if NUCLEUS channel does not exist, set the second channel to 0 + # channels = [0,0] + # IF ALL YOUR IMAGES ARE THE SAME TYPE, you can give a list with 2 elements + channels = [0,0] # IF YOU HAVE GRAYSCALE + # channels = [2,3] # IF YOU HAVE G=cytoplasm and B=nucleus + # channels = [2,1] # IF YOU HAVE G=cytoplasm and R=nucleus + + # or if you have different types of channels in each image + # channels = [[0,0],[0,0]] + + # if diameter is set to None, the size of the cells is estimated on a per + # image basis you can set the average cell `diameter` in pixels yourself + # (recommended) diameter can be a list or a single number for all images + + # put list of images in cellpose format + imageList = np.split(imageStackIn,imageStackIn.shape[0]) + + masks, flows, styles, diams = model.eval(imageList, diameter=None, + channels=channels) + # combine masks into array + masksArray = np.stack(masks) + + return masksArray - # DEFINE CELLPOSE MODEL - # model_type='cyto' or model_type='nuclei' - if channelName == - model = models.Cellpose(gpu=False, model_type='cyto') def apply_machine_learning_segmentation(imageStackIn: np.ndarray, method: str, @@ -512,7 +548,7 @@ def apply_machine_learning_segmentation(imageStackIn: np.ndarray, segmentOutput = segment_using_ilastik(imageStackIn) elif method == 'cellpose': segmentOutput = segment_using_cellpose(imageStackIn, channelName) - elif method == 'unet' + elif method == 'unet': segmentOutput = segment_using_unet(imageStackIn) return segmentOutput From bcd05d88eb63ec05250585df846711565b966e4c Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 18 Jun 2020 11:27:31 -0400 Subject: [PATCH 089/419] add diameter param to segment_using_cellpose --- merlin/util/segmentation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index e1970b56..dd943425 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -485,7 +485,8 @@ def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: return None def segment_using_cellpose(imageStackIn: np.ndarray, - channelName: str) -> np.ndarray: + channelName: str, + diameter: np.int) -> np.ndarray: """Perform segmentation using cellpose. Code adapted from https://nbviewer.jupyter.org/github/MouseLand/cellpose/blob/ master/notebooks/run_cellpose.ipynb From 625d2f8c0be7ed38638a7a4dd763359758ccb7c9 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 18 Jun 2020 11:28:30 -0400 Subject: [PATCH 090/419] add diameter param to segment_using_cellpose --- merlin/util/segmentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index dd943425..1f316848 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -527,7 +527,7 @@ def segment_using_cellpose(imageStackIn: np.ndarray, # put list of images in cellpose format imageList = np.split(imageStackIn,imageStackIn.shape[0]) - masks, flows, styles, diams = model.eval(imageList, diameter=None, + masks, flows, styles, diams = model.eval(imageList, diameter=diameter, channels=channels) # combine masks into array masksArray = np.stack(masks) From 6e257d93d85950821a98c42b8d36e5f4a34599f2 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 18 Jun 2020 12:23:26 -0400 Subject: [PATCH 091/419] put segmentation params into dict --- merlin/analysis/segment.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index b0369f49..22fc3653 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -287,6 +287,8 @@ def __init__(self, dataSet, parameters=None, analysisName=None): if 'method' not in self.parameters: self.parameters['method'] = 'cellpose' + if 'diameter' not in self.parameters: + self.parameters['diameter'] = 50 if 'compartment_channel_name' not in self.parameters: self.parameters['compartment_channel_name'] = 'DAPI' @@ -338,10 +340,15 @@ def _run_analysis(self, fragmentIndex): print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) + if self.parameters['method'] == 'cellpose': + segParameters = dict({ + 'method':'cellpose', + 'diameter':self.parameters['diameter'], + 'channel':self.parameters['compartment_channel_name'] + }) + segmentationOutput = segmentation.apply_machine_learning_segmentation( - compartmentImages, - self.parameters['method'], - self.parameters['compartment_channel_name']) + compartmentImages,segParameters) endTime = time.time() print(" Segmentation finished, ET {:.2f} min".format( From aea55f8cdca8a6bb77aefd859673aa0adfdb81df Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 19 Jun 2020 08:23:26 -0400 Subject: [PATCH 092/419] add parameters as dict --- merlin/analysis/segment.py | 2 +- merlin/util/segmentation.py | 43 +++++++++++++++++++++---------------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 22fc3653..3f49e075 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -348,7 +348,7 @@ def _run_analysis(self, fragmentIndex): }) segmentationOutput = segmentation.apply_machine_learning_segmentation( - compartmentImages,segParameters) + compartmentImages, segParameters) endTime = time.time() print(" Segmentation finished, ET {:.2f} min".format( diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 1f316848..b7c1bd4d 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -485,26 +485,29 @@ def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: return None def segment_using_cellpose(imageStackIn: np.ndarray, - channelName: str, - diameter: np.int) -> np.ndarray: + params: dict) -> np.ndarray: """Perform segmentation using cellpose. Code adapted from https://nbviewer.jupyter.org/github/MouseLand/cellpose/blob/ master/notebooks/run_cellpose.ipynb Args: imageStackIn: a 3 dimensional numpy array containing the images arranged as (z, x, y). - channelName: a string with the channel name + params: a dictionary with the parameters for segmentation Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) """ - channelName = channelName.lower() + channelName = params['channel'].lower() # Define cellpose model - if any([channelName == 'dapi', channelName == 'lamin']): + if any([channelName == 'dapi', + channelName == 'lamin']): model = models.Cellpose(gpu=False, model_type='nuclei') - if any([channelName == 'polyt', channelName == 'polya', - channelName == 'ecadherin',channelName == 'cd45', - channelName == 'wga', channelName == 'cona']): + if any([channelName == 'polyt', + channelName == 'polya', + channelName == 'ecadherin', + channelName == 'cd45', + channelName == 'wga', + channelName == 'cona']): model = models.Cellpose(gpu=False, model_type='cyto') # define CHANNELS to run segementation on @@ -527,7 +530,8 @@ def segment_using_cellpose(imageStackIn: np.ndarray, # put list of images in cellpose format imageList = np.split(imageStackIn,imageStackIn.shape[0]) - masks, flows, styles, diams = model.eval(imageList, diameter=diameter, + masks, flows, styles, diams = model.eval(imageList, + diameter=params['diameter'], channels=channels) # combine masks into array masksArray = np.stack(masks) @@ -535,21 +539,24 @@ def segment_using_cellpose(imageStackIn: np.ndarray, return masksArray -def apply_machine_learning_segmentation(imageStackIn: np.ndarray, - method: str, - channelName: str) -> np.ndarray: +def apply_machine_learning_segmentation(imageStackIn: np.ndarray, + params: dict) -> np.ndarray: """Select segmentation algorithm to use Args: imageStackIn: a 3 dimensional numpy array containing the images arranged as (z, x, y). + params: dictionary with key:value pairs with parameters to be passed + to the segmentation code. Keys used are 'method', 'diameter', + 'channel' + Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) """ - if method == 'ilastik': - segmentOutput = segment_using_ilastik(imageStackIn) - elif method == 'cellpose': - segmentOutput = segment_using_cellpose(imageStackIn, channelName) - elif method == 'unet': - segmentOutput = segment_using_unet(imageStackIn) + if params['method'] == 'ilastik': + segmentOutput = segment_using_ilastik(imageStackIn, params) + elif params['method'] == 'cellpose': + segmentOutput = segment_using_cellpose(imageStackIn, params) + elif params['method'] == 'unet': + segmentOutput = segment_using_unet(imageStackIn, params) return segmentOutput From 1621edea4d22aa1dfdf1a5496855a1368a0d6a07 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 19 Jun 2020 09:31:49 -0400 Subject: [PATCH 093/419] change indentation --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 3f49e075..c12a5556 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -353,7 +353,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" Segmentation finished, ET {:.2f} min".format( (endTime - startTime) / 60)) -""" + """ endTime = time.time() print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) @@ -393,7 +393,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" watershed z positions combined, ET {:.2f} min".format( (endTime - startTime) / 60)) -""" + """ # get features from mask. This is the slowestart (6 min for the # previous part, 15+ for the rest, for a 7 frame Image. From e570a303077b0f5ca4ac51b3625b114a411ea5bf Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 19 Jun 2020 13:19:11 -0400 Subject: [PATCH 094/419] return watershed class name to previous state --- merlin/analysis/segment.py | 2 +- merlin/core/dataset.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index c12a5556..03fcfc43 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -124,7 +124,7 @@ def _read_and_filter_image_stack(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions()))]) -class WatershedSegmentCV2(FeatureSavingAnalysisTask): +class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): """ An analysis task that determines the boundaries of features in the diff --git a/merlin/core/dataset.py b/merlin/core/dataset.py index 24b1e9e0..dd9f66fc 100755 --- a/merlin/core/dataset.py +++ b/merlin/core/dataset.py @@ -572,7 +572,7 @@ def load_analysis_task(self, analysisTaskName: str) \ -> analysistask.AnalysisTask: loadName = os.sep.join([self.get_task_subdirectory( analysisTaskName), 'task.json']) - + print(loadName) with open(loadName, 'r') as inFile: parameters = json.load(inFile) analysisModule = importlib.import_module(parameters['module']) From b4561b1d4283ef6d01cfcaf1dde535f70a54c659 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 19 Jun 2020 13:58:55 -0400 Subject: [PATCH 095/419] add 2D>3D segmentation mask --- merlin/analysis/segment.py | 41 ++------------------------------------ 1 file changed, 2 insertions(+), 39 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 03fcfc43..77bbb617 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -353,47 +353,10 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" Segmentation finished, ET {:.2f} min".format( (endTime - startTime) / 60)) - """ - endTime = time.time() - print(" images read, ET {:.2f} min".format( - (endTime - startTime) / 60)) - print(" membraneImages Type: " + str(type(membraneImages))) - print(" membraneImages Size: [" - + str(membraneImages.shape[0]) - + "," + str(membraneImages.shape[1]) - + "," + str(membraneImages.shape[2]) + "]") - print(" compartmentImages Type: " + str(type(compartmentImages))) - print(" compartmentImages Size: [" - + str(compartmentImages.shape[0]) - + "," + str(compartmentImages.shape[1]) - + "," + str(compartmentImages.shape[2]) + "]") - - # Prepare masks for cv2 watershed - watershedMarkers = watershed.get_cv2_watershed_markers( - compartmentImages, - membraneImages, - membraneFlag) - - endTime = time.time() - print(" markers calculated, ET {:.2f} min".format( - (endTime - startTime) / 60)) - - # perform watershed in individual z positions - watershedOutput = watershed.apply_cv2_watershed(compartmentImages, - watershedMarkers) - - endTime = time.time() - print(" watershed calculated, ET {:.2f} min".format( - (endTime - startTime) / 60)) # combine all z positions in watershed - watershedCombinedOutput = watershed \ - .combine_2d_segmentation_masks_into_3d(watershedOutput) - - endTime = time.time() - print(" watershed z positions combined, ET {:.2f} min".format( - (endTime - startTime) / 60)) - """ + watershedCombinedOutput = segmentation \ + .combine_2d_segmentation_masks_into_3d(segmentationOutput) # get features from mask. This is the slowestart (6 min for the # previous part, 15+ for the rest, for a 7 frame Image. From e4bc131a803557117dfceaed4093de65fe82ea80 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 19 Jun 2020 14:58:36 -0400 Subject: [PATCH 096/419] changed segmentation output variable name --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 77bbb617..d7a290be 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -250,7 +250,7 @@ def _run_analysis(self, fragmentIndex): featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( (watershedCombinedOutput == i), fragmentIndex, globalTask.fov_to_global_transform(fragmentIndex), zPos) - for i in np.unique(watershedOutput) if i != 0] + for i in np.unique(watershedCombinedOutput) if i != 0] featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) @@ -364,7 +364,7 @@ def _run_analysis(self, fragmentIndex): featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( (watershedCombinedOutput == i), fragmentIndex, globalTask.fov_to_global_transform(fragmentIndex), zPos) - for i in np.unique(watershedOutput) if i != 0] + for i in np.unique(watershedCombinedOutput) if i != 0] featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) From c660309eab312f2e8bf6edf0afa8c584e9b50e35 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 24 Jun 2020 10:52:08 -0400 Subject: [PATCH 097/419] changed value of the background mask from 100 to 0 --- merlin/util/segmentation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index b7c1bd4d..6ef7f35b 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -408,7 +408,7 @@ def get_overlapping_objects(watershedZ0: np.ndarray, """ z1Indexes = np.unique(watershedZ1[watershedZ0 == n0]) - z1Indexes = z1Indexes[z1Indexes > 100] + z1Indexes = z1Indexes[z1Indexes > 0] if z1Indexes.shape[0] > 0: @@ -467,7 +467,7 @@ def combine_2d_segmentation_masks_into_3d(watershedOutput: # starting far from coverslip for z in range(watershedOutput.shape[0]-1, 0, -1): zIndex = np.unique(watershedOutput[z, :, :])[ - np.unique(watershedOutput[z, :, :]) > 100] + np.unique(watershedOutput[z, :, :]) > 0] for n0 in zIndex: n1, f0, f1 = get_overlapping_objects(watershedCombinedZ[z, :, :], From 05a24894bc1c5c5e9cd45287de8463ac7f5e39cc Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 24 Jun 2020 11:09:57 -0400 Subject: [PATCH 098/419] mofified botched indentation in combine 2d>3d --- merlin/util/segmentation.py | 62 +++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 6ef7f35b..7989b280 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -389,39 +389,38 @@ def apply_cv2_watershed(compartmentImages: np.ndarray, return watershedOutput -def get_overlapping_objects(watershedZ0: np.ndarray, - watershedZ1: np.ndarray, n0: int): - """Perform watershed using cv2 +def get_overlapping_objects(segmentationZ0: np.ndarray, + segmentationZ1: np.ndarray, n0: int): + """compare cell labels in adjacent image masks Args: - watershedZ0: a 2 dimensional numpy array containing a - segmentation mask - watershedZ1: a 2 dimensional numpy array containing a - segmentation mask adjacent to watershedZ1 + segmentationZ0: a 2 dimensional numpy array containing a + segmentation mask in position Z + segmentationZ1: a 2 dimensional numpy array containing a + segmentation mask adjacent tosegmentationZ0 n0: an integer with the index of the object (cell/nuclei) - to be compared between the provided watershed - segmentation masks + to be compared between the provided segmentation masks Returns: a tuple (n1, f0, f1) containing the label of the cell in Z1 overlapping n0 (n1), the fraction of n0 overlaping n1 (f0) and the fraction of n1 overlapping n0 (f1) """ - z1Indexes = np.unique(watershedZ1[watershedZ0 == n0]) + z1Indexes = np.unique(segmentationZ1[segmentationZ0 == n0]) z1Indexes = z1Indexes[z1Indexes > 0] if z1Indexes.shape[0] > 0: # calculate overlap fraction - n0Area = np.count_nonzero(watershedZ0 == n0) + n0Area = np.count_nonzero(segmentationZ0 == n0) n1Area = np.zeros(len(z1Indexes)) overlapArea = np.zeros(len(z1Indexes)) for ii in range(len(z1Indexes)): n1 = z1Indexes[ii] - n1Area[ii] = np.count_nonzero(watershedZ1 == n1) - overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * - (watershedZ1 == n1)) + n1Area[ii] = np.count_nonzero(segmentationZ1 == n1) + overlapArea[ii] = np.count_nonzero((segmentationZ0 == n0) * + (segmentationZ1 == n1)) n0OverlapFraction = np.asarray(overlapArea / n0Area) n1OverlapFraction = np.asarray(overlapArea / n1Area) @@ -444,39 +443,42 @@ def get_overlapping_objects(watershedZ0: np.ndarray, return False, False, False -def combine_2d_segmentation_masks_into_3d(watershedOutput: +def combine_2d_segmentation_masks_into_3d(segmentationOutput: np.ndarray) -> np.ndarray: - """Take a 3 dimensional watershed masks and relabel them so that + """Take a 3 dimensional segmentation masks and relabel them so that nuclei in adjacent sections have the same label if the area their overlap surpases certain threshold Args: - watershedOutput: a 3 dimensional numpy array containing the + segmentationOutput: a 3 dimensional numpy array containing the segmentation masks arranged as (z, x, y). Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) of relabeled segmented cells """ - # Initialize empty array with size as watershedOutput array - watershedCombinedZ = np.zeros(watershedOutput.shape) + # Initialize empty array with size as segmentationOutput array + segmentationCombinedZ = np.zeros(segmentationOutput.shape) - # copy the mask of the section farthest to the coverslip - watershedCombinedZ[-1, :, :] = watershedOutput[-1, :, :] + # copy the mask of the section farthest to the coverslip to start + segmentationCombinedZ[-1, :, :] = segmentationOutput[-1, :, :] # starting far from coverslip - for z in range(watershedOutput.shape[0]-1, 0, -1): - zIndex = np.unique(watershedOutput[z, :, :])[ - np.unique(watershedOutput[z, :, :]) > 0] + for z in range(segmentationOutput.shape[0]-1, 0, -1): + + # get non-background cell indexes + zIndex = np.unique(segmentationOutput[z, :, :])[ + np.unique(segmentationOutput[z, :, :]) > 0] - for n0 in zIndex: - n1, f0, f1 = get_overlapping_objects(watershedCombinedZ[z, :, :], - watershedOutput[z-1, :, :], + # compare each cell in z0 + for n0 in zIndex: + n1, f0, f1 = get_overlapping_objects(segmentationCombinedZ[z, :, :], + segmentationOutput[z-1, :, :], n0) - if n1: - watershedCombinedZ[z-1, :, :][(watershedOutput[z-1, :, :] == + if n1: + segmentationCombinedZ[z-1, :, :][(segmentationOutput[z-1, :, :] == n1)] = n0 - return watershedCombinedZ + return segmentationCombinedZ def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: return None From 03b50f0017b6118b800b56e4899e4e6d41a27666 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 24 Jun 2020 12:20:33 -0400 Subject: [PATCH 099/419] modifying tuple output in 2d>3d --- merlin/util/segmentation.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 7989b280..1c762418 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -378,7 +378,7 @@ def apply_cv2_watershed(compartmentImages: np.ndarray, independent """ - watershedOutput = np.zeros(watershedMarkers.shape) + watershedOutput = np.zeros(watershedMarkers.shape) for z in range(nucleiImages.shape[0]): rgbImage = convert_grayscale_to_rgb(compartmentImages[z, :, :]) watershedOutput[z, :, :] = cv2.watershed(rgbImage, @@ -390,7 +390,7 @@ def apply_cv2_watershed(compartmentImages: np.ndarray, def get_overlapping_objects(segmentationZ0: np.ndarray, - segmentationZ1: np.ndarray, n0: int): + segmentationZ1: np.ndarray, n0: int): """compare cell labels in adjacent image masks Args: @@ -435,8 +435,8 @@ def get_overlapping_objects(segmentationZ0: np.ndarray, if (n0OverlapFraction[indexSorted[0]] > 0.2 and n1OverlapFraction[indexSorted[0]] > 0.5): return z1Indexes[indexSorted[0]], - n0OverlapFraction[indexSorted[0]], - n1OverlapFraction[indexSorted[0]] + n0OverlapFraction[indexSorted[0]], + n1OverlapFraction[indexSorted[0]] else: return False, False, False else: @@ -472,9 +472,12 @@ def combine_2d_segmentation_masks_into_3d(segmentationOutput: # compare each cell in z0 for n0 in zIndex: - n1, f0, f1 = get_overlapping_objects(segmentationCombinedZ[z, :, :], + out = get_overlapping_objects(segmentationCombinedZ[z, :, :], segmentationOutput[z-1, :, :], n0) + n1 = out[0] + f0 = out[1] + f1 = out[2] if n1: segmentationCombinedZ[z-1, :, :][(segmentationOutput[z-1, :, :] == n1)] = n0 From 03b8f1728324ce8cbfa720fa542413bb085cc28d Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 24 Jun 2020 12:26:30 -0400 Subject: [PATCH 100/419] modifying tuple output in 2d>3d --- merlin/util/segmentation.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 1c762418..e54cea3f 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -472,12 +472,9 @@ def combine_2d_segmentation_masks_into_3d(segmentationOutput: # compare each cell in z0 for n0 in zIndex: - out = get_overlapping_objects(segmentationCombinedZ[z, :, :], + n1, f0, f1 = get_overlapping_objects(segmentationCombinedZ[z, :, :], segmentationOutput[z-1, :, :], n0) - n1 = out[0] - f0 = out[1] - f1 = out[2] if n1: segmentationCombinedZ[z-1, :, :][(segmentationOutput[z-1, :, :] == n1)] = n0 From e452cf1ee16e5f481f854f730bd0303e2a4061eb Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 24 Jun 2020 12:35:36 -0400 Subject: [PATCH 101/419] made tuple explicit --- merlin/util/segmentation.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index e54cea3f..832e962d 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -434,13 +434,13 @@ def get_overlapping_objects(segmentationZ0: np.ndarray, if (n0OverlapFraction[indexSorted[0]] > 0.2 and n1OverlapFraction[indexSorted[0]] > 0.5): - return z1Indexes[indexSorted[0]], - n0OverlapFraction[indexSorted[0]], - n1OverlapFraction[indexSorted[0]] + return (z1Indexes[indexSorted[0]], + n0OverlapFraction[indexSorted[0]], + n1OverlapFraction[indexSorted[0]]) else: - return False, False, False + return (False, False, False) else: - return False, False, False + return (False, False, False) def combine_2d_segmentation_masks_into_3d(segmentationOutput: From 17e47b33ad99929d65f693af3d17735a884d5f75 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 1 Jul 2020 16:47:50 -0400 Subject: [PATCH 102/419] pep8 compliance --- merlin/analysis/segment.py | 6 +++--- merlin/util/segmentation.py | 37 +++++++++++++++++++------------------ 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index d7a290be..cf8cb80f 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -96,8 +96,8 @@ def _run_analysis(self, fragmentIndex): watershedIndex, 5) seeds = segmentation.separate_merged_seeds( segmentation.extract_seeds(seedImages)) - normalizedWatershed, watershedMask = segmentation.prepare_watershed_images( - watershedImages) + normalizedWatershed, watershedMask = segmentation\ + .prepare_watershed_images(watershedImages) seeds[np.invert(watershedMask)] = 0 watershedOutput = skiseg.watershed( @@ -322,7 +322,7 @@ def _run_analysis(self, fragmentIndex): print(' globalTask loaded') - # read membrane and compartment indexes + # read membrane and compartment indexes compartmentIndex = self.dataSet \ .get_data_organization() \ .get_data_channel_index( diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 832e962d..e33aeaa5 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -17,7 +17,7 @@ """ This module contains utility functions for preparing images for watershed segmentation, as well as functions to perform segmentation -using macnine learning approaches +using machine learning approaches """ # To match Matlab's strel('disk', 20) @@ -152,13 +152,13 @@ def get_membrane_mask(membraneImages: np.ndarray, compartmentChannelName: str) -> np.ndarray: """Calculate binary mask with 1's in membrane pixels and 0 otherwise. The images expected are some type of membrane label (WGA, ConA, - Lamin, Cadherins) or compartment images (DAPI, CD45, polyT) + Lamin, Cadherins) or compartment images (DAPI, CD45, polyT) Args: membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). membraneChannelName: A string with the name of a membrane channel. - compartmentChannelName: A string with the name of the compartment + compartmentChannelName: A string with the name of the compartment channel Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) @@ -210,8 +210,8 @@ def get_membrane_mask(membraneImages: np.ndarray, def get_compartment_mask(compartmentImages: np.ndarray) -> np.ndarray: - """Calculate binary mask with 1's in compartment (nuclei or cytoplasm) - pixels and 0 otherwise. The images expected are some type of compartment + """Calculate binary mask with 1's in compartment (nuclei or cytoplasm) + pixels and 0 otherwise. The images expected are some type of compartment label (e.g. Nuclei: DAPI, Cytoplasm: PolyT, CD45, etc) Args: @@ -243,7 +243,7 @@ def get_compartment_mask(compartmentImages: np.ndarray) -> np.ndarray: # generate border mask, necessary to avoid making a single # connected component when using binary_fill_holes below - borderMask = np.zeros((compartmentImages.shape[1], + borderMask = np.zeros((compartmentImages.shape[1], compartmentImages.shape[2])) borderMask[25:(compartmentImages.shape[1]-25), 25:(compartmentImages.shape[2]-25)] = 1 @@ -291,7 +291,7 @@ def get_cv2_watershed_markers(compartmentImages: np.ndarray, arranged as (z, x, y). membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). - membraneFlag: 0 if compartment and membrane images are the same, 1 + membraneFlag: 0 if compartment and membrane images are the same, 1 otherwise Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) of @@ -390,7 +390,8 @@ def apply_cv2_watershed(compartmentImages: np.ndarray, def get_overlapping_objects(segmentationZ0: np.ndarray, - segmentationZ1: np.ndarray, n0: int): + segmentationZ1: np.ndarray, + n0: int) -> Tuple[]: """compare cell labels in adjacent image masks Args: @@ -398,7 +399,7 @@ def get_overlapping_objects(segmentationZ0: np.ndarray, segmentation mask in position Z segmentationZ1: a 2 dimensional numpy array containing a segmentation mask adjacent tosegmentationZ0 - n0: an integer with the index of the object (cell/nuclei) + n0: an integer with the index of the object (cell/nuclei) to be compared between the provided segmentation masks Returns: a tuple (n1, f0, f1) containing the label of the cell in Z1 @@ -470,14 +471,14 @@ def combine_2d_segmentation_masks_into_3d(segmentationOutput: zIndex = np.unique(segmentationOutput[z, :, :])[ np.unique(segmentationOutput[z, :, :]) > 0] - # compare each cell in z0 + # compare each cell in z0 for n0 in zIndex: n1, f0, f1 = get_overlapping_objects(segmentationCombinedZ[z, :, :], segmentationOutput[z-1, :, :], n0) if n1: - segmentationCombinedZ[z-1, :, :][(segmentationOutput[z-1, :, :] == - n1)] = n0 + segmentationCombinedZ[z-1, :, :][ + (segmentationOutput[z-1, :, :] ==n1)] = n0 return segmentationCombinedZ def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: @@ -488,7 +489,7 @@ def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: def segment_using_cellpose(imageStackIn: np.ndarray, params: dict) -> np.ndarray: - """Perform segmentation using cellpose. Code adapted from + """Perform segmentation using cellpose. Code adapted from https://nbviewer.jupyter.org/github/MouseLand/cellpose/blob/ master/notebooks/run_cellpose.ipynb Args: @@ -525,14 +526,14 @@ def segment_using_cellpose(imageStackIn: np.ndarray, # or if you have different types of channels in each image # channels = [[0,0],[0,0]] - # if diameter is set to None, the size of the cells is estimated on a per - # image basis you can set the average cell `diameter` in pixels yourself + # if diameter is set to None, the size of the cells is estimated on a per + # image basis you can set the average cell `diameter` in pixels yourself # (recommended) diameter can be a list or a single number for all images # put list of images in cellpose format imageList = np.split(imageStackIn,imageStackIn.shape[0]) - masks, flows, styles, diams = model.eval(imageList, + masks, flows, styles, diams = model.eval(imageList, diameter=params['diameter'], channels=channels) # combine masks into array @@ -541,7 +542,7 @@ def segment_using_cellpose(imageStackIn: np.ndarray, return masksArray -def apply_machine_learning_segmentation(imageStackIn: np.ndarray, +def apply_machine_learning_segmentation(imageStackIn: np.ndarray, params: dict) -> np.ndarray: """Select segmentation algorithm to use Args: @@ -549,7 +550,7 @@ def apply_machine_learning_segmentation(imageStackIn: np.ndarray, arranged as (z, x, y). params: dictionary with key:value pairs with parameters to be passed to the segmentation code. Keys used are 'method', 'diameter', - 'channel' + 'channel' Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) From f25cf4c107193be08568bdd584798c668ce41e98 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 1 Jul 2020 17:02:22 -0400 Subject: [PATCH 103/419] pep8 compliance --- merlin/analysis/segment.py | 15 +++++---- merlin/util/segmentation.py | 65 +++++++++++++++++++------------------ 2 files changed, 42 insertions(+), 38 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index cf8cb80f..12dcf3e7 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -150,7 +150,7 @@ class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): def __init__(self, dataSet, parameters=None, analysisName=None): super().__init__(dataSet, parameters, analysisName) - + if 'membrane_channel_name' not in self.parameters: self.parameters['membrane_channel_name'] = 'DAPI' if 'compartment_channel_name' not in self.parameters: @@ -265,6 +265,7 @@ def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: return np.array([warpTask.get_aligned_image(fov, channelIndex, z) for z in range(len(self.dataSet.get_z_positions()))]) + class MachineLearningSegment(FeatureSavingAnalysisTask): """ @@ -273,9 +274,9 @@ class MachineLearningSegment(FeatureSavingAnalysisTask): method. The available methods are: unet: - + ilastik: - + cellpose: TODO: ADD FLAT FIELD CORRECTION TASK @@ -284,7 +285,7 @@ class MachineLearningSegment(FeatureSavingAnalysisTask): def __init__(self, dataSet, parameters=None, analysisName=None): super().__init__(dataSet, parameters, analysisName) - + if 'method' not in self.parameters: self.parameters['method'] = 'cellpose' if 'diameter' not in self.parameters: @@ -342,9 +343,9 @@ def _run_analysis(self, fragmentIndex): if self.parameters['method'] == 'cellpose': segParameters = dict({ - 'method':'cellpose', - 'diameter':self.parameters['diameter'], - 'channel':self.parameters['compartment_channel_name'] + 'method': 'cellpose', + 'diameter': self.parameters['diameter'], + 'channel': self.parameters['compartment_channel_name'] }) segmentationOutput = segmentation.apply_machine_learning_segmentation( diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index e33aeaa5..528bd15b 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -140,8 +140,8 @@ def prepare_watershed_images(watershedImageStack: np.ndarray normalizedWatershed = 1 - (watershedImageStack - np.min(watershedImageStack)) / \ - (np.max(watershedImageStack) - - np.min(watershedImageStack)) + (np.max(watershedImageStack) + - np.min(watershedImageStack)) normalizedWatershed[np.invert(watershedMask)] = 1 return normalizedWatershed, watershedMask @@ -181,26 +181,26 @@ def get_membrane_mask(membraneImages: np.ndarray, else: filterSigma2 = 5 filterSize2 = int(2*np.ceil(2*filterSigma2)+1) - edgeSigma = 2 #1 #2 - lowThresh = 0.1 #0.5 #0.2 - hiThresh = 0.5 #0.7 #0.6 + edgeSigma = 2# 1 #2 + lowThresh = 0.1# 0.5 #0.2 + hiThresh = 0.5# 0.7 #0.6 for z in range(membraneImages.shape[0]): blurredImage = cv2.GaussianBlur(membraneImages[z, :, :], - (filterSize2,filterSize2), + (filterSize2, filterSize2), filterSigma2) edge0 = feature.canny(membraneImages[z, :, :], - sigma=edgeSigma, - use_quantiles=True, - low_threshold=lowThresh, - high_threshold=hiThresh) - edge0 = morphology.dilation(edge0,morphology.selem.disk(10)) - - edge1 = feature.canny(blurredImage, - sigma=edgeSigma, - use_quantiles=True, - low_threshold=lowThresh, - high_threshold=hiThresh) - edge1 = morphology.dilation(edge1,morphology.selem.disk(10)) + sigma=edgeSigma, + use_quantiles=True, + low_threshold=lowThresh, + high_threshold=hiThresh) + edge0 = morphology.dilation(edge0, morphology.selem.disk(10)) + + edge1 = feature.canny(blurredImage, + sigma=edgeSigma, + use_quantiles=True, + low_threshold=lowThresh, + high_threshold=hiThresh) + edge1 = morphology.dilation(edge1, morphology.selem.disk(10)) mask[z, :, :] = edge0 + edge1 @@ -245,7 +245,7 @@ def get_compartment_mask(compartmentImages: np.ndarray) -> np.ndarray: # connected component when using binary_fill_holes below borderMask = np.zeros((compartmentImages.shape[1], compartmentImages.shape[2])) - borderMask[25:(compartmentImages.shape[1]-25), + borderMask[25:(compartmentImages.shape[1]-25), 25:(compartmentImages.shape[2]-25)] = 1 # generate compartment mask from hessian, fine @@ -378,7 +378,7 @@ def apply_cv2_watershed(compartmentImages: np.ndarray, independent """ - watershedOutput = np.zeros(watershedMarkers.shape) + watershedOutput = np.zeros(watershedMarkers.shape) for z in range(nucleiImages.shape[0]): rgbImage = convert_grayscale_to_rgb(compartmentImages[z, :, :]) watershedOutput[z, :, :] = cv2.watershed(rgbImage, @@ -390,7 +390,7 @@ def apply_cv2_watershed(compartmentImages: np.ndarray, def get_overlapping_objects(segmentationZ0: np.ndarray, - segmentationZ1: np.ndarray, + segmentationZ1: np.ndarray, n0: int) -> Tuple[]: """compare cell labels in adjacent image masks @@ -436,8 +436,8 @@ def get_overlapping_objects(segmentationZ0: np.ndarray, if (n0OverlapFraction[indexSorted[0]] > 0.2 and n1OverlapFraction[indexSorted[0]] > 0.5): return (z1Indexes[indexSorted[0]], - n0OverlapFraction[indexSorted[0]], - n1OverlapFraction[indexSorted[0]]) + n0OverlapFraction[indexSorted[0]], + n1OverlapFraction[indexSorted[0]]) else: return (False, False, False) else: @@ -474,19 +474,22 @@ def combine_2d_segmentation_masks_into_3d(segmentationOutput: # compare each cell in z0 for n0 in zIndex: n1, f0, f1 = get_overlapping_objects(segmentationCombinedZ[z, :, :], - segmentationOutput[z-1, :, :], - n0) + segmentationOutput[z-1, :, :], + n0) if n1: segmentationCombinedZ[z-1, :, :][ - (segmentationOutput[z-1, :, :] ==n1)] = n0 + (segmentationOutput[z-1, :, :] == n1)] = n0 return segmentationCombinedZ + def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: return None + def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: return None + def segment_using_cellpose(imageStackIn: np.ndarray, params: dict) -> np.ndarray: """Perform segmentation using cellpose. Code adapted from @@ -502,14 +505,14 @@ def segment_using_cellpose(imageStackIn: np.ndarray, channelName = params['channel'].lower() # Define cellpose model - if any([channelName == 'dapi', + if any([channelName == 'dapi', channelName == 'lamin']): model = models.Cellpose(gpu=False, model_type='nuclei') - if any([channelName == 'polyt', + if any([channelName == 'polyt', channelName == 'polya', channelName == 'ecadherin', channelName == 'cd45', - channelName == 'wga', + channelName == 'wga', channelName == 'cona']): model = models.Cellpose(gpu=False, model_type='cyto') @@ -519,7 +522,7 @@ def segment_using_cellpose(imageStackIn: np.ndarray, # if NUCLEUS channel does not exist, set the second channel to 0 # channels = [0,0] # IF ALL YOUR IMAGES ARE THE SAME TYPE, you can give a list with 2 elements - channels = [0,0] # IF YOU HAVE GRAYSCALE + channels = [0,0] # IF YOU HAVE GRAYSCALE # channels = [2,3] # IF YOU HAVE G=cytoplasm and B=nucleus # channels = [2,1] # IF YOU HAVE G=cytoplasm and R=nucleus @@ -531,7 +534,7 @@ def segment_using_cellpose(imageStackIn: np.ndarray, # (recommended) diameter can be a list or a single number for all images # put list of images in cellpose format - imageList = np.split(imageStackIn,imageStackIn.shape[0]) + imageList = np.split(imageStackIn, imageStackIn.shape[0]) masks, flows, styles, diams = model.eval(imageList, diameter=params['diameter'], From d98348a3887b1576a0ffd7dafeca81e184553233 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 1 Jul 2020 18:14:37 -0400 Subject: [PATCH 104/419] pep8 compliance --- merlin/util/watershed.py | 44 ++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index f41a404b..11870e8d 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -148,13 +148,13 @@ def get_membrane_mask(membraneImages: np.ndarray, compartmentChannelName: str) -> np.ndarray: """Calculate binary mask with 1's in membrane pixels and 0 otherwise. The images expected are some type of membrane label (WGA, ConA, - Lamin, Cadherins) or compartment images (DAPI, CD45, polyT) + Lamin, Cadherins) or compartment images (DAPI, CD45, polyT) Args: membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). membraneChannelName: A string with the name of a membrane channel. - compartmentChannelName: A string with the name of the compartment + compartmentChannelName: A string with the name of the compartment channel Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) @@ -177,26 +177,26 @@ def get_membrane_mask(membraneImages: np.ndarray, else: filterSigma2 = 5 filterSize2 = int(2*np.ceil(2*filterSigma2)+1) - edgeSigma = 2 #1 #2 - lowThresh = 0.1 #0.5 #0.2 - hiThresh = 0.5 #0.7 #0.6 + edgeSigma = 2 #1 #2 + lowThresh = 0.1 #0.5 #0.2 + hiThresh = 0.5 #0.7 #0.6 for z in range(membraneImages.shape[0]): blurredImage = cv2.GaussianBlur(membraneImages[z, :, :], - (filterSize2,filterSize2), + (filterSize2, filterSize2), filterSigma2) edge0 = feature.canny(membraneImages[z, :, :], sigma=edgeSigma, use_quantiles=True, low_threshold=lowThresh, high_threshold=hiThresh) - edge0 = morphology.dilation(edge0,morphology.selem.disk(10)) - - edge1 = feature.canny(blurredImage, - sigma=edgeSigma, - use_quantiles=True, - low_threshold=lowThresh, - high_threshold=hiThresh) - edge1 = morphology.dilation(edge1,morphology.selem.disk(10)) + edge0 = morphology.dilation(edge0, morphology.selem.disk(10)) + + edge1 = feature.canny(blurredImage, + sigma=edgeSigma, + use_quantiles=True, + low_threshold=lowThresh, + high_threshold=hiThresh) + edge1 = morphology.dilation(edge1, morphology.selem.disk(10)) mask[z, :, :] = edge0 + edge1 @@ -206,8 +206,8 @@ def get_membrane_mask(membraneImages: np.ndarray, def get_compartment_mask(compartmentImages: np.ndarray) -> np.ndarray: - """Calculate binary mask with 1's in compartment (nuclei or cytoplasm) - pixels and 0 otherwise. The images expected are some type of compartment + """Calculate binary mask with 1's in compartment (nuclei or cytoplasm) + pixels and 0 otherwise. The images expected are some type of compartment label (e.g. Nuclei: DAPI, Cytoplasm: PolyT, CD45, etc) Args: @@ -239,9 +239,9 @@ def get_compartment_mask(compartmentImages: np.ndarray) -> np.ndarray: # generate border mask, necessary to avoid making a single # connected component when using binary_fill_holes below - borderMask = np.zeros((compartmentImages.shape[1], + borderMask = np.zeros((compartmentImages.shape[1], compartmentImages.shape[2])) - borderMask[25:(compartmentImages.shape[1]-25), + borderMask[25:(compartmentImages.shape[1]-25), 25:(compartmentImages.shape[2]-25)] = 1 # generate compartment mask from hessian, fine @@ -287,7 +287,7 @@ def get_cv2_watershed_markers(compartmentImages: np.ndarray, arranged as (z, x, y). membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). - membraneFlag: 0 if compartment and membrane images are the same, 1 + membraneFlag: 0 if compartment and membrane images are the same, 1 otherwise Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) of @@ -386,7 +386,7 @@ def apply_cv2_watershed(compartmentImages: np.ndarray, def get_overlapping_objects(watershedZ0: np.ndarray, - watershedZ1: np.ndarray, n0: int): + watershedZ1: np.ndarray, n0: int): """Perform watershed using cv2 Args: @@ -394,8 +394,8 @@ def get_overlapping_objects(watershedZ0: np.ndarray, segmentation mask watershedZ1: a 2 dimensional numpy array containing a segmentation mask adjacent to watershedZ1 - n0: an integer with the index of the object (cell/nuclei) - to be compared between the provided watershed + n0: an integer with the index of the object (cell/nuclei) + to be compared between the provided watershed segmentation masks Returns: a tuple (n1, f0, f1) containing the label of the cell in Z1 From 5144a5813e3789643c5ed0fe7fd74a9c02b87c51 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 1 Jul 2020 18:18:52 -0400 Subject: [PATCH 105/419] pep8 compliance --- merlin/util/segmentation.py | 2 +- merlin/util/watershed.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 528bd15b..3a1c201b 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -522,7 +522,7 @@ def segment_using_cellpose(imageStackIn: np.ndarray, # if NUCLEUS channel does not exist, set the second channel to 0 # channels = [0,0] # IF ALL YOUR IMAGES ARE THE SAME TYPE, you can give a list with 2 elements - channels = [0,0] # IF YOU HAVE GRAYSCALE + channels = [0, 0] # IF YOU HAVE GRAYSCALE # channels = [2,3] # IF YOU HAVE G=cytoplasm and B=nucleus # channels = [2,1] # IF YOU HAVE G=cytoplasm and R=nucleus diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 11870e8d..762c3e4d 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -177,18 +177,18 @@ def get_membrane_mask(membraneImages: np.ndarray, else: filterSigma2 = 5 filterSize2 = int(2*np.ceil(2*filterSigma2)+1) - edgeSigma = 2 #1 #2 - lowThresh = 0.1 #0.5 #0.2 - hiThresh = 0.5 #0.7 #0.6 + edgeSigma = 2 # 1, 2 + lowThresh = 0.1 # 0.5, 0.2 + hiThresh = 0.5 # 0.7, 0.6 for z in range(membraneImages.shape[0]): blurredImage = cv2.GaussianBlur(membraneImages[z, :, :], (filterSize2, filterSize2), filterSigma2) edge0 = feature.canny(membraneImages[z, :, :], - sigma=edgeSigma, - use_quantiles=True, - low_threshold=lowThresh, - high_threshold=hiThresh) + sigma=edgeSigma, + use_quantiles=True, + low_threshold=lowThresh, + high_threshold=hiThresh) edge0 = morphology.dilation(edge0, morphology.selem.disk(10)) edge1 = feature.canny(blurredImage, From ca4def3eacca37ef66f5730a12dad3243c75f8e1 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 1 Jul 2020 18:36:19 -0400 Subject: [PATCH 106/419] added Machine Learning Segment and CV2 to tests --- .../test_analysis_parameters.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/auxiliary_files/test_analysis_parameters.json b/test/auxiliary_files/test_analysis_parameters.json index 4d27f2ec..17680453 100755 --- a/test/auxiliary_files/test_analysis_parameters.json +++ b/test/auxiliary_files/test_analysis_parameters.json @@ -112,6 +112,25 @@ "global_align_task": "SimpleGlobalAlignment" } }, + { + "task": "WatershedSegmentNucleiCV2", + "module": "merlin.analysis.segment", + "parameters": { + "warp_task": "FiducialCorrelationWarp", + "global_align_task": "SimpleGlobalAlignment" + } + }, + { + "task": "MachineLearningSegment", + "module": "merlin.analysis.segment", + "parameters": { + "warp_task": "FiducialCorrelationWarp", + "global_align_task": "SimpleGlobalAlignment", + "diameter": 50, + "method": "cellpose", + "compartment_channel_name": "DAPI" + } + }, { "task": "CleanCellBoundaries", "module": "merlin.analysis.segment", From 62bb725888663599c40a4a55811c55e67bb759b5 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 1 Jul 2020 19:16:44 -0400 Subject: [PATCH 107/419] remove print statements for timing --- merlin/analysis/segment.py | 72 ++------------------------------------ 1 file changed, 3 insertions(+), 69 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 12dcf3e7..9dcd4b4a 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -178,13 +178,9 @@ def get_cell_boundaries(self) -> List[spatialfeature.SpatialFeature]: def _run_analysis(self, fragmentIndex): startTime = time.time() - print('Entered the _run_analysis method, FOV ' + str(fragmentIndex)) - globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) - print(' globalTask loaded') - # read membrane and compartment indexes membraneIndex = self.dataSet \ .get_data_organization() \ @@ -195,55 +191,25 @@ def _run_analysis(self, fragmentIndex): .get_data_channel_index( self.parameters['compartment_channel_name']) - endTime = time.time() - print(" image indexes read, ET {:.2f} min".format( - (endTime - startTime) / 60)) - # read membrane and compartment images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) compartmentImages = self._read_image_stack(fragmentIndex, compartmentIndex) - endTime = time.time() - print(" images read, ET {:.2f} min".format( - (endTime - startTime) / 60)) - print(" membraneImages Type: " + str(type(membraneImages))) - print(" membraneImages Size: [" - + str(membraneImages.shape[0]) - + "," + str(membraneImages.shape[1]) - + "," + str(membraneImages.shape[2]) + "]") - print(" compartmentImages Type: " + str(type(compartmentImages))) - print(" compartmentImages Size: [" - + str(compartmentImages.shape[0]) - + "," + str(compartmentImages.shape[1]) - + "," + str(compartmentImages.shape[2]) + "]") - # Prepare masks for cv2 watershed watershedMarkers = segmentation.get_cv2_watershed_markers( compartmentImages, membraneImages, self.parameters['membrane_channel_name']) - endTime = time.time() - print(" markers calculated, ET {:.2f} min".format( - (endTime - startTime) / 60)) - # perform watershed in individual z positions watershedOutput = segmentation.apply_cv2_watershed(compartmentImages, watershedMarkers) - endTime = time.time() - print(" watershed calculated, ET {:.2f} min".format( - (endTime - startTime) / 60)) - # combine all z positions in watershed watershedCombinedOutput = segmentation \ .combine_2d_segmentation_masks_into_3d(watershedOutput) - endTime = time.time() - print(" watershed z positions combined, ET {:.2f} min".format( - (endTime - startTime) / 60)) - # get features from mask. This is the slowestart (6 min for the # previous part, 15+ for the rest, for a 7 frame Image. zPos = np.array(self.dataSet.get_data_organization().get_z_positions()) @@ -255,10 +221,6 @@ def _run_analysis(self, fragmentIndex): featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) - endTime = time.time() - print(" features written, ET {:.2f} min".format( - (endTime - startTime) / 60)) - def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) @@ -271,16 +233,10 @@ class MachineLearningSegment(FeatureSavingAnalysisTask): """ An analysis task that determines the boundaries of features in the image data in each field of view using a the specified machine learning - method. The available methods are: - - unet: - - ilastik: - - cellpose: - - TODO: ADD FLAT FIELD CORRECTION TASK + method. The available method is cellpose (https://github.com/MouseLand/ + cellpose). + TODO: implement unets / Ilastik """ def __init__(self, dataSet, parameters=None, analysisName=None): @@ -313,34 +269,20 @@ def get_cell_boundaries(self) -> List[spatialfeature.SpatialFeature]: return featureDB.read_features() def _run_analysis(self, fragmentIndex): - startTime = time.time() - - print('Entered the _run_analysis method, FOV ' + str(fragmentIndex)) - print('Using ' + self.parameters['method'] + ' method.') globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) - print(' globalTask loaded') - # read membrane and compartment indexes compartmentIndex = self.dataSet \ .get_data_organization() \ .get_data_channel_index( self.parameters['compartment_channel_name']) - endTime = time.time() - print(" image indexes read, ET {:.2f} min".format( - (endTime - startTime) / 60)) - # Read images and perform segmentation compartmentImages = self._read_image_stack(fragmentIndex, compartmentIndex) - endTime = time.time() - print(" images read, ET {:.2f} min".format( - (endTime - startTime) / 60)) - if self.parameters['method'] == 'cellpose': segParameters = dict({ 'method': 'cellpose', @@ -351,10 +293,6 @@ def _run_analysis(self, fragmentIndex): segmentationOutput = segmentation.apply_machine_learning_segmentation( compartmentImages, segParameters) - endTime = time.time() - print(" Segmentation finished, ET {:.2f} min".format( - (endTime - startTime) / 60)) - # combine all z positions in watershed watershedCombinedOutput = segmentation \ .combine_2d_segmentation_masks_into_3d(segmentationOutput) @@ -370,10 +308,6 @@ def _run_analysis(self, fragmentIndex): featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) - endTime = time.time() - print(" features written, ET {:.2f} min".format( - (endTime - startTime) / 60)) - def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) From 1ef2767a19d247d28816e80c26b82aab820d4a1e Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 07:12:40 -0400 Subject: [PATCH 108/419] copied dataportal.py from master --- merlin/util/dataportal.py | 46 +++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/merlin/util/dataportal.py b/merlin/util/dataportal.py index bbbc2cb0..3c8d2bb6 100644 --- a/merlin/util/dataportal.py +++ b/merlin/util/dataportal.py @@ -342,22 +342,12 @@ def get_sibling_with_extension(self, newExtension: str): return GCloudFilePortal( self._exchange_extension(newExtension), self._client) - def _error_tolerant_access(self, request, attempts=5, wait=60): - while attempts > 0: - try: - file = self._fileHandle.download_as_string() - return file - except (exceptions.GatewayTimeout, exceptions.ServiceUnavailable): - attempts -= 1 - sleep(60) - if attempts == 0: - raise - - def read_as_text(self): + def _error_tolerant_reading(self, method, startByte=None, + endByte=None): backoffSeries = [1, 2, 4, 8, 16, 32, 64, 128, 256] for sleepDuration in backoffSeries: try: - file = self._fileHandle.download_as_string().decode('utf-8') + file = method(start=startByte, end=endByte) return file except (exceptions.GatewayTimeout, exceptions.ServiceUnavailable): if sleepDuration == backoffSeries[-1]: @@ -365,18 +355,26 @@ def read_as_text(self): else: sleep(sleepDuration) + def read_as_text(self): + """ + Attempts to read a file from bucket as text, it if encounters a timeout + exception it reattempts after sleeping for exponentially increasing + delays, up to a delay of about 4 minutes + """ + file = self._error_tolerant_reading(self._fileHandle.download_as_string) + return file.decode('utf-8') + def read_file_bytes(self, startByte, endByte): - backoffSeries = [1, 2, 4, 8, 16, 32, 64, 128, 256] - for sleepDuration in backoffSeries: - try: - file = self._fileHandle.download_as_string(start=startByte, - end=endByte-1) - return file - except (exceptions.GatewayTimeout, exceptions.ServiceUnavailable): - if sleepDuration == backoffSeries[-1]: - raise - else: - sleep(sleepDuration) + """ + Attempts to read a file from bucket as bytes, it if encounters a timeout + exception it reattempts after sleeping for exponentially increasing + delays, up to a delay of about 4 minutes + """ + file = self._error_tolerant_reading(self._fileHandle.download_as_string, + startByte=startByte, + endByte=endByte-1) + return file + def close(self) -> None: pass From 3f8c1b4d49b5af4ca68f6f3af8626e0997c0e0c0 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 07:18:42 -0400 Subject: [PATCH 109/419] removed explicit tuple output from function --- merlin/util/segmentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 3a1c201b..2d7fe73f 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -391,7 +391,7 @@ def apply_cv2_watershed(compartmentImages: np.ndarray, def get_overlapping_objects(segmentationZ0: np.ndarray, segmentationZ1: np.ndarray, - n0: int) -> Tuple[]: + n0: int): """compare cell labels in adjacent image masks Args: From 526a3e701494683518e1e577977fdb137bca8675 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 07:33:30 -0400 Subject: [PATCH 110/419] added explicit tuple output --- merlin/util/segmentation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 2d7fe73f..3055ebb2 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -391,7 +391,8 @@ def apply_cv2_watershed(compartmentImages: np.ndarray, def get_overlapping_objects(segmentationZ0: np.ndarray, segmentationZ1: np.ndarray, - n0: int): + n0: int) -> Tuple[np.float64, + np.float64, np.float64]: """compare cell labels in adjacent image masks Args: From 67c3730051601558d6deb8eab209352f3ca98502 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 07:56:34 -0400 Subject: [PATCH 111/419] add cellpose to requirements --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 9b6a77a0..b3f6b6c4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,3 +28,4 @@ tables boto3 xmltodict google-cloud-storage +cellpose \ No newline at end of file From 8b873df99604f33d48a927a0891729ffbd5bb408 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 08:04:52 -0400 Subject: [PATCH 112/419] updated docutils and pillow requirements --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index b3f6b6c4..5a4b077c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,4 +28,6 @@ tables boto3 xmltodict google-cloud-storage +pillow<=7.0.0 +docutils<0.16,>=0.10 cellpose \ No newline at end of file From 50a92cb92df0c0c0cfe272c52991ff4f1ff5ce63 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 10:14:29 -0400 Subject: [PATCH 113/419] changed marker bg in ge_cv2_markers --- merlin/util/segmentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 3055ebb2..8a2ef68d 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -325,7 +325,7 @@ def get_cv2_watershed_markers(compartmentImages: np.ndarray, ret, markers = cv2.connectedComponents(foreground) # Add one to all labels so that sure background is not 0, but 1 - markers = markers + 100 + markers = markers + 1 # Now, mark the region of unknown with zero markers[unknown == 255] = 0 From 9049980d51d67be38102d6c5839cd28d10106140 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 10:21:12 -0400 Subject: [PATCH 114/419] removed docutils requirement to avoid conflict with v017 --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5a4b077c..c4018e95 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,5 +29,4 @@ boto3 xmltodict google-cloud-storage pillow<=7.0.0 -docutils<0.16,>=0.10 cellpose \ No newline at end of file From 9058b2f5579e81fefc8ed6eb1a39ed01a8613f19 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 11:23:03 -0400 Subject: [PATCH 115/419] added input variables to get_membrane_mask --- merlin/util/segmentation.py | 17 +++++++++++------ requirements.txt | 1 + 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 8a2ef68d..2e9b924d 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -148,8 +148,8 @@ def prepare_watershed_images(watershedImageStack: np.ndarray def get_membrane_mask(membraneImages: np.ndarray, - membraneChannelName: str, - compartmentChannelName: str) -> np.ndarray: + compartmentChannelName: str, + membraneChannelName: str) -> np.ndarray: """Calculate binary mask with 1's in membrane pixels and 0 otherwise. The images expected are some type of membrane label (WGA, ConA, Lamin, Cadherins) or compartment images (DAPI, CD45, polyT) @@ -282,7 +282,8 @@ def get_compartment_mask(compartmentImages: np.ndarray) -> np.ndarray: def get_cv2_watershed_markers(compartmentImages: np.ndarray, membraneImages: np.ndarray, - membraneFlag: int) -> np.ndarray: + compartmentChannelName: str, + membraneChannelName: str) -> np.ndarray: """Combine membrane and compartment markers into a single multilabel mask for CV2 watershed @@ -291,15 +292,19 @@ def get_cv2_watershed_markers(compartmentImages: np.ndarray, arranged as (z, x, y). membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). - membraneFlag: 0 if compartment and membrane images are the same, 1 - otherwise + compartmentChannelName: str with the name of the compartment channel + to use + membraneChannelName: str with the name of the membrane channel + to use Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) of cv2-compatible watershed markers """ compartmentMask = get_compartment_mask(compartmentImages) - membraneMask = get_membrane_mask(membraneImages, membraneFlag) + membraneMask = get_membrane_mask(membraneImages, + compartmentChannelName, + membraneChannelName) watershedMarker = np.zeros(compartmentMask.shape) diff --git a/requirements.txt b/requirements.txt index c4018e95..e4dc2d75 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,5 +28,6 @@ tables boto3 xmltodict google-cloud-storage +docutils<0.16,>=0.10 pillow<=7.0.0 cellpose \ No newline at end of file From 76bf34a5b233706c8bc623d83df2c9586958be13 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 12:49:56 -0400 Subject: [PATCH 116/419] added additional variable --- merlin/analysis/segment.py | 1 + 1 file changed, 1 insertion(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 9dcd4b4a..0cc628c1 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -200,6 +200,7 @@ def _run_analysis(self, fragmentIndex): watershedMarkers = segmentation.get_cv2_watershed_markers( compartmentImages, membraneImages, + self.parameters['compartment_channel_name'], self.parameters['membrane_channel_name']) # perform watershed in individual z positions From 86544250d7a91f6b79c9dfee996413b545e3dc85 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 18:09:50 -0400 Subject: [PATCH 117/419] updated variable name --- merlin/util/segmentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 2e9b924d..461e4fee 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -384,7 +384,7 @@ def apply_cv2_watershed(compartmentImages: np.ndarray, """ watershedOutput = np.zeros(watershedMarkers.shape) - for z in range(nucleiImages.shape[0]): + for z in range(compartmentImages.shape[0]): rgbImage = convert_grayscale_to_rgb(compartmentImages[z, :, :]) watershedOutput[z, :, :] = cv2.watershed(rgbImage, watershedMarkers[z, :, :]. From fdc4fde9b6e52b3c847c27f4d64d7383c27395c8 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 22 Jan 2020 14:44:24 -0500 Subject: [PATCH 118/419] Adding _get_membrane_mask and _get_nuclei_mask functions to WatershedSegmentNucleiCV2 class --- merlin/analysis/segment.py | 209 +++++++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 786cb0d9..d90401ea 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -2,10 +2,14 @@ import numpy as np from skimage import measure from skimage import segmentation +from skimage import morphology +from skimage import feature +from skimage import filters import rtree from shapely import geometry from typing import List, Dict from scipy.spatial import cKDTree +from scipy.ndimage.morphology import binary_fill_holes from merlin.core import dataset from merlin.core import analysistask @@ -119,6 +123,211 @@ def _read_and_filter_image_stack(self, fov: int, channelIndex: int, (filterSize, filterSize), filterSigma) for z in range(len(self.dataSet.get_z_positions()))]) +class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): + + """ + An analysis task that determines the boundaries of features in the + image data in each field of view using a watershed algorithm + implemented in CV2. + + A tutorial explaining the general scheme of the method can be + found in https://opencv-python-tutroals.readthedocs.io/en/latest/ + py_tutorials/py_imgproc/py_watershed/py_watershed.html. + + The watershed segmentation is performed in each z-position independently and + combined into 3D objects in a later step + + Since each field of view is analyzed individually, the segmentation results + should be cleaned in order to merge cells that cross the field of + view boundary. + """ + + def __init__(self, dataSet, parameters=None, analysisName=None): + super().__init__(dataSet, parameters, analysisName) + + if 'seed_channel_name' not in self.parameters: + self.parameters['seed_channel_name'] = 'WGA' + if 'watershed_channel_name' not in self.parameters: + self.parameters['watershed_channel_name'] = 'DAPI' + + def fragment_count(self): + return len(self.dataSet.get_fovs()) + + def get_estimated_memory(self): + # TODO - refine estimate + return 2048 + + def get_estimated_time(self): + # TODO - refine estimate + return 5 + + def get_dependencies(self): + return [self.parameters['warp_task'], + self.parameters['global_align_task']] + + def get_cell_boundaries(self) -> List[spatialfeature.SpatialFeature]: + featureDB = self.get_feature_database() + return featureDB.read_features() + + def _run_analysis(self, fragmentIndex): + globalTask = self.dataSet.load_analysis_task( + self.parameters['global_align_task']) + + seedIndex = self.dataSet.get_data_organization().get_data_channel_index( + self.parameters['seed_channel_name']) + seedImages = self._read_and_filter_image_stack(fragmentIndex, + seedIndex, 5) + + watershedIndex = self.dataSet.get_data_organization() \ + .get_data_channel_index(self.parameters['watershed_channel_name']) + watershedImages = self._read_and_filter_image_stack(fragmentIndex, + watershedIndex, 5) + seeds = watershed.separate_merged_seeds( + watershed.extract_seeds(seedImages)) + normalizedWatershed, watershedMask = watershed.prepare_watershed_images( + watershedImages) + + seeds[np.invert(watershedMask)] = 0 + watershedOutput = segmentation.watershed( + normalizedWatershed, measure.label(seeds), mask=watershedMask, + connectivity=np.ones((3, 3, 3)), watershed_line=True) + + zPos = np.array(self.dataSet.get_data_organization().get_z_positions()) + featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( + (watershedOutput == i), fragmentIndex, + globalTask.fov_to_global_transform(fragmentIndex), zPos) + for i in np.unique(watershedOutput) if i != 0] + + featureDB = self.get_feature_database() + featureDB.write_features(featureList, fragmentIndex) + + def _get_membrane_mask(self, fov: int, channelIndex: int, + filterSigma: float) -> np.ndarray: + warpTask = self.dataSet.load_analysis_task( + self.parameters['warp_task']) + + imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) + for z in range(len(self.dataSet.get_z_positions()))]) + + # generate mask based on edge detection + edgeMask = np.zeros(imageStack.shape) + for z in range(len(self.dataSet.get_z_positions())): + edgeMask[:,:,z] = canny( + white_tophat(imageStack[:,:,z], selem.disk(10)), + sigma=2, use_quantiles=True, low_threshold=0.5, + high_threshold=0.8) + edgeMask[:,:,z] = binary_closing(edgeMask[:,:,z],selem.disk(5)) + edgeMask[:,:,z] = remove_small_objects( + edgeMask[:,:,z].astype('bool'), min_size=100, connectivity=1) + edgeMask[:,:,z] = skeletonize(edgeMask[:,:,z]) + + # generate mask based on thresholding + tresholdingMask = np.zeros(imageStack.shape) + fineBlockSize = 61 + for z in range(len(self.dataSet.get_z_positions())): + tresholdingMask[:,:,z] = imageStack[:,:,z] > + threshold_local(imageStack[:,:,z], fineBlockSize, offset=0) + tresholdingMask[:,:,z] = remove_small_objects( + imageStack[:,:,z].astype('bool'), min_size=100, connectivity=1) + tresholdingMask[:,:,z] = binary_closing(imageStack[:,:,z], + selem.disk(5)) + tresholdingMask[:,:,z] = skeletonize(imageStack[:,:,z]) + + # combine masks + return edgeMask + thresholdingMask + + def _get_nuclei_mask(self, fov: int, channelIndex: int, + filterSigma: float) -> np.ndarray: + + warpTask = self.dataSet.load_analysis_task( + self.parameters['warp_task']) + + imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) + for z in range(len(self.dataSet.get_z_positions()))]) + + # generate nuclei mask based on thresholding + thresholdingMask = np.zeros(imageStack.shape) + coarseBlockSize = 241 + fineBlockSize = 61 + for z in range(len(self.dataSet.get_z_positions())): + coarseThresholdingMask = imageStack[:,:,z] > + threshold_local(imageStack[:,:,z], coarseBlockSize, offset=0) + fineThresholdingMask = imageStack[:,:,z] > + threshold_local(imageStack[:,:,z], fineBlockSize, offset=0) + thresholdingMask[:,:,z] = coarseThresholdingMask* + fineThresholdingMask + thresholdingMask[:,:,z] = binary_fill_holes(thresholdingMask[:,:,z]) + + # generate nuclei mask from hessian, fine + fineHessianMask = np.zeros(imageStack.shape) + for z in range(len(self.dataSet.get_z_positions())): + fineHessian = hessian(imageStack[:,:,z]) + fineHessianMask[:,:,z] = fineHessian == fineHessian.max() + fineHessianMask[:,:,z] = binary_closing( + fineHessianMask[:,:,z], selem.disk(5)) + fineHessianMask[:,:,z] = binary_fill_holes(fineHessianMask[:,:,z]) + + # generate dapi mask from hessian, coarse + coarseHessianMask = np.zeros(imageStack.shape) + for z in range(len(self.dataSet.get_z_positions())): + coarseHessian = hessian(imageStack[:,:,z] - + white_tophat(imageStack[:,:,z], selem.disk(20))) + coarseHessianMask[:,:,z] = coarseHessian == coarseHessian.max() + coarseHessianMask[:,:,z] = binary_closing(coarseHessianMask[:,:,z], + selem.disk(5)) + coarseHessianMask[:,:,z] = binary_fill_holes( + coarseHessianMask[:,:,z]) + + # combine masks + nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask + return binary_fill_holes(nucleiMask) + + def _generate_watershed_mask(self, fov: int, channelIndex: int, + filterSigma: float) -> np.ndarray: + + + def _read_and_filter_image_stack(self, fov: int, channelIndex: int, + filterSigma: float) -> np.ndarray: + filterSize = int(2*np.ceil(2*filterSigma)+1) + warpTask = self.dataSet.load_analysis_task( + self.parameters['warp_task']) + return np.array([cv2.GaussianBlur( + warpTask.get_aligned_image(fov, channelIndex, z), + (filterSize, filterSize), filterSigma) + for z in range(len(self.dataSet.get_z_positions()))]) + + +#-------------------------------------------------------------------------------------------------------------------- +# Generate. As before, use a combination of masks +#-------------------------------------------------------------------------------------------------------------------- +sure_bg_dapi = sm.dilation(foreground,sm.selem.disk(15)) + +mask_wga_dil = sm.dilation(mask_wga,sm.selem.disk(10)) +sure_fg = sm.erosion(foreground*~mask_wga_dil,sm.selem.disk(5)) + +unknown_dapi = sure_bg_dapi*~sure_fg + +sure_bg_dapi = np.uint8(sure_bg_dapi)*255 +sure_fg = np.uint8(sure_fg)*255 +unknown_dapi = np.uint8(unknown_dapi)*255 + + +# Marker labelling +ret, markers = cv2.connectedComponents(sure_fg) + +# Add one to all labels so that sure background is not 0, but 1 +markers_dapi = markers+100 + +# Now, mark the region of unknown with zero +markers_dapi[unknown_dapi==255] = 0 + +# Apply watershed using cv2 +markers_ws_dapi = cv2.watershed(Idapi_inv,markers_dapi) + + + + + class CleanCellBoundaries(analysistask.ParallelAnalysisTask): ''' From 7335d789c1c73d35db1434dbbca1eceb6f36a45e Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Jan 2020 08:49:46 -0500 Subject: [PATCH 119/419] changed formatting to comply to pep8 --- merlin/analysis/segment.py | 50 ++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index d90401ea..661b0184 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -134,12 +134,12 @@ class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): found in https://opencv-python-tutroals.readthedocs.io/en/latest/ py_tutorials/py_imgproc/py_watershed/py_watershed.html. - The watershed segmentation is performed in each z-position independently and - combined into 3D objects in a later step + The watershed segmentation is performed in each z-position + independently and combined into 3D objects in a later step - Since each field of view is analyzed individually, the segmentation results - should be cleaned in order to merge cells that cross the field of - view boundary. + Since each field of view is analyzed individually, the segmentation + results should be cleaned in order to merge cells that cross the + field of view boundary. """ def __init__(self, dataSet, parameters=None, analysisName=None): @@ -213,9 +213,9 @@ def _get_membrane_mask(self, fov: int, channelIndex: int, edgeMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): edgeMask[:,:,z] = canny( - white_tophat(imageStack[:,:,z], selem.disk(10)), - sigma=2, use_quantiles=True, low_threshold=0.5, - high_threshold=0.8) + white_tophat(imageStack[:,:,z], selem.disk(10)), + sigma=2, use_quantiles=True, + low_threshold=0.5, high_threshold=0.8) edgeMask[:,:,z] = binary_closing(edgeMask[:,:,z],selem.disk(5)) edgeMask[:,:,z] = remove_small_objects( edgeMask[:,:,z].astype('bool'), min_size=100, connectivity=1) @@ -225,12 +225,13 @@ def _get_membrane_mask(self, fov: int, channelIndex: int, tresholdingMask = np.zeros(imageStack.shape) fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - tresholdingMask[:,:,z] = imageStack[:,:,z] > + tresholdingMask[:,:,z] = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], fineBlockSize, offset=0) tresholdingMask[:,:,z] = remove_small_objects( - imageStack[:,:,z].astype('bool'), min_size=100, connectivity=1) - tresholdingMask[:,:,z] = binary_closing(imageStack[:,:,z], - selem.disk(5)) + imageStack[:,:,z].astype('bool'), + min_size=100, connectivity=1) + tresholdingMask[:,:,z] = binary_closing(imageStack[:,:,z], + selem.disk(5)) tresholdingMask[:,:,z] = skeletonize(imageStack[:,:,z]) # combine masks @@ -250,12 +251,14 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int, coarseBlockSize = 241 fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = imageStack[:,:,z] > - threshold_local(imageStack[:,:,z], coarseBlockSize, offset=0) + coarseThresholdingMask = imageStack[:,:,z] > + threshold_local(imageStack[:,:,z], + coarseBlockSize, offset=0) fineThresholdingMask = imageStack[:,:,z] > - threshold_local(imageStack[:,:,z], fineBlockSize, offset=0) + threshold_local(imageStack[:,:,z], + fineBlockSize, offset=0) thresholdingMask[:,:,z] = coarseThresholdingMask* - fineThresholdingMask + fineThresholdingMask thresholdingMask[:,:,z] = binary_fill_holes(thresholdingMask[:,:,z]) # generate nuclei mask from hessian, fine @@ -263,20 +266,21 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions())): fineHessian = hessian(imageStack[:,:,z]) fineHessianMask[:,:,z] = fineHessian == fineHessian.max() - fineHessianMask[:,:,z] = binary_closing( - fineHessianMask[:,:,z], selem.disk(5)) + fineHessianMask[:,:,z] = binary_closing(fineHessianMask[:,:,z], + selem.disk(5)) fineHessianMask[:,:,z] = binary_fill_holes(fineHessianMask[:,:,z]) # generate dapi mask from hessian, coarse coarseHessianMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): - coarseHessian = hessian(imageStack[:,:,z] - - white_tophat(imageStack[:,:,z], selem.disk(20))) + coarseHessian = hessian(imageStack[:,:,z] - + white_tophat(imageStack[:,:,z], + selem.disk(20))) coarseHessianMask[:,:,z] = coarseHessian == coarseHessian.max() - coarseHessianMask[:,:,z] = binary_closing(coarseHessianMask[:,:,z], - selem.disk(5)) + coarseHessianMask[:,:,z] = binary_closing(coarseHessianMask[:,:,z], + selem.disk(5)) coarseHessianMask[:,:,z] = binary_fill_holes( - coarseHessianMask[:,:,z]) + coarseHessianMask[:,:,z]) # combine masks nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask From 8db691f29f34c7434a43f3beba11c3936161c2e8 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Jan 2020 09:47:58 -0500 Subject: [PATCH 120/419] add borderMask to _get_nuclei_mask function --- merlin/analysis/segment.py | 44 ++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 661b0184..9651589c 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -210,6 +210,7 @@ def _get_membrane_mask(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions()))]) # generate mask based on edge detection + """ edgeMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): edgeMask[:,:,z] = canny( @@ -218,24 +219,28 @@ def _get_membrane_mask(self, fov: int, channelIndex: int, low_threshold=0.5, high_threshold=0.8) edgeMask[:,:,z] = binary_closing(edgeMask[:,:,z],selem.disk(5)) edgeMask[:,:,z] = remove_small_objects( - edgeMask[:,:,z].astype('bool'), min_size=100, connectivity=1) + edgeMask[:,:,z].astype('bool'), + min_size=100, connectivity=1) edgeMask[:,:,z] = skeletonize(edgeMask[:,:,z]) + """ # generate mask based on thresholding tresholdingMask = np.zeros(imageStack.shape) fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:,:,z] = imageStack[:,:,z] > - threshold_local(imageStack[:,:,z], fineBlockSize, offset=0) + threshold_local(imageStack[:,:,z], + fineBlockSize, offset=0) tresholdingMask[:,:,z] = remove_small_objects( - imageStack[:,:,z].astype('bool'), - min_size=100, connectivity=1) + imageStack[:,:,z].astype('bool'), + min_size=100, connectivity=1) tresholdingMask[:,:,z] = binary_closing(imageStack[:,:,z], - selem.disk(5)) + selem.disk(5)) tresholdingMask[:,:,z] = skeletonize(imageStack[:,:,z]) # combine masks - return edgeMask + thresholdingMask + # return edgeMask + thresholdingMask + return thresholdingMask def _get_nuclei_mask(self, fov: int, channelIndex: int, filterSigma: float) -> np.ndarray: @@ -253,21 +258,31 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions())): coarseThresholdingMask = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], - coarseBlockSize, offset=0) + coarseBlockSize, offset=0) fineThresholdingMask = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], - fineBlockSize, offset=0) + fineBlockSize, offset=0) thresholdingMask[:,:,z] = coarseThresholdingMask* fineThresholdingMask - thresholdingMask[:,:,z] = binary_fill_holes(thresholdingMask[:,:,z]) - + thresholdingMask[:,:,z] = binary_fill_holes( + thresholdingMask[:,:,z]) + + # generate border mask, necessary to avoid making a single + # connected component when using binary_fill_holes below + borderMask = np.zeros((2048,2048)) + borderMask[25:2023,25:2023] = 1 + + # TODO - use the image size variable for borderMask + + # generate nuclei mask from hessian, fine fineHessianMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): fineHessian = hessian(imageStack[:,:,z]) fineHessianMask[:,:,z] = fineHessian == fineHessian.max() fineHessianMask[:,:,z] = binary_closing(fineHessianMask[:,:,z], - selem.disk(5)) + selem.disk(5)) + fineHessianMask[:,:,z] = fineHessianMask[:,:,z]*borderMask fineHessianMask[:,:,z] = binary_fill_holes(fineHessianMask[:,:,z]) # generate dapi mask from hessian, coarse @@ -275,12 +290,13 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions())): coarseHessian = hessian(imageStack[:,:,z] - white_tophat(imageStack[:,:,z], - selem.disk(20))) + selem.disk(20))) coarseHessianMask[:,:,z] = coarseHessian == coarseHessian.max() coarseHessianMask[:,:,z] = binary_closing(coarseHessianMask[:,:,z], - selem.disk(5)) + selem.disk(5)) + coarseHessianMask[:,:,z] = coarseHessianMask[:,:,z]*borderMask coarseHessianMask[:,:,z] = binary_fill_holes( - coarseHessianMask[:,:,z]) + coarseHessianMask[:,:,z]) # combine masks nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask From 5c01ed9eb36eb17ac6493e010da46e11fd026a7d Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Jan 2020 14:11:30 -0500 Subject: [PATCH 121/419] added method _generate_markers to WatershedSegmentNucleiCV2 --- merlin/analysis/segment.py | 102 +++++++++++++++++++++---------------- 1 file changed, 57 insertions(+), 45 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 9651589c..27c9456d 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -180,6 +180,16 @@ def _run_analysis(self, fragmentIndex): watershedIndex = self.dataSet.get_data_organization() \ .get_data_channel_index(self.parameters['watershed_channel_name']) + + membraneMask = self._get_membrane_mask(fragmentIndex, watershedIndex) + nucleiMask = self._get_nuclei_mask(fragmentIndex, watershedIndex) + watershedMarkers = self._generate_markers(nucleiMask,membraneMask) + + + + + + watershedImages = self._read_and_filter_image_stack(fragmentIndex, watershedIndex, 5) seeds = watershed.separate_merged_seeds( @@ -201,8 +211,7 @@ def _run_analysis(self, fragmentIndex): featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) - def _get_membrane_mask(self, fov: int, channelIndex: int, - filterSigma: float) -> np.ndarray: + def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) @@ -230,20 +239,20 @@ def _get_membrane_mask(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:,:,z] = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], - fineBlockSize, offset=0) + fineBlockSize, + offset=0) tresholdingMask[:,:,z] = remove_small_objects( - imageStack[:,:,z].astype('bool'), - min_size=100, connectivity=1) + imageStack[:,:,z].astype('bool'), + min_size=100, connectivity=1) tresholdingMask[:,:,z] = binary_closing(imageStack[:,:,z], - selem.disk(5)) + selem.disk(5)) tresholdingMask[:,:,z] = skeletonize(imageStack[:,:,z]) # combine masks # return edgeMask + thresholdingMask return thresholdingMask - def _get_nuclei_mask(self, fov: int, channelIndex: int, - filterSigma: float) -> np.ndarray: + def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) @@ -258,10 +267,12 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions())): coarseThresholdingMask = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], - coarseBlockSize, offset=0) + coarseBlockSize, + offset=0) fineThresholdingMask = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], - fineBlockSize, offset=0) + fineBlockSize, + offset=0) thresholdingMask[:,:,z] = coarseThresholdingMask* fineThresholdingMask thresholdingMask[:,:,z] = binary_fill_holes( @@ -281,7 +292,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int, fineHessian = hessian(imageStack[:,:,z]) fineHessianMask[:,:,z] = fineHessian == fineHessian.max() fineHessianMask[:,:,z] = binary_closing(fineHessianMask[:,:,z], - selem.disk(5)) + selem.disk(5)) fineHessianMask[:,:,z] = fineHessianMask[:,:,z]*borderMask fineHessianMask[:,:,z] = binary_fill_holes(fineHessianMask[:,:,z]) @@ -290,10 +301,10 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions())): coarseHessian = hessian(imageStack[:,:,z] - white_tophat(imageStack[:,:,z], - selem.disk(20))) + selem.disk(20))) coarseHessianMask[:,:,z] = coarseHessian == coarseHessian.max() coarseHessianMask[:,:,z] = binary_closing(coarseHessianMask[:,:,z], - selem.disk(5)) + selem.disk(5)) coarseHessianMask[:,:,z] = coarseHessianMask[:,:,z]*borderMask coarseHessianMask[:,:,z] = binary_fill_holes( coarseHessianMask[:,:,z]) @@ -302,6 +313,39 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int, nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask return binary_fill_holes(nucleiMask) + def _generate_markers(self, nucleiMask: np.ndarray, + membraneMask: np.ndarray) -> np.ndarray: + + watershedMarker = np.zeros(nucleiMask.shape) + + for z in range(len(self.dataSet.get_z_positions())): + + # generate areas of sure bg and fg, as well as the area of + # unknown classification + background = sm.dilation(nucleiMask[:,:,z],sm.selem.disk(15)) + membraneDilated = sm.dilation(membraneMask[:,:,z].astype('bool'), + sm.selem.disk(10)) + foreground = sm.erosion(nucleiMask[:,:,z]*~membraneDilated, + sm.selem.disk(5)) + unknown = background*~foreground + + background = np.uint8(background)*255 + foreground = np.uint8(foreground)*255 + unknown = np.uint8(unknown)*255 + + # Marker labelling + ret, markers = cv2.connectedComponents(foreground) + + # Add one to all labels so that sure background is not 0, but 1 + markers = markers+100 + + # Now, mark the region of unknown with zero + markers[unknown==255] = 0 + + watershedMarker[:,:,z] = markers + + return watershedMarker + def _generate_watershed_mask(self, fov: int, channelIndex: int, filterSigma: float) -> np.ndarray: @@ -317,38 +361,6 @@ def _read_and_filter_image_stack(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions()))]) -#-------------------------------------------------------------------------------------------------------------------- -# Generate. As before, use a combination of masks -#-------------------------------------------------------------------------------------------------------------------- -sure_bg_dapi = sm.dilation(foreground,sm.selem.disk(15)) - -mask_wga_dil = sm.dilation(mask_wga,sm.selem.disk(10)) -sure_fg = sm.erosion(foreground*~mask_wga_dil,sm.selem.disk(5)) - -unknown_dapi = sure_bg_dapi*~sure_fg - -sure_bg_dapi = np.uint8(sure_bg_dapi)*255 -sure_fg = np.uint8(sure_fg)*255 -unknown_dapi = np.uint8(unknown_dapi)*255 - - -# Marker labelling -ret, markers = cv2.connectedComponents(sure_fg) - -# Add one to all labels so that sure background is not 0, but 1 -markers_dapi = markers+100 - -# Now, mark the region of unknown with zero -markers_dapi[unknown_dapi==255] = 0 - -# Apply watershed using cv2 -markers_ws_dapi = cv2.watershed(Idapi_inv,markers_dapi) - - - - - - class CleanCellBoundaries(analysistask.ParallelAnalysisTask): ''' A task to construct a network graph where each cell is a node, and overlaps From 3fc16d76e4cd6e8bca4cbff3a50bc0560c95e6a7 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Jan 2020 14:26:39 -0500 Subject: [PATCH 122/419] added method _apply_watershed --- merlin/analysis/segment.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 27c9456d..41f32d41 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -183,9 +183,9 @@ def _run_analysis(self, fragmentIndex): membraneMask = self._get_membrane_mask(fragmentIndex, watershedIndex) nucleiMask = self._get_nuclei_mask(fragmentIndex, watershedIndex) - watershedMarkers = self._generate_markers(nucleiMask,membraneMask) - - + watershedMarkers = self._get_watershed_markers(nucleiMask,membraneMask) + watershedOutput = self._apply_watershed(fragmentIndex, watershedIndex, + watershedMarkers) @@ -313,7 +313,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask return binary_fill_holes(nucleiMask) - def _generate_markers(self, nucleiMask: np.ndarray, + def _get_watershed_markers(self, nucleiMask: np.ndarray, membraneMask: np.ndarray) -> np.ndarray: watershedMarker = np.zeros(nucleiMask.shape) @@ -346,8 +346,8 @@ def _generate_markers(self, nucleiMask: np.ndarray, return watershedMarker - def _generate_watershed_mask(self, fov: int, channelIndex: int, - filterSigma: float) -> np.ndarray: + def _apply_watershed(self, fov: int, channelIndex: int, + watershedMarkers: np.ndarray) -> np.ndarray: def _read_and_filter_image_stack(self, fov: int, channelIndex: int, From 81043f195edb71085e4fa5d01b59f63603a0711d Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Jan 2020 16:33:41 -0500 Subject: [PATCH 123/419] added method _convert_grayscale_to_rgb --- merlin/analysis/segment.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 41f32d41..8a80fac1 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -346,8 +346,33 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, return watershedMarker + def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: + # create 3D images of 8bit + # https://stackoverflow.com/questions/25485886/how-to-convert-a + # -16-bit-to-an-8-bit-image-in-opencv + + # invert image + uint16Image = 2**16 - uint16Image + + # convert to uint8 + ratio = np.amax(uint16Image) / 256 ; + uint8Image = (uint16Image / ratio).astype('uint8') + + rgbImage = np.zeros((2048,2048,3)) + rgbImage[:,:,0] = uint8Image + rgbImage[:,:,1] = uint8Image + rgbImage[:,:,2] = uint8Image + rgbImage = rgbImage.astype('uint8') + + return rgbImage + def _apply_watershed(self, fov: int, channelIndex: int, watershedMarkers: np.ndarray) -> np.ndarray: + warpTask = self.dataSet.load_analysis_task( + self.parameters['warp_task']) + + imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) + for z in range(len(self.dataSet.get_z_positions()))]) def _read_and_filter_image_stack(self, fov: int, channelIndex: int, From 8cde4bffb74fc4a9bd4266ebd226c617f22efdde Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Jan 2020 16:45:05 -0500 Subject: [PATCH 124/419] modify _apply_watershed method --- merlin/analysis/segment.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 8a80fac1..51688cf5 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -374,6 +374,14 @@ def _apply_watershed(self, fov: int, channelIndex: int, imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) for z in range(len(self.dataSet.get_z_positions()))]) + watershedOutput = np.zeros(watershedMarkers.shape) + for z in range(len(self.dataSet.get_z_positions())): + rgbImage = convert_grayscale_to_rgb(dapiStack[:,:,z]) + watershedOutput[:,:,z] = cv2.watershed(rgbImage, + watershedMarkers[:,:,z]. + astype('int32')) + return watershedOutput + def _read_and_filter_image_stack(self, fov: int, channelIndex: int, filterSigma: float) -> np.ndarray: From 518b6b74d35be28af19cdb6b9a8400b95ef2cc8b Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Jan 2020 17:55:02 -0500 Subject: [PATCH 125/419] adding _combine_watershed_z_positions method, starting by pseudocode --- merlin/analysis/segment.py | 61 ++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 51688cf5..6e7a3c2b 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -181,15 +181,20 @@ def _run_analysis(self, fragmentIndex): watershedIndex = self.dataSet.get_data_organization() \ .get_data_channel_index(self.parameters['watershed_channel_name']) + # Prepare masks for cv2 watershed membraneMask = self._get_membrane_mask(fragmentIndex, watershedIndex) nucleiMask = self._get_nuclei_mask(fragmentIndex, watershedIndex) watershedMarkers = self._get_watershed_markers(nucleiMask,membraneMask) + + # perform watershed in individual z positions watershedOutput = self._apply_watershed(fragmentIndex, watershedIndex, watershedMarkers) + # combine all z positions in watershed + watershedCombinedOutput = self._combine_watershed_z_positions( + watershedOutput) - - + """ watershedImages = self._read_and_filter_image_stack(fragmentIndex, watershedIndex, 5) seeds = watershed.separate_merged_seeds( @@ -201,6 +206,7 @@ def _run_analysis(self, fragmentIndex): watershedOutput = segmentation.watershed( normalizedWatershed, measure.label(seeds), mask=watershedMask, connectivity=np.ones((3, 3, 3)), watershed_line=True) + """ zPos = np.array(self.dataSet.get_data_organization().get_z_positions()) featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( @@ -239,7 +245,7 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:,:,z] = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], - fineBlockSize, + fineBlockSize, offset=0) tresholdingMask[:,:,z] = remove_small_objects( imageStack[:,:,z].astype('bool'), @@ -267,11 +273,11 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: for z in range(len(self.dataSet.get_z_positions())): coarseThresholdingMask = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], - coarseBlockSize, + coarseBlockSize, offset=0) fineThresholdingMask = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], - fineBlockSize, + fineBlockSize, offset=0) thresholdingMask[:,:,z] = coarseThresholdingMask* fineThresholdingMask @@ -310,17 +316,17 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseHessianMask[:,:,z]) # combine masks - nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask + nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask return binary_fill_holes(nucleiMask) - def _get_watershed_markers(self, nucleiMask: np.ndarray, + def _get_watershed_markers(self, nucleiMask: np.ndarray, membraneMask: np.ndarray) -> np.ndarray: watershedMarker = np.zeros(nucleiMask.shape) for z in range(len(self.dataSet.get_z_positions())): - # generate areas of sure bg and fg, as well as the area of + # generate areas of sure bg and fg, as well as the area of # unknown classification background = sm.dilation(nucleiMask[:,:,z],sm.selem.disk(15)) membraneDilated = sm.dilation(membraneMask[:,:,z].astype('bool'), @@ -347,15 +353,16 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, return watershedMarker def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: - # create 3D images of 8bit - # https://stackoverflow.com/questions/25485886/how-to-convert-a - # -16-bit-to-an-8-bit-image-in-opencv + # cv2 only works in 3D images of 8bit. Make a 3D grayscale by + # using the same grayscale image in each of the rgb channels + # code below based on https://stackoverflow.com/questions/ + # 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv # invert image uint16Image = 2**16 - uint16Image # convert to uint8 - ratio = np.amax(uint16Image) / 256 ; + ratio = np.amax(uint16Image) / 256 uint8Image = (uint16Image / ratio).astype('uint8') rgbImage = np.zeros((2048,2048,3)) @@ -382,7 +389,34 @@ def _apply_watershed(self, fov: int, channelIndex: int, astype('int32')) return watershedOutput + def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) + -> np.ndarray: + """ + PSEUDECODE + + initialize empty array the same size as the watershedOutput array + + start loop from the section farthest from the coverslip (N) + + for each nuclei + get nuclei indexes in sections N - 1 and N + 1 (if applies) + + if there is nuclei in the projection of N into N-1 and N+1 + get the index of the overlaping nuclei in N+1 + + find the most frequent non-zero index in N+1, + + else if projection N -> N+1 has overlaping nuclei + get the indexes of nuclei in N+1 + find the index in N+1, different from zero, that is most frequent + + + relabel_segmentation_stack + clean_segmentation_stack + + """ + """ def _read_and_filter_image_stack(self, fov: int, channelIndex: int, filterSigma: float) -> np.ndarray: filterSize = int(2*np.ceil(2*filterSigma)+1) @@ -392,7 +426,8 @@ def _read_and_filter_image_stack(self, fov: int, channelIndex: int, warpTask.get_aligned_image(fov, channelIndex, z), (filterSize, filterSize), filterSigma) for z in range(len(self.dataSet.get_z_positions()))]) - + """ + class CleanCellBoundaries(analysistask.ParallelAnalysisTask): ''' From 4cee91f78a4ab28a1f95b7b106959449586a72ee Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 24 Jan 2020 17:34:20 -0500 Subject: [PATCH 126/419] expanding _combine_watershed_z_positions and adding _get_overlapping_nuclei method --- merlin/analysis/segment.py | 91 +++++++++++++++++++++++--------------- 1 file changed, 56 insertions(+), 35 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 6e7a3c2b..685576ac 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -383,51 +383,72 @@ def _apply_watershed(self, fov: int, channelIndex: int, watershedOutput = np.zeros(watershedMarkers.shape) for z in range(len(self.dataSet.get_z_positions())): - rgbImage = convert_grayscale_to_rgb(dapiStack[:,:,z]) + rgbImage = _convert_grayscale_to_rgb(dapiStack[:,:,z]) watershedOutput[:,:,z] = cv2.watershed(rgbImage, watershedMarkers[:,:,z]. astype('int32')) return watershedOutput - def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) - -> np.ndarray: - """ - PSEUDECODE + def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, + watershedZ1: np.ndarray, n0: int): + z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) + z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes>100] + + if z1NucleiIndexes.shape[0] > 0: + + # calculate overlap fraction + n0Area = np.count_nonzero(watershedZ0 == n0) + n1Area = np.zeros(len(z1NucleiIndexes)) + overlapArea = np.zeros(len(z1NucleiIndexes)) + + for ii in range(len(z1NucleiIndexes)): + n1 = z1NucleiIndexes[ii] + n1Area[ii] = np.count_nonzero(watershedZ1 == n1) + overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) + *(watershedZ1 == n1)) + + n0OverlapFraction = np.asarray(overlapArea/n0Area) + n1OverlapFraction = np.asarray(overlapArea/n1Area) + index = list(range(len(n0OverlapFraction))) + + # select the nuclei that has the highest fraction in n0 and n1 + r1, r2, indexSorted = zip(*sorted(zip(n0OverlapFraction, + n1OverlapFraction, + index), + reverse=True)) + + if n0OverlapFraction[indexSorted[0]] > 0.2 and + n1OverlapFraction[indexSorted[0]] > 0.5: + return m1NucleiIndexes[indexSorted[0]], + n0OverlapFraction[indexSorted[0]], + n1OverlapFraction[indexSorted[0]] + else: + return False, False, False + else: + return False, False, False - initialize empty array the same size as the watershedOutput array - start loop from the section farthest from the coverslip (N) + def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) + -> np.ndarray: - for each nuclei - get nuclei indexes in sections N - 1 and N + 1 (if applies) + # Initialize empty array with size as watershedOutput array + watershedCombinedZ = np.zeros(watershedOutput.shape) - if there is nuclei in the projection of N into N-1 and N+1 - get the index of the overlaping nuclei in N+1 - - find the most frequent non-zero index in N+1, - - - else if projection N -> N+1 has overlaping nuclei - get the indexes of nuclei in N+1 - find the index in N+1, different from zero, that is most frequent - - - relabel_segmentation_stack - clean_segmentation_stack - - """ - """ - def _read_and_filter_image_stack(self, fov: int, channelIndex: int, - filterSigma: float) -> np.ndarray: - filterSize = int(2*np.ceil(2*filterSigma)+1) - warpTask = self.dataSet.load_analysis_task( - self.parameters['warp_task']) - return np.array([cv2.GaussianBlur( - warpTask.get_aligned_image(fov, channelIndex, z), - (filterSize, filterSize), filterSigma) - for z in range(len(self.dataSet.get_z_positions()))]) - """ + # copy the mask of the section farthest to the coverslip + watershedCombinedZ[:,:,-1] = watershedOutput[:,:,-1] + # starting far from coverslip + for z in range(len(self.dataSet.get_z_positions())-1,0,-1): + zNucleiIndex = np.unique(watershedOutput[:,:,z])[ + np.unique(watershedOutput[:,:,z])>100] + + for n0 in zNucleiIndex: # for each nuclei N(Z) in Z + n1,f0,f1 = _get_overlapping_nuclei(watershedCombinedZ[:,:,z], + watershedOutput[:,:,z-1],n0) + if n1: + watershedCombinedZ[:,:,z-1][watershedOutput[:,:,z-1] == n1] + = n0 + return watershedCombinedZ class CleanCellBoundaries(analysistask.ParallelAnalysisTask): ''' From cf11017ef2ac4bbdd84ca904c1c00c0ae3468278 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 24 Jan 2020 17:45:42 -0500 Subject: [PATCH 127/419] pep8 compliance --- merlin/analysis/segment.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 685576ac..924d219d 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -234,7 +234,7 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: low_threshold=0.5, high_threshold=0.8) edgeMask[:,:,z] = binary_closing(edgeMask[:,:,z],selem.disk(5)) edgeMask[:,:,z] = remove_small_objects( - edgeMask[:,:,z].astype('bool'), + edgeMask[:,:,z].astype('bool'), min_size=100, connectivity=1) edgeMask[:,:,z] = skeletonize(edgeMask[:,:,z]) """ @@ -287,7 +287,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: # generate border mask, necessary to avoid making a single # connected component when using binary_fill_holes below borderMask = np.zeros((2048,2048)) - borderMask[25:2023,25:2023] = 1 + borderMask[25:2023,25:2023] = 1 # TODO - use the image size variable for borderMask @@ -394,7 +394,7 @@ def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes>100] - if z1NucleiIndexes.shape[0] > 0: + if z1NucleiIndexes.shape[0] > 0: # calculate overlap fraction n0Area = np.count_nonzero(watershedZ0 == n0) @@ -417,36 +417,38 @@ def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, index), reverse=True)) - if n0OverlapFraction[indexSorted[0]] > 0.2 and + if n0OverlapFraction[indexSorted[0]] > 0.2 and n1OverlapFraction[indexSorted[0]] > 0.5: - return m1NucleiIndexes[indexSorted[0]], - n0OverlapFraction[indexSorted[0]], + return m1NucleiIndexes[indexSorted[0]], + n0OverlapFraction[indexSorted[0]], n1OverlapFraction[indexSorted[0]] else: - return False, False, False + return False, False, False else: return False, False, False def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) -> np.ndarray: - + # TO DO: this implementation is very rough, needs to be improved. + # good just for testing purposes + # Initialize empty array with size as watershedOutput array watershedCombinedZ = np.zeros(watershedOutput.shape) - # copy the mask of the section farthest to the coverslip + # copy the mask of the section farthest to the coverslip watershedCombinedZ[:,:,-1] = watershedOutput[:,:,-1] # starting far from coverslip - for z in range(len(self.dataSet.get_z_positions())-1,0,-1): + for z in range(len(self.dataSet.get_z_positions())-1,0,-1): zNucleiIndex = np.unique(watershedOutput[:,:,z])[ np.unique(watershedOutput[:,:,z])>100] for n0 in zNucleiIndex: # for each nuclei N(Z) in Z n1,f0,f1 = _get_overlapping_nuclei(watershedCombinedZ[:,:,z], - watershedOutput[:,:,z-1],n0) + watershedOutput[:,:,z-1],n0) if n1: - watershedCombinedZ[:,:,z-1][watershedOutput[:,:,z-1] == n1] + watershedCombinedZ[:,:,z-1][watershedOutput[:,:,z-1] == n1] = n0 return watershedCombinedZ From 2027483ebaab81cec6e4890ae2a3bb93be3c3017 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 24 Jan 2020 17:55:30 -0500 Subject: [PATCH 128/419] pep8 compliance --- merlin/analysis/segment.py | 58 +++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 924d219d..10167674 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -9,7 +9,7 @@ from shapely import geometry from typing import List, Dict from scipy.spatial import cKDTree -from scipy.ndimage.morphology import binary_fill_holes +from scipy.ndimage.morphology import binary_fill_holes from merlin.core import dataset from merlin.core import analysistask @@ -123,18 +123,19 @@ def _read_and_filter_image_stack(self, fov: int, channelIndex: int, (filterSize, filterSize), filterSigma) for z in range(len(self.dataSet.get_z_positions()))]) + class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): """ An analysis task that determines the boundaries of features in the - image data in each field of view using a watershed algorithm + image data in each field of view using a watershed algorithm implemented in CV2. - A tutorial explaining the general scheme of the method can be + A tutorial explaining the general scheme of the method can be found in https://opencv-python-tutroals.readthedocs.io/en/latest/ - py_tutorials/py_imgproc/py_watershed/py_watershed.html. + py_tutorials/py_imgproc/py_watershed/py_watershed.html. - The watershed segmentation is performed in each z-position + The watershed segmentation is performed in each z-position independently and combined into 3D objects in a later step Since each field of view is analyzed individually, the segmentation @@ -199,7 +200,8 @@ def _run_analysis(self, fragmentIndex): watershedIndex, 5) seeds = watershed.separate_merged_seeds( watershed.extract_seeds(seedImages)) - normalizedWatershed, watershedMask = watershed.prepare_watershed_images( + normalizedWatershed, watershedMask = + watershed.prepare_watershed_images( watershedImages) seeds[np.invert(watershedMask)] = 0 @@ -271,12 +273,12 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseBlockSize = 241 fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = imageStack[:,:,z] > - threshold_local(imageStack[:,:,z], + coarseThresholdingMask = imageStack[:,:,z] + >threshold_local(imageStack[:,:,z], coarseBlockSize, offset=0) - fineThresholdingMask = imageStack[:,:,z] > - threshold_local(imageStack[:,:,z], + fineThresholdingMask = imageStack[:,:,z] + > threshold_local(imageStack[:,:,z], fineBlockSize, offset=0) thresholdingMask[:,:,z] = coarseThresholdingMask* @@ -291,7 +293,6 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: # TODO - use the image size variable for borderMask - # generate nuclei mask from hessian, fine fineHessianMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): @@ -301,7 +302,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: selem.disk(5)) fineHessianMask[:,:,z] = fineHessianMask[:,:,z]*borderMask fineHessianMask[:,:,z] = binary_fill_holes(fineHessianMask[:,:,z]) - + # generate dapi mask from hessian, coarse coarseHessianMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): @@ -314,18 +315,17 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseHessianMask[:,:,z] = coarseHessianMask[:,:,z]*borderMask coarseHessianMask[:,:,z] = binary_fill_holes( coarseHessianMask[:,:,z]) - + # combine masks nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask return binary_fill_holes(nucleiMask) def _get_watershed_markers(self, nucleiMask: np.ndarray, membraneMask: np.ndarray) -> np.ndarray: - watershedMarker = np.zeros(nucleiMask.shape) for z in range(len(self.dataSet.get_z_positions())): - + # generate areas of sure bg and fg, as well as the area of # unknown classification background = sm.dilation(nucleiMask[:,:,z],sm.selem.disk(15)) @@ -334,11 +334,11 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, foreground = sm.erosion(nucleiMask[:,:,z]*~membraneDilated, sm.selem.disk(5)) unknown = background*~foreground - + background = np.uint8(background)*255 foreground = np.uint8(foreground)*255 unknown = np.uint8(unknown)*255 - + # Marker labelling ret, markers = cv2.connectedComponents(foreground) @@ -357,20 +357,20 @@ def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: # using the same grayscale image in each of the rgb channels # code below based on https://stackoverflow.com/questions/ # 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv - + # invert image uint16Image = 2**16 - uint16Image - + # convert to uint8 ratio = np.amax(uint16Image) / 256 uint8Image = (uint16Image / ratio).astype('uint8') - + rgbImage = np.zeros((2048,2048,3)) rgbImage[:,:,0] = uint8Image rgbImage[:,:,1] = uint8Image rgbImage[:,:,2] = uint8Image rgbImage = rgbImage.astype('uint8') - + return rgbImage def _apply_watershed(self, fov: int, channelIndex: int, @@ -393,30 +393,30 @@ def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, watershedZ1: np.ndarray, n0: int): z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes>100] - + if z1NucleiIndexes.shape[0] > 0: - + # calculate overlap fraction n0Area = np.count_nonzero(watershedZ0 == n0) n1Area = np.zeros(len(z1NucleiIndexes)) overlapArea = np.zeros(len(z1NucleiIndexes)) - + for ii in range(len(z1NucleiIndexes)): n1 = z1NucleiIndexes[ii] n1Area[ii] = np.count_nonzero(watershedZ1 == n1) overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) *(watershedZ1 == n1)) - + n0OverlapFraction = np.asarray(overlapArea/n0Area) n1OverlapFraction = np.asarray(overlapArea/n1Area) index = list(range(len(n0OverlapFraction))) - + # select the nuclei that has the highest fraction in n0 and n1 r1, r2, indexSorted = zip(*sorted(zip(n0OverlapFraction, n1OverlapFraction, index), reverse=True)) - + if n0OverlapFraction[indexSorted[0]] > 0.2 and n1OverlapFraction[indexSorted[0]] > 0.5: return m1NucleiIndexes[indexSorted[0]], @@ -430,7 +430,7 @@ def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) -> np.ndarray: - # TO DO: this implementation is very rough, needs to be improved. + # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes # Initialize empty array with size as watershedOutput array @@ -443,7 +443,7 @@ def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) for z in range(len(self.dataSet.get_z_positions())-1,0,-1): zNucleiIndex = np.unique(watershedOutput[:,:,z])[ np.unique(watershedOutput[:,:,z])>100] - + for n0 in zNucleiIndex: # for each nuclei N(Z) in Z n1,f0,f1 = _get_overlapping_nuclei(watershedCombinedZ[:,:,z], watershedOutput[:,:,z-1],n0) From 5a44c63583fba8367e69123ad1ce779000035288 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 24 Jan 2020 18:08:59 -0500 Subject: [PATCH 129/419] pep8 compliance --- merlin/analysis/segment.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 10167674..dc706e2a 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -137,7 +137,7 @@ class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): The watershed segmentation is performed in each z-position independently and combined into 3D objects in a later step - + Since each field of view is analyzed individually, the segmentation results should be cleaned in order to merge cells that cross the field of view boundary. @@ -181,12 +181,13 @@ def _run_analysis(self, fragmentIndex): watershedIndex = self.dataSet.get_data_organization() \ .get_data_channel_index(self.parameters['watershed_channel_name']) - + # Prepare masks for cv2 watershed membraneMask = self._get_membrane_mask(fragmentIndex, watershedIndex) nucleiMask = self._get_nuclei_mask(fragmentIndex, watershedIndex) - watershedMarkers = self._get_watershed_markers(nucleiMask,membraneMask) - + watershedMarkers = self._get_watershed_markers(nucleiMask, + membraneMask) + # perform watershed in individual z positions watershedOutput = self._apply_watershed(fragmentIndex, watershedIndex, watershedMarkers) From 5df9cefb4bf7a3f328cdef5330998e226966a50b Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 10:58:37 -0500 Subject: [PATCH 130/419] pep8 compliance --- merlin/analysis/segment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index dc706e2a..0b330080 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -186,7 +186,7 @@ def _run_analysis(self, fragmentIndex): membraneMask = self._get_membrane_mask(fragmentIndex, watershedIndex) nucleiMask = self._get_nuclei_mask(fragmentIndex, watershedIndex) watershedMarkers = self._get_watershed_markers(nucleiMask, - membraneMask) + membraneMask) # perform watershed in individual z positions watershedOutput = self._apply_watershed(fragmentIndex, watershedIndex, From 020995511e42d33192b66fae4f8a84e45f3169cb Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:04:04 -0500 Subject: [PATCH 131/419] pep8 compliance --- merlin/analysis/segment.py | 98 +++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 0b330080..736f8934 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -226,36 +226,36 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) for z in range(len(self.dataSet.get_z_positions()))]) - + # generate mask based on edge detection """ edgeMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): - edgeMask[:,:,z] = canny( - white_tophat(imageStack[:,:,z], selem.disk(10)), + edgeMask[:, :, z] = canny( + white_tophat(imageStack[:, :, z], selem.disk(10)), sigma=2, use_quantiles=True, low_threshold=0.5, high_threshold=0.8) - edgeMask[:,:,z] = binary_closing(edgeMask[:,:,z],selem.disk(5)) - edgeMask[:,:,z] = remove_small_objects( - edgeMask[:,:,z].astype('bool'), + edgeMask[:, :, z] = binary_closing(edgeMask[:, :, z],selem.disk(5)) + edgeMask[:, :, z] = remove_small_objects( + edgeMask[:, :, z].astype('bool'), min_size=100, connectivity=1) - edgeMask[:,:,z] = skeletonize(edgeMask[:,:,z]) + edgeMask[:, :, z] = skeletonize(edgeMask[:, :, z]) """ # generate mask based on thresholding tresholdingMask = np.zeros(imageStack.shape) fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - tresholdingMask[:,:,z] = imageStack[:,:,z] > - threshold_local(imageStack[:,:,z], + tresholdingMask[:, :, z] = imageStack[:, :, z] > + threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) - tresholdingMask[:,:,z] = remove_small_objects( - imageStack[:,:,z].astype('bool'), + tresholdingMask[:, :, z] = remove_small_objects( + imageStack[:, :, z].astype('bool'), min_size=100, connectivity=1) - tresholdingMask[:,:,z] = binary_closing(imageStack[:,:,z], + tresholdingMask[:, :, z] = binary_closing(imageStack[:, :, z], selem.disk(5)) - tresholdingMask[:,:,z] = skeletonize(imageStack[:,:,z]) + tresholdingMask[:, :, z] = skeletonize(imageStack[:, :, z]) # combine masks # return edgeMask + thresholdingMask @@ -274,18 +274,18 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseBlockSize = 241 fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = imageStack[:,:,z] - >threshold_local(imageStack[:,:,z], + coarseThresholdingMask = imageStack[:, :, z] + >threshold_local(imageStack[:, :, z], coarseBlockSize, offset=0) - fineThresholdingMask = imageStack[:,:,z] - > threshold_local(imageStack[:,:,z], + fineThresholdingMask = imageStack[:, :, z] + > threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) - thresholdingMask[:,:,z] = coarseThresholdingMask* + thresholdingMask[:, :, z] = coarseThresholdingMask* fineThresholdingMask - thresholdingMask[:,:,z] = binary_fill_holes( - thresholdingMask[:,:,z]) + thresholdingMask[:, :, z] = binary_fill_holes( + thresholdingMask[:, :, z]) # generate border mask, necessary to avoid making a single # connected component when using binary_fill_holes below @@ -297,25 +297,25 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: # generate nuclei mask from hessian, fine fineHessianMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): - fineHessian = hessian(imageStack[:,:,z]) - fineHessianMask[:,:,z] = fineHessian == fineHessian.max() - fineHessianMask[:,:,z] = binary_closing(fineHessianMask[:,:,z], + fineHessian = hessian(imageStack[:, :, z]) + fineHessianMask[:, :, z] = fineHessian == fineHessian.max() + fineHessianMask[:, :, z] = binary_closing(fineHessianMask[:, :, z], selem.disk(5)) - fineHessianMask[:,:,z] = fineHessianMask[:,:,z]*borderMask - fineHessianMask[:,:,z] = binary_fill_holes(fineHessianMask[:,:,z]) + fineHessianMask[:, :, z] = fineHessianMask[:, :, z]*borderMask + fineHessianMask[:, :, z] = binary_fill_holes(fineHessianMask[:, :, z]) # generate dapi mask from hessian, coarse coarseHessianMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): - coarseHessian = hessian(imageStack[:,:,z] - - white_tophat(imageStack[:,:,z], + coarseHessian = hessian(imageStack[:, :, z] - + white_tophat(imageStack[:, :, z], selem.disk(20))) - coarseHessianMask[:,:,z] = coarseHessian == coarseHessian.max() - coarseHessianMask[:,:,z] = binary_closing(coarseHessianMask[:,:,z], + coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() + coarseHessianMask[:, :, z] = binary_closing(coarseHessianMask[:, :, z], selem.disk(5)) - coarseHessianMask[:,:,z] = coarseHessianMask[:,:,z]*borderMask - coarseHessianMask[:,:,z] = binary_fill_holes( - coarseHessianMask[:,:,z]) + coarseHessianMask[:, :, z] = coarseHessianMask[:, :, z]*borderMask + coarseHessianMask[:, :, z] = binary_fill_holes( + coarseHessianMask[:, :, z]) # combine masks nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask @@ -329,10 +329,10 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, # generate areas of sure bg and fg, as well as the area of # unknown classification - background = sm.dilation(nucleiMask[:,:,z],sm.selem.disk(15)) - membraneDilated = sm.dilation(membraneMask[:,:,z].astype('bool'), + background = sm.dilation(nucleiMask[:, :, z],sm.selem.disk(15)) + membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), sm.selem.disk(10)) - foreground = sm.erosion(nucleiMask[:,:,z]*~membraneDilated, + foreground = sm.erosion(nucleiMask[:, :, z]*~membraneDilated, sm.selem.disk(5)) unknown = background*~foreground @@ -349,7 +349,7 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, # Now, mark the region of unknown with zero markers[unknown==255] = 0 - watershedMarker[:,:,z] = markers + watershedMarker[:, :, z] = markers return watershedMarker @@ -367,9 +367,9 @@ def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: uint8Image = (uint16Image / ratio).astype('uint8') rgbImage = np.zeros((2048,2048,3)) - rgbImage[:,:,0] = uint8Image - rgbImage[:,:,1] = uint8Image - rgbImage[:,:,2] = uint8Image + rgbImage[:, :, 0] = uint8Image + rgbImage[:, :, 1] = uint8Image + rgbImage[:, :, 2] = uint8Image rgbImage = rgbImage.astype('uint8') return rgbImage @@ -384,9 +384,9 @@ def _apply_watershed(self, fov: int, channelIndex: int, watershedOutput = np.zeros(watershedMarkers.shape) for z in range(len(self.dataSet.get_z_positions())): - rgbImage = _convert_grayscale_to_rgb(dapiStack[:,:,z]) - watershedOutput[:,:,z] = cv2.watershed(rgbImage, - watershedMarkers[:,:,z]. + rgbImage = _convert_grayscale_to_rgb(dapiStack[:, :, z]) + watershedOutput[:, :, z] = cv2.watershed(rgbImage, + watershedMarkers[:, :, z]. astype('int32')) return watershedOutput @@ -438,18 +438,18 @@ def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) watershedCombinedZ = np.zeros(watershedOutput.shape) # copy the mask of the section farthest to the coverslip - watershedCombinedZ[:,:,-1] = watershedOutput[:,:,-1] + watershedCombinedZ[:, :, -1] = watershedOutput[:, :, -1] # starting far from coverslip - for z in range(len(self.dataSet.get_z_positions())-1,0,-1): - zNucleiIndex = np.unique(watershedOutput[:,:,z])[ - np.unique(watershedOutput[:,:,z])>100] + for z in range(len(self.dataSet.get_z_positions())-1, 0, -1): + zNucleiIndex = np.unique(watershedOutput[:, :, z])[ + np.unique(watershedOutput[:, :, z])>100] for n0 in zNucleiIndex: # for each nuclei N(Z) in Z - n1,f0,f1 = _get_overlapping_nuclei(watershedCombinedZ[:,:,z], - watershedOutput[:,:,z-1],n0) + n1,f0,f1 = _get_overlapping_nuclei(watershedCombinedZ[:, :, z], + watershedOutput[:, :, z-1],n0) if n1: - watershedCombinedZ[:,:,z-1][watershedOutput[:,:,z-1] == n1] + watershedCombinedZ[:, :, z-1][watershedOutput[:, :, z-1] == n1] = n0 return watershedCombinedZ From c8715a97af804b815e6b69b1ed79a0a68905cd51 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:09:06 -0500 Subject: [PATCH 132/419] pep8 compliance --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 736f8934..b9ce29ab 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -225,8 +225,8 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: self.parameters['warp_task']) imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet.get_z_positions()))]) - + for z in range(len(self.dataSet. + get_z_positions()))]) # generate mask based on edge detection """ edgeMask = np.zeros(imageStack.shape) From 0d82d9bbf7955bd861403fc97f8af240b98d7ac5 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:11:58 -0500 Subject: [PATCH 133/419] pep8 compliance --- merlin/analysis/segment.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index b9ce29ab..8e1b810e 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -225,7 +225,7 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: self.parameters['warp_task']) imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet. + for z in range(len(self.dataSet. get_z_positions()))]) # generate mask based on edge detection """ @@ -247,14 +247,12 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:, :, z] = imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], - fineBlockSize, - offset=0) + threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) tresholdingMask[:, :, z] = remove_small_objects( - imageStack[:, :, z].astype('bool'), - min_size=100, connectivity=1) + imageStack[:, :, z].astype('bool'), min_size=100, + connectivity=1) tresholdingMask[:, :, z] = binary_closing(imageStack[:, :, z], - selem.disk(5)) + selem.disk(5)) tresholdingMask[:, :, z] = skeletonize(imageStack[:, :, z]) # combine masks From 79d5d332eb307a1a16f41b8a482f1905fd2f6f59 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:12:48 -0500 Subject: [PATCH 134/419] pep8 compliance --- merlin/analysis/segment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 8e1b810e..32d4317a 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -225,7 +225,7 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: self.parameters['warp_task']) imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet. + for z in range(len(self.dataSet. get_z_positions()))]) # generate mask based on edge detection """ From 32e47cff8d19178ee7c775baa09a002463df0110 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:13:39 -0500 Subject: [PATCH 135/419] pep8 compliance --- merlin/analysis/segment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 32d4317a..52911b4e 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -225,7 +225,7 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: self.parameters['warp_task']) imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet. + for z in range(len(self.dataSet. get_z_positions()))]) # generate mask based on edge detection """ From 2ae10dea8e6df7f1f190e1bfc732a7f534a9cc8a Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:15:00 -0500 Subject: [PATCH 136/419] pep8 compliance --- merlin/analysis/segment.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 52911b4e..34de8891 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -226,7 +226,7 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) for z in range(len(self.dataSet. - get_z_positions()))]) + get_z_positions()))]) # generate mask based on edge detection """ edgeMask = np.zeros(imageStack.shape) @@ -247,7 +247,9 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:, :, z] = imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) + threshold_local(imageStack[:, :, z], + fineBlockSize, + offset=0) tresholdingMask[:, :, z] = remove_small_objects( imageStack[:, :, z].astype('bool'), min_size=100, connectivity=1) From c3cd8a40a9239a0cd786dc10a165cf5868cf53ad Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:15:52 -0500 Subject: [PATCH 137/419] pep8 compliance --- merlin/analysis/segment.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 34de8891..ac620bef 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -247,9 +247,9 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:, :, z] = imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], - fineBlockSize, - offset=0) + threshold_local(imageStack[:, :, z], + fineBlockSize, + offset=0) tresholdingMask[:, :, z] = remove_small_objects( imageStack[:, :, z].astype('bool'), min_size=100, connectivity=1) From 7e1da458a3e00ce20175c103cb71b13d763b7267 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:19:12 -0500 Subject: [PATCH 138/419] pep8 compliance --- merlin/analysis/segment.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index ac620bef..d9a3751d 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -247,14 +247,14 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:, :, z] = imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], - fineBlockSize, - offset=0) + threshold_local(imageStack[:, :, z], + fineBlockSize, + offset=0) tresholdingMask[:, :, z] = remove_small_objects( - imageStack[:, :, z].astype('bool'), min_size=100, + imageStack[:, :, z].astype('bool'), min_size=100, connectivity=1) tresholdingMask[:, :, z] = binary_closing(imageStack[:, :, z], - selem.disk(5)) + selem.disk(5)) tresholdingMask[:, :, z] = skeletonize(imageStack[:, :, z]) # combine masks @@ -267,7 +267,8 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: self.parameters['warp_task']) imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet.get_z_positions()))]) + for z in range(len(self.dataSet. + get_z_positions()))]) # generate nuclei mask based on thresholding thresholdingMask = np.zeros(imageStack.shape) From 138cdb091f70fdcf367acc4a1e01beaae8135265 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:27:50 -0500 Subject: [PATCH 139/419] pep8 compliance --- merlin/analysis/segment.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index d9a3751d..0e6fe0e5 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -247,14 +247,15 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:, :, z] = imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], - fineBlockSize, - offset=0) + threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) + tresholdingMask[:, :, z] = remove_small_objects( imageStack[:, :, z].astype('bool'), min_size=100, connectivity=1) + tresholdingMask[:, :, z] = binary_closing(imageStack[:, :, z], - selem.disk(5)) + selem.disk(5)) + tresholdingMask[:, :, z] = skeletonize(imageStack[:, :, z]) # combine masks @@ -290,8 +291,8 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: # generate border mask, necessary to avoid making a single # connected component when using binary_fill_holes below - borderMask = np.zeros((2048,2048)) - borderMask[25:2023,25:2023] = 1 + borderMask = np.zeros((2048, 2048)) + borderMask[25:2023, 25:2023] = 1 # TODO - use the image size variable for borderMask @@ -330,7 +331,7 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, # generate areas of sure bg and fg, as well as the area of # unknown classification - background = sm.dilation(nucleiMask[:, :, z],sm.selem.disk(15)) + background = sm.dilation(nucleiMask[:, :, z], sm.selem.disk(15)) membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), sm.selem.disk(10)) foreground = sm.erosion(nucleiMask[:, :, z]*~membraneDilated, @@ -367,7 +368,7 @@ def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: ratio = np.amax(uint16Image) / 256 uint8Image = (uint16Image / ratio).astype('uint8') - rgbImage = np.zeros((2048,2048,3)) + rgbImage = np.zeros((2048, 2048, 3)) rgbImage[:, :, 0] = uint8Image rgbImage[:, :, 1] = uint8Image rgbImage[:, :, 2] = uint8Image From c666b4b7aff8af4c285215f365d442148efb0990 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:30:30 -0500 Subject: [PATCH 140/419] pep8 compliance --- merlin/analysis/segment.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 0e6fe0e5..0281d446 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -248,14 +248,14 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:, :, z] = imageStack[:, :, z] > threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) - + tresholdingMask[:, :, z] = remove_small_objects( imageStack[:, :, z].astype('bool'), min_size=100, connectivity=1) - + tresholdingMask[:, :, z] = binary_closing(imageStack[:, :, z], - selem.disk(5)) - + selem.disk(5)) + tresholdingMask[:, :, z] = skeletonize(imageStack[:, :, z]) # combine masks @@ -276,16 +276,15 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseBlockSize = 241 fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = imageStack[:, :, z] - >threshold_local(imageStack[:, :, z], - coarseBlockSize, - offset=0) - fineThresholdingMask = imageStack[:, :, z] - > threshold_local(imageStack[:, :, z], - fineBlockSize, - offset=0) + coarseThresholdingMask = imageStack[:, :, z] > + threshold_local(imageStack[:, :, z], coarseBlockSize, offset=0) + + fineThresholdingMask = imageStack[:, :, z] > + threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) + thresholdingMask[:, :, z] = coarseThresholdingMask* fineThresholdingMask + thresholdingMask[:, :, z] = binary_fill_holes( thresholdingMask[:, :, z]) From a8eff460c3b33a00173a39abe08352a22c01c8cb Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:40:24 -0500 Subject: [PATCH 141/419] pep8 compliance --- merlin/analysis/segment.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 0281d446..8a2cba45 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -248,14 +248,11 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:, :, z] = imageStack[:, :, z] > threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) - tresholdingMask[:, :, z] = remove_small_objects( imageStack[:, :, z].astype('bool'), min_size=100, connectivity=1) - tresholdingMask[:, :, z] = binary_closing(imageStack[:, :, z], selem.disk(5)) - tresholdingMask[:, :, z] = skeletonize(imageStack[:, :, z]) # combine masks @@ -276,15 +273,12 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseBlockSize = 241 fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = imageStack[:, :, z] > + coarseThresholdingMask = imageStack[:, :, z] > threshold_local(imageStack[:, :, z], coarseBlockSize, offset=0) - - fineThresholdingMask = imageStack[:, :, z] > + fineThresholdingMask = imageStack[:, :, z] > threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) - - thresholdingMask[:, :, z] = coarseThresholdingMask* - fineThresholdingMask - + thresholdingMask[:, :, z] = coarseThresholdingMask * + fineThresholdingMask thresholdingMask[:, :, z] = binary_fill_holes( thresholdingMask[:, :, z]) @@ -301,9 +295,10 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: fineHessian = hessian(imageStack[:, :, z]) fineHessianMask[:, :, z] = fineHessian == fineHessian.max() fineHessianMask[:, :, z] = binary_closing(fineHessianMask[:, :, z], - selem.disk(5)) + selem.disk(5)) fineHessianMask[:, :, z] = fineHessianMask[:, :, z]*borderMask - fineHessianMask[:, :, z] = binary_fill_holes(fineHessianMask[:, :, z]) + fineHessianMask[:, :, z] = binary_fill_holes( + fineHessianMask[:, :, z]) # generate dapi mask from hessian, coarse coarseHessianMask = np.zeros(imageStack.shape) From 88e70b78656fae4e37c13bf46d1678241f1ede20 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:44:45 -0500 Subject: [PATCH 142/419] pep8 compliance --- merlin/analysis/segment.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 8a2cba45..26ef2d02 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -277,7 +277,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: threshold_local(imageStack[:, :, z], coarseBlockSize, offset=0) fineThresholdingMask = imageStack[:, :, z] > threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) - thresholdingMask[:, :, z] = coarseThresholdingMask * + thresholdingMask[:, :, z] = coarseThresholdingMask * fineThresholdingMask thresholdingMask[:, :, z] = binary_fill_holes( thresholdingMask[:, :, z]) @@ -307,8 +307,8 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: white_tophat(imageStack[:, :, z], selem.disk(20))) coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() - coarseHessianMask[:, :, z] = binary_closing(coarseHessianMask[:, :, z], - selem.disk(5)) + coarseHessianMask[:, :, z] = binary_closing( + coarseHessianMask[:, :, z], selem.disk(5)) coarseHessianMask[:, :, z] = coarseHessianMask[:, :, z]*borderMask coarseHessianMask[:, :, z] = binary_fill_holes( coarseHessianMask[:, :, z]) @@ -326,10 +326,10 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, # generate areas of sure bg and fg, as well as the area of # unknown classification background = sm.dilation(nucleiMask[:, :, z], sm.selem.disk(15)) - membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), - sm.selem.disk(10)) + membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), + sm.selem.disk(10)) foreground = sm.erosion(nucleiMask[:, :, z]*~membraneDilated, - sm.selem.disk(5)) + sm.selem.disk(5)) unknown = background*~foreground background = np.uint8(background)*255 From 3c793d93049a69e4b4a06bca49945bb28eb1e832 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:48:35 -0500 Subject: [PATCH 143/419] pep8 compliance --- merlin/analysis/segment.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 26ef2d02..5b0886a7 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -318,7 +318,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: return binary_fill_holes(nucleiMask) def _get_watershed_markers(self, nucleiMask: np.ndarray, - membraneMask: np.ndarray) -> np.ndarray: + membraneMask: np.ndarray) -> np.ndarray: watershedMarker = np.zeros(nucleiMask.shape) for z in range(len(self.dataSet.get_z_positions())): @@ -327,14 +327,14 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, # unknown classification background = sm.dilation(nucleiMask[:, :, z], sm.selem.disk(15)) membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), - sm.selem.disk(10)) + sm.selem.disk(10)) foreground = sm.erosion(nucleiMask[:, :, z]*~membraneDilated, - sm.selem.disk(5)) + sm.selem.disk(5)) unknown = background*~foreground background = np.uint8(background)*255 foreground = np.uint8(foreground)*255 - unknown = np.uint8(unknown)*255 + unknown = np.uint8(unknown)*255 # Marker labelling ret, markers = cv2.connectedComponents(foreground) @@ -343,7 +343,7 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, markers = markers+100 # Now, mark the region of unknown with zero - markers[unknown==255] = 0 + markers[unknown == 255] = 0 watershedMarker[:, :, z] = markers @@ -371,12 +371,13 @@ def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: return rgbImage def _apply_watershed(self, fov: int, channelIndex: int, - watershedMarkers: np.ndarray) -> np.ndarray: - warpTask = self.dataSet.load_analysis_task( + watershedMarkers: np.ndarray) -> np.ndarray: + warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet.get_z_positions()))]) + for z in range(len(self.dataSet. + get_z_positions()))]) watershedOutput = np.zeros(watershedMarkers.shape) for z in range(len(self.dataSet.get_z_positions())): From a79b5d3fa128d9ba468932ab7cdf4839f8749c70 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:53:48 -0500 Subject: [PATCH 144/419] pep8 compliance --- merlin/analysis/segment.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 5b0886a7..3473f4c6 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -318,7 +318,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: return binary_fill_holes(nucleiMask) def _get_watershed_markers(self, nucleiMask: np.ndarray, - membraneMask: np.ndarray) -> np.ndarray: + membraneMask: np.ndarray) -> np.ndarray: watershedMarker = np.zeros(nucleiMask.shape) for z in range(len(self.dataSet.get_z_positions())): @@ -371,7 +371,7 @@ def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: return rgbImage def _apply_watershed(self, fov: int, channelIndex: int, - watershedMarkers: np.ndarray) -> np.ndarray: + watershedMarkers: np.ndarray) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) @@ -383,18 +383,18 @@ def _apply_watershed(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions())): rgbImage = _convert_grayscale_to_rgb(dapiStack[:, :, z]) watershedOutput[:, :, z] = cv2.watershed(rgbImage, - watershedMarkers[:, :, z]. - astype('int32')) + watershedMarkers[:, :, z]. + astype('int32')) return watershedOutput - def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, - watershedZ1: np.ndarray, n0: int): + def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, + watershedZ1: np.ndarray, n0: int): z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) - z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes>100] + z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes > 100] if z1NucleiIndexes.shape[0] > 0: - # calculate overlap fraction + # calculate overlap fraction n0Area = np.count_nonzero(watershedZ0 == n0) n1Area = np.zeros(len(z1NucleiIndexes)) overlapArea = np.zeros(len(z1NucleiIndexes)) @@ -402,8 +402,8 @@ def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, for ii in range(len(z1NucleiIndexes)): n1 = z1NucleiIndexes[ii] n1Area[ii] = np.count_nonzero(watershedZ1 == n1) - overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) - *(watershedZ1 == n1)) + overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * + (watershedZ1 == n1)) n0OverlapFraction = np.asarray(overlapArea/n0Area) n1OverlapFraction = np.asarray(overlapArea/n1Area) @@ -415,11 +415,11 @@ def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, index), reverse=True)) - if n0OverlapFraction[indexSorted[0]] > 0.2 and + if n0OverlapFraction[indexSorted[0]] > 0.2 and n1OverlapFraction[indexSorted[0]] > 0.5: return m1NucleiIndexes[indexSorted[0]], - n0OverlapFraction[indexSorted[0]], - n1OverlapFraction[indexSorted[0]] + n0OverlapFraction[indexSorted[0]], + n1OverlapFraction[indexSorted[0]] else: return False, False, False else: From c50e2f2e9f34c3c647a5ce1488348eb009f303a7 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:56:41 -0500 Subject: [PATCH 145/419] pep8 compliance --- merlin/analysis/segment.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 3473f4c6..3808b44f 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -318,7 +318,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: return binary_fill_holes(nucleiMask) def _get_watershed_markers(self, nucleiMask: np.ndarray, - membraneMask: np.ndarray) -> np.ndarray: + membraneMask: np.ndarray) -> np.ndarray: watershedMarker = np.zeros(nucleiMask.shape) for z in range(len(self.dataSet.get_z_positions())): @@ -371,7 +371,7 @@ def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: return rgbImage def _apply_watershed(self, fov: int, channelIndex: int, - watershedMarkers: np.ndarray) -> np.ndarray: + watershedMarkers: np.ndarray) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) @@ -387,8 +387,8 @@ def _apply_watershed(self, fov: int, channelIndex: int, astype('int32')) return watershedOutput - def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, - watershedZ1: np.ndarray, n0: int): + def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, + watershedZ1: np.ndarray, n0: int): z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes > 100] @@ -402,7 +402,7 @@ def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, for ii in range(len(z1NucleiIndexes)): n1 = z1NucleiIndexes[ii] n1Area[ii] = np.count_nonzero(watershedZ1 == n1) - overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * + overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * (watershedZ1 == n1)) n0OverlapFraction = np.asarray(overlapArea/n0Area) @@ -415,8 +415,8 @@ def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, index), reverse=True)) - if n0OverlapFraction[indexSorted[0]] > 0.2 and - n1OverlapFraction[indexSorted[0]] > 0.5: + if n0OverlapFraction[indexSorted[0]] > 0.2 and + n1OverlapFraction[indexSorted[0]] > 0.5: return m1NucleiIndexes[indexSorted[0]], n0OverlapFraction[indexSorted[0]], n1OverlapFraction[indexSorted[0]] From 953efa8cd17399ce811383b2893a36e96c59ccf8 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:59:20 -0500 Subject: [PATCH 146/419] pep8 compliance --- merlin/analysis/segment.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 3808b44f..72049a8c 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -418,8 +418,8 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, if n0OverlapFraction[indexSorted[0]] > 0.2 and n1OverlapFraction[indexSorted[0]] > 0.5: return m1NucleiIndexes[indexSorted[0]], - n0OverlapFraction[indexSorted[0]], - n1OverlapFraction[indexSorted[0]] + n0OverlapFraction[indexSorted[0]], + n1OverlapFraction[indexSorted[0]] else: return False, False, False else: @@ -427,7 +427,7 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) - -> np.ndarray: + -> np.ndarray: # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes From b30f6f54e57b8df48e2f0c192b76ac9bc10ba76a Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 12:00:52 -0500 Subject: [PATCH 147/419] pep8 compliance --- merlin/analysis/segment.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 72049a8c..c17f0708 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -425,9 +425,8 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, else: return False, False, False - def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) - -> np.ndarray: + -> np.ndarray: # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes From ecfc2ac374e37d009efa55e1c96fb05569b5383d Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 12:02:24 -0500 Subject: [PATCH 148/419] pep8 compliance --- merlin/analysis/segment.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index c17f0708..b3911369 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -425,8 +425,9 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, else: return False, False, False - def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) - -> np.ndarray: + def _combine_watershed_z_positions(self, + watershedOutput: np.ndarray) + -> np.ndarray: # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes From bf4c3b4d3ca2562804ba01a53f352a4002aee056 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 13:26:40 -0500 Subject: [PATCH 149/419] fixing invalid syntax --- merlin/analysis/segment.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index b3911369..3eafec1b 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -246,8 +246,10 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: tresholdingMask = np.zeros(imageStack.shape) fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - tresholdingMask[:, :, z] = imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) + tresholdingMask[:, :, z] = (imageStack[:, :, z] > + threshold_local(imageStack[:, :, z], + fineBlockSize, + offset=0)) tresholdingMask[:, :, z] = remove_small_objects( imageStack[:, :, z].astype('bool'), min_size=100, connectivity=1) @@ -425,9 +427,8 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, else: return False, False, False - def _combine_watershed_z_positions(self, - watershedOutput: np.ndarray) - -> np.ndarray: + def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) + -> np.ndarray: # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes @@ -440,11 +441,11 @@ def _combine_watershed_z_positions(self, # starting far from coverslip for z in range(len(self.dataSet.get_z_positions())-1, 0, -1): zNucleiIndex = np.unique(watershedOutput[:, :, z])[ - np.unique(watershedOutput[:, :, z])>100] + np.unique(watershedOutput[:, :, z]) > 100] for n0 in zNucleiIndex: # for each nuclei N(Z) in Z n1,f0,f1 = _get_overlapping_nuclei(watershedCombinedZ[:, :, z], - watershedOutput[:, :, z-1],n0) + watershedOutput[:, :, z-1], n0) if n1: watershedCombinedZ[:, :, z-1][watershedOutput[:, :, z-1] == n1] = n0 From ac00fff36616d957160c2f305652ca8ea3675a7a Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 13:38:23 -0500 Subject: [PATCH 150/419] fixing invalid syntax --- merlin/analysis/segment.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 3eafec1b..ed9a2464 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -275,10 +275,14 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseBlockSize = 241 fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], coarseBlockSize, offset=0) - fineThresholdingMask = imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) + coarseThresholdingMask = (imageStack[:, :, z] > + threshold_local(imageStack[:, :, z], + coarseBlockSize, + offset=0)) + fineThresholdingMask = (imageStack[:, :, z] > + threshold_local(imageStack[:, :, z], + fineBlockSize, + offset=0)) thresholdingMask[:, :, z] = coarseThresholdingMask * fineThresholdingMask thresholdingMask[:, :, z] = binary_fill_holes( @@ -407,8 +411,8 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * (watershedZ1 == n1)) - n0OverlapFraction = np.asarray(overlapArea/n0Area) - n1OverlapFraction = np.asarray(overlapArea/n1Area) + n0OverlapFraction = np.asarray(overlapArea / n0Area) + n1OverlapFraction = np.asarray(overlapArea / n1Area) index = list(range(len(n0OverlapFraction))) # select the nuclei that has the highest fraction in n0 and n1 From 4ba56a37b29c6d5a1f53068e7e62563c115e1fa9 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 13:48:19 -0500 Subject: [PATCH 151/419] fixing invalid syntax --- merlin/analysis/segment.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index ed9a2464..194eea48 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -283,8 +283,8 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: threshold_local(imageStack[:, :, z], fineBlockSize, offset=0)) - thresholdingMask[:, :, z] = coarseThresholdingMask * - fineThresholdingMask + thresholdingMask[:, :, z] = (coarseThresholdingMask * + fineThresholdingMask) thresholdingMask[:, :, z] = binary_fill_holes( thresholdingMask[:, :, z]) @@ -302,7 +302,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: fineHessianMask[:, :, z] = fineHessian == fineHessian.max() fineHessianMask[:, :, z] = binary_closing(fineHessianMask[:, :, z], selem.disk(5)) - fineHessianMask[:, :, z] = fineHessianMask[:, :, z]*borderMask + fineHessianMask[:, :, z] = fineHessianMask[:, :, z] * borderMask fineHessianMask[:, :, z] = binary_fill_holes( fineHessianMask[:, :, z]) @@ -315,7 +315,8 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() coarseHessianMask[:, :, z] = binary_closing( coarseHessianMask[:, :, z], selem.disk(5)) - coarseHessianMask[:, :, z] = coarseHessianMask[:, :, z]*borderMask + coarseHessianMask[:, :, z] = (coarseHessianMask[:, :, z] * + borderMask) coarseHessianMask[:, :, z] = binary_fill_holes( coarseHessianMask[:, :, z]) @@ -334,19 +335,19 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, background = sm.dilation(nucleiMask[:, :, z], sm.selem.disk(15)) membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), sm.selem.disk(10)) - foreground = sm.erosion(nucleiMask[:, :, z]*~membraneDilated, + foreground = sm.erosion(nucleiMask[:, :, z] *~ membraneDilated, sm.selem.disk(5)) - unknown = background*~foreground + unknown = background *~ foreground - background = np.uint8(background)*255 - foreground = np.uint8(foreground)*255 - unknown = np.uint8(unknown)*255 + background = np.uint8(background) * 255 + foreground = np.uint8(foreground) * 255 + unknown = np.uint8(unknown) * 255 # Marker labelling ret, markers = cv2.connectedComponents(foreground) # Add one to all labels so that sure background is not 0, but 1 - markers = markers+100 + markers = markers + 100 # Now, mark the region of unknown with zero markers[unknown == 255] = 0 From c6a39b09e08e252c95b2e75fbe52090d111deb6e Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 13:52:09 -0500 Subject: [PATCH 152/419] fixing invalid syntax --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 194eea48..bc497061 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -422,8 +422,8 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, index), reverse=True)) - if n0OverlapFraction[indexSorted[0]] > 0.2 and - n1OverlapFraction[indexSorted[0]] > 0.5: + if (n0OverlapFraction[indexSorted[0]] > 0.2 and + n1OverlapFraction[indexSorted[0]] > 0.5): return m1NucleiIndexes[indexSorted[0]], n0OverlapFraction[indexSorted[0]], n1OverlapFraction[indexSorted[0]] From e013485a387ef4b87dbeaa100a18731e25f65ed3 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 13:56:13 -0500 Subject: [PATCH 153/419] fixing invalid syntax --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index bc497061..c3c32c90 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -432,8 +432,8 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, else: return False, False, False - def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) - -> np.ndarray: + def _combine_watershed_z_positions(self, + watershedOutput: np.ndarray) -> np.ndarray: # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes From 135e24ce874ee17ce070bb6a93f836f07c1af874 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 14:00:18 -0500 Subject: [PATCH 154/419] fixing invalid syntax --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index c3c32c90..fdc242eb 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -452,8 +452,8 @@ def _combine_watershed_z_positions(self, n1,f0,f1 = _get_overlapping_nuclei(watershedCombinedZ[:, :, z], watershedOutput[:, :, z-1], n0) if n1: - watershedCombinedZ[:, :, z-1][watershedOutput[:, :, z-1] == n1] - = n0 + watershedCombinedZ[:, :, z-1][(watershedOutput[:, :, z-1] == + n1)] = n0 return watershedCombinedZ class CleanCellBoundaries(analysistask.ParallelAnalysisTask): From d7748d692803ea400a83c771ddf1cb2497b3de40 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 14:10:20 -0500 Subject: [PATCH 155/419] pep8 compliance --- merlin/analysis/segment.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index fdc242eb..4b7feae2 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -281,7 +281,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: offset=0)) fineThresholdingMask = (imageStack[:, :, z] > threshold_local(imageStack[:, :, z], - fineBlockSize, + fineBlockSize, offset=0)) thresholdingMask[:, :, z] = (coarseThresholdingMask * fineThresholdingMask) @@ -315,7 +315,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() coarseHessianMask[:, :, z] = binary_closing( coarseHessianMask[:, :, z], selem.disk(5)) - coarseHessianMask[:, :, z] = (coarseHessianMask[:, :, z] * + coarseHessianMask[:, :, z] = (coarseHessianMask[:, :, z] * borderMask) coarseHessianMask[:, :, z] = binary_fill_holes( coarseHessianMask[:, :, z]) @@ -335,9 +335,9 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, background = sm.dilation(nucleiMask[:, :, z], sm.selem.disk(15)) membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), sm.selem.disk(10)) - foreground = sm.erosion(nucleiMask[:, :, z] *~ membraneDilated, + foreground = sm.erosion(nucleiMask[:, :, z] * ~ membraneDilated, sm.selem.disk(5)) - unknown = background *~ foreground + unknown = background * ~ foreground background = np.uint8(background) * 255 foreground = np.uint8(foreground) * 255 @@ -433,7 +433,8 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, return False, False, False def _combine_watershed_z_positions(self, - watershedOutput: np.ndarray) -> np.ndarray: + watershedOutput: np.ndarray) -> np.ndarray: + # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes @@ -448,11 +449,12 @@ def _combine_watershed_z_positions(self, zNucleiIndex = np.unique(watershedOutput[:, :, z])[ np.unique(watershedOutput[:, :, z]) > 100] - for n0 in zNucleiIndex: # for each nuclei N(Z) in Z - n1,f0,f1 = _get_overlapping_nuclei(watershedCombinedZ[:, :, z], - watershedOutput[:, :, z-1], n0) + for n0 in zNucleiIndex: + n1, f0, f1 = _get_overlapping_nuclei(watershedCombinedZ[:, :, z], + watershedOutput[:, :, z-1], + n0) if n1: - watershedCombinedZ[:, :, z-1][(watershedOutput[:, :, z-1] == + watershedCombinedZ[:, :, z-1][(watershedOutput[:, :, z-1] == n1)] = n0 return watershedCombinedZ From 898ca767ee2e5f9c95a41a2d19640c16779efd6f Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 14:12:19 -0500 Subject: [PATCH 156/419] pep8 compliance --- merlin/analysis/segment.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 4b7feae2..90c9aaa4 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -433,7 +433,8 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, return False, False, False def _combine_watershed_z_positions(self, - watershedOutput: np.ndarray) -> np.ndarray: + watershedOutput: + np.ndarray) ->np.ndarray: # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes From ec25d4dddc90c8faf7a77375e24e2477e095ce54 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 14:14:32 -0500 Subject: [PATCH 157/419] pep8 compliance --- merlin/analysis/segment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 90c9aaa4..150834b6 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -434,7 +434,7 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, def _combine_watershed_z_positions(self, watershedOutput: - np.ndarray) ->np.ndarray: + np.ndarray) -> np.ndarray: # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes From 50d8b5289fc83f91f496e3da97425477d9917c4e Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 14:49:52 -0500 Subject: [PATCH 158/419] added _read_and_filter_image_stack to WatershedSegmentNucleiCV2 --- merlin/analysis/segment.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 150834b6..0a8cd45c 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -220,6 +220,16 @@ def _run_analysis(self, fragmentIndex): featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) + def _read_and_filter_image_stack(self, fov: int, channelIndex: int, + filterSigma: float) -> np.ndarray: + filterSize = int(2*np.ceil(2*filterSigma)+1) + warpTask = self.dataSet.load_analysis_task( + self.parameters['warp_task']) + return np.array([cv2.GaussianBlur( + warpTask.get_aligned_image(fov, channelIndex, z), + (filterSize, filterSize), filterSigma) + for z in range(len(self.dataSet.get_z_positions()))]) + def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) From 677d747c8ab2fd2eddd25ee764a3715b7005f420 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 16:38:22 -0500 Subject: [PATCH 159/419] reorganized Watershed...CV2 to remove repeated method calls --- merlin/analysis/segment.py | 156 +++++++++++++------------------------ 1 file changed, 53 insertions(+), 103 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 0a8cd45c..fa4e47a1 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -146,10 +146,10 @@ class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): def __init__(self, dataSet, parameters=None, analysisName=None): super().__init__(dataSet, parameters, analysisName) - if 'seed_channel_name' not in self.parameters: - self.parameters['seed_channel_name'] = 'WGA' - if 'watershed_channel_name' not in self.parameters: - self.parameters['watershed_channel_name'] = 'DAPI' + if 'membrane_channel_name' not in self.parameters: + self.parameters['membrane_channel_name'] = 'ConA' + if 'nuclei_channel_name' not in self.parameters: + self.parameters['nucleichannel_name'] = 'DAPI' def fragment_count(self): return len(self.dataSet.get_fovs()) @@ -174,123 +174,75 @@ def _run_analysis(self, fragmentIndex): globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) - seedIndex = self.dataSet.get_data_organization().get_data_channel_index( - self.parameters['seed_channel_name']) - seedImages = self._read_and_filter_image_stack(fragmentIndex, - seedIndex, 5) - - watershedIndex = self.dataSet.get_data_organization() \ - .get_data_channel_index(self.parameters['watershed_channel_name']) + # read membrane (seed) and nuclei (watershed) indexes + membraneIndex = self.dataSet.get_data_organization(). + get_data_channel_index(self.parameters['membrane_channel_name']) + nucleiIndex = self.dataSet.get_data_organization(). + get_data_channel_index(self.parameters['nuclei_channel_name']) + + # read membrane (seed) and nuclei (watershed) images + membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) + nucleiImages = self._read_image_stack(fragmentIndex, nucleiIndex) # Prepare masks for cv2 watershed - membraneMask = self._get_membrane_mask(fragmentIndex, watershedIndex) - nucleiMask = self._get_nuclei_mask(fragmentIndex, watershedIndex) - watershedMarkers = self._get_watershed_markers(nucleiMask, - membraneMask) + watershedMarkers = self._get_watershed_markers(nucleiImages, + membraneImages) # perform watershed in individual z positions - watershedOutput = self._apply_watershed(fragmentIndex, watershedIndex, + watershedOutput = self._apply_watershed(nucleiImages, watershedMarkers) # combine all z positions in watershed watershedCombinedOutput = self._combine_watershed_z_positions( watershedOutput) - """ - watershedImages = self._read_and_filter_image_stack(fragmentIndex, - watershedIndex, 5) - seeds = watershed.separate_merged_seeds( - watershed.extract_seeds(seedImages)) - normalizedWatershed, watershedMask = - watershed.prepare_watershed_images( - watershedImages) - - seeds[np.invert(watershedMask)] = 0 - watershedOutput = segmentation.watershed( - normalizedWatershed, measure.label(seeds), mask=watershedMask, - connectivity=np.ones((3, 3, 3)), watershed_line=True) - """ - zPos = np.array(self.dataSet.get_data_organization().get_z_positions()) featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( - (watershedOutput == i), fragmentIndex, + (watershedCombinedOutput == i), fragmentIndex, globalTask.fov_to_global_transform(fragmentIndex), zPos) for i in np.unique(watershedOutput) if i != 0] featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) - def _read_and_filter_image_stack(self, fov: int, channelIndex: int, - filterSigma: float) -> np.ndarray: - filterSize = int(2*np.ceil(2*filterSigma)+1) + def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) - return np.array([cv2.GaussianBlur( - warpTask.get_aligned_image(fov, channelIndex, z), - (filterSize, filterSize), filterSigma) - for z in range(len(self.dataSet.get_z_positions()))]) - - def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: - warpTask = self.dataSet.load_analysis_task( - self.parameters['warp_task']) - - imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet. - get_z_positions()))]) - # generate mask based on edge detection - """ - edgeMask = np.zeros(imageStack.shape) - for z in range(len(self.dataSet.get_z_positions())): - edgeMask[:, :, z] = canny( - white_tophat(imageStack[:, :, z], selem.disk(10)), - sigma=2, use_quantiles=True, - low_threshold=0.5, high_threshold=0.8) - edgeMask[:, :, z] = binary_closing(edgeMask[:, :, z],selem.disk(5)) - edgeMask[:, :, z] = remove_small_objects( - edgeMask[:, :, z].astype('bool'), - min_size=100, connectivity=1) - edgeMask[:, :, z] = skeletonize(edgeMask[:, :, z]) - """ + return np.array([warpTask.get_aligned_image(fov, channelIndex, z) + for z in range(len(self.dataSet.get_z_positions()))]) + def _get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: # generate mask based on thresholding - tresholdingMask = np.zeros(imageStack.shape) + mask = np.zeros(membraneImages.shape) fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - tresholdingMask[:, :, z] = (imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], - fineBlockSize, - offset=0)) - tresholdingMask[:, :, z] = remove_small_objects( - imageStack[:, :, z].astype('bool'), min_size=100, - connectivity=1) - tresholdingMask[:, :, z] = binary_closing(imageStack[:, :, z], + mask[:, :, z] = (membraneImages[:, :, z] > + threshold_local(membraneImages[:, :, z], + fineBlockSize, + offset=0)) + mask[:, :, z] = remove_small_objects(membraneImages[:, :, z]. + astype('bool'), + min_size=100, + connectivity=1) + mask[:, :, z] = binary_closing(membraneImages[:, :, z], selem.disk(5)) - tresholdingMask[:, :, z] = skeletonize(imageStack[:, :, z]) + mask[:, :, z] = skeletonize(membraneImages[:, :, z]) # combine masks - # return edgeMask + thresholdingMask - return thresholdingMask - - def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: - - warpTask = self.dataSet.load_analysis_task( - self.parameters['warp_task']) - - imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet. - get_z_positions()))]) + return mask + def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: # generate nuclei mask based on thresholding - thresholdingMask = np.zeros(imageStack.shape) + thresholdingMask = np.zeros(nucleiImages.shape) coarseBlockSize = 241 fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = (imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], + coarseThresholdingMask = (nucleiImages[:, :, z] > + threshold_local(nucleiImages[:, :, z], coarseBlockSize, offset=0)) - fineThresholdingMask = (imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], + fineThresholdingMask = (nucleiImages[:, :, z] > + threshold_local(nucleiImages[:, :, z], fineBlockSize, offset=0)) thresholdingMask[:, :, z] = (coarseThresholdingMask * @@ -306,21 +258,21 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: # TODO - use the image size variable for borderMask # generate nuclei mask from hessian, fine - fineHessianMask = np.zeros(imageStack.shape) + fineHessianMask = np.zeros(nucleiImages.shape) for z in range(len(self.dataSet.get_z_positions())): - fineHessian = hessian(imageStack[:, :, z]) + fineHessian = hessian(nucleiImages[:, :, z]) fineHessianMask[:, :, z] = fineHessian == fineHessian.max() fineHessianMask[:, :, z] = binary_closing(fineHessianMask[:, :, z], selem.disk(5)) fineHessianMask[:, :, z] = fineHessianMask[:, :, z] * borderMask fineHessianMask[:, :, z] = binary_fill_holes( - fineHessianMask[:, :, z]) + fineHessianMask[:, :, z]) # generate dapi mask from hessian, coarse - coarseHessianMask = np.zeros(imageStack.shape) + coarseHessianMask = np.zeros(nucleiImages.shape) for z in range(len(self.dataSet.get_z_positions())): - coarseHessian = hessian(imageStack[:, :, z] - - white_tophat(imageStack[:, :, z], + coarseHessian = hessian(nucleiImages[:, :, z] - + white_tophat(nucleiImages[:, :, z], selem.disk(20))) coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() coarseHessianMask[:, :, z] = binary_closing( @@ -334,8 +286,12 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask return binary_fill_holes(nucleiMask) - def _get_watershed_markers(self, nucleiMask: np.ndarray, - membraneMask: np.ndarray) -> np.ndarray: + def _get_watershed_markers(self, nucleiImages: np.ndarray, + membraneImages: np.ndarray) -> np.ndarray: + + nucleiMask = self._get_nuclei_mask(nucleiImages) + membraneMask = self._get_membrane_mask(membraneImages) + watershedMarker = np.zeros(nucleiMask.shape) for z in range(len(self.dataSet.get_z_positions())): @@ -387,18 +343,12 @@ def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: return rgbImage - def _apply_watershed(self, fov: int, channelIndex: int, + def _apply_watershed(self, nucleiImages: np.ndarray, watershedMarkers: np.ndarray) -> np.ndarray: - warpTask = self.dataSet.load_analysis_task( - self.parameters['warp_task']) - - imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet. - get_z_positions()))]) watershedOutput = np.zeros(watershedMarkers.shape) for z in range(len(self.dataSet.get_z_positions())): - rgbImage = _convert_grayscale_to_rgb(dapiStack[:, :, z]) + rgbImage = _convert_grayscale_to_rgb(nucleiImages[:, :, z]) watershedOutput[:, :, z] = cv2.watershed(rgbImage, watershedMarkers[:, :, z]. astype('int32')) From 4af4c77d4af53522fdf85d822bca84f67738dffc Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 16:44:01 -0500 Subject: [PATCH 160/419] cleaned watershed --- merlin/analysis/segment.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index fa4e47a1..cf59a553 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -352,6 +352,8 @@ def _apply_watershed(self, nucleiImages: np.ndarray, watershedOutput[:, :, z] = cv2.watershed(rgbImage, watershedMarkers[:, :, z]. astype('int32')) + watershedOutput[:, :, z][watershedOutput[:, :, z] <= 100] = 0 + return watershedOutput def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, From a1ec19141e5d1f0dd759074220c06db0c26e2d3c Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 16:47:37 -0500 Subject: [PATCH 161/419] pep8 compliance --- merlin/analysis/segment.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index cf59a553..c2eef180 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -176,9 +176,11 @@ def _run_analysis(self, fragmentIndex): # read membrane (seed) and nuclei (watershed) indexes membraneIndex = self.dataSet.get_data_organization(). - get_data_channel_index(self.parameters['membrane_channel_name']) + get_data_channel_index( + self.parameters['m brane_channel_name']) nucleiIndex = self.dataSet.get_data_organization(). - get_data_channel_index(self.parameters['nuclei_channel_name']) + get_data_channel_index( + self.parameters['nuclei_channel_name']) # read membrane (seed) and nuclei (watershed) images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) From ddfc6d8171f129fb871ef6410999523a56f9f3a9 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 16:49:19 -0500 Subject: [PATCH 162/419] pep8 compliance --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index c2eef180..ff545a84 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -176,8 +176,8 @@ def _run_analysis(self, fragmentIndex): # read membrane (seed) and nuclei (watershed) indexes membraneIndex = self.dataSet.get_data_organization(). - get_data_channel_index( - self.parameters['m brane_channel_name']) + get_data_channel_index( + self.parameters['membrane_channel_name']) nucleiIndex = self.dataSet.get_data_organization(). get_data_channel_index( self.parameters['nuclei_channel_name']) From e1d2a0d53674f42f4cdb118254aada7d59642188 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 16:54:08 -0500 Subject: [PATCH 163/419] pep8 compliance --- merlin/analysis/segment.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index ff545a84..5505b8f8 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -175,12 +175,12 @@ def _run_analysis(self, fragmentIndex): self.parameters['global_align_task']) # read membrane (seed) and nuclei (watershed) indexes - membraneIndex = self.dataSet.get_data_organization(). - get_data_channel_index( - self.parameters['membrane_channel_name']) + membraneIndex = self.dataSet \ + .get_data_organization() \ + .get_data_channel_index( + self.parameters['membrane_channel_name']) nucleiIndex = self.dataSet.get_data_organization(). - get_data_channel_index( - self.parameters['nuclei_channel_name']) + get_data_channel_index(self.parameters['nuclei_channel_name']) # read membrane (seed) and nuclei (watershed) images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) From eb46c9c727e5812c2743fa521e92a0eda03cf6d9 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 16:55:47 -0500 Subject: [PATCH 164/419] pep8 compliance --- merlin/analysis/segment.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 5505b8f8..6d74df95 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -175,13 +175,11 @@ def _run_analysis(self, fragmentIndex): self.parameters['global_align_task']) # read membrane (seed) and nuclei (watershed) indexes - membraneIndex = self.dataSet \ - .get_data_organization() \ - .get_data_channel_index( - self.parameters['membrane_channel_name']) + membraneIndex = self.dataSet.get_data_organization(). + get_data_channel_index(self.parameters['membrane_channel_name']) nucleiIndex = self.dataSet.get_data_organization(). get_data_channel_index(self.parameters['nuclei_channel_name']) - + # read membrane (seed) and nuclei (watershed) images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) nucleiImages = self._read_image_stack(fragmentIndex, nucleiIndex) From aff9c23d7225cf5a4cf536d549875571bc55f06d Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 16:58:17 -0500 Subject: [PATCH 165/419] pep8 compliance --- merlin/analysis/segment.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 6d74df95..40bbf3ed 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -180,7 +180,7 @@ def _run_analysis(self, fragmentIndex): nucleiIndex = self.dataSet.get_data_organization(). get_data_channel_index(self.parameters['nuclei_channel_name']) - # read membrane (seed) and nuclei (watershed) images + # read membrane (seed) and nuclei (watershed) images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) nucleiImages = self._read_image_stack(fragmentIndex, nucleiIndex) @@ -221,11 +221,11 @@ def _get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: fineBlockSize, offset=0)) mask[:, :, z] = remove_small_objects(membraneImages[:, :, z]. - astype('bool'), - min_size=100, - connectivity=1) + astype('bool'), + min_size=100, + connectivity=1) mask[:, :, z] = binary_closing(membraneImages[:, :, z], - selem.disk(5)) + selem.disk(5)) mask[:, :, z] = skeletonize(membraneImages[:, :, z]) # combine masks @@ -288,10 +288,10 @@ def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: def _get_watershed_markers(self, nucleiImages: np.ndarray, membraneImages: np.ndarray) -> np.ndarray: - + nucleiMask = self._get_nuclei_mask(nucleiImages) membraneMask = self._get_membrane_mask(membraneImages) - + watershedMarker = np.zeros(nucleiMask.shape) for z in range(len(self.dataSet.get_z_positions())): @@ -352,8 +352,8 @@ def _apply_watershed(self, nucleiImages: np.ndarray, watershedOutput[:, :, z] = cv2.watershed(rgbImage, watershedMarkers[:, :, z]. astype('int32')) - watershedOutput[:, :, z][watershedOutput[:, :, z] <= 100] = 0 - + watershedOutput[:, :, z][watershedOutput[:, :, z] <= 100] = 0 + return watershedOutput def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, From fe7f84717826157d73d97ad6f34e985503849ba0 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 17:05:54 -0500 Subject: [PATCH 166/419] fixing invalid syntax --- merlin/analysis/segment.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 40bbf3ed..3aa46d72 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -175,10 +175,14 @@ def _run_analysis(self, fragmentIndex): self.parameters['global_align_task']) # read membrane (seed) and nuclei (watershed) indexes - membraneIndex = self.dataSet.get_data_organization(). - get_data_channel_index(self.parameters['membrane_channel_name']) - nucleiIndex = self.dataSet.get_data_organization(). - get_data_channel_index(self.parameters['nuclei_channel_name']) + membraneIndex = self.dataSet \ + .get_data_organization() \ + .get_data_channel_index( + self.parameters['membrane_channel_name']) + nucleiIndex = self.dataSet \ + .get_data_organization() \ + .get_data_channel_index( + self.parameters['nuclei_channel_name']) # read membrane (seed) and nuclei (watershed) images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) From a195fcca97ed9ea3c16a7e4883ece19131827058 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 17:53:10 -0500 Subject: [PATCH 167/419] correct skimage function names --- merlin/analysis/segment.py | 59 +++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 3aa46d72..4a7f5b36 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -221,16 +221,16 @@ def _get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): mask[:, :, z] = (membraneImages[:, :, z] > - threshold_local(membraneImages[:, :, z], + filters.threshold_local(membraneImages[:, :, z], fineBlockSize, offset=0)) - mask[:, :, z] = remove_small_objects(membraneImages[:, :, z]. - astype('bool'), - min_size=100, - connectivity=1) - mask[:, :, z] = binary_closing(membraneImages[:, :, z], - selem.disk(5)) - mask[:, :, z] = skeletonize(membraneImages[:, :, z]) + mask[:, :, z] = morphology.remove_small_objects( + membraneImages[:, :, z].astype('bool'), + min_size=100, + connectivity=1) + mask[:, :, z] = morphology.binary_closing(membraneImages[:, :, z], + morphology.selem.disk(5)) + mask[:, :, z] = morphology.skeletonize(membraneImages[:, :, z]) # combine masks return mask @@ -242,13 +242,15 @@ def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): coarseThresholdingMask = (nucleiImages[:, :, z] > - threshold_local(nucleiImages[:, :, z], - coarseBlockSize, - offset=0)) + filters.threshold_local( + nucleiImages[:, :, z], + coarseBlockSize, + offset=0)) fineThresholdingMask = (nucleiImages[:, :, z] > - threshold_local(nucleiImages[:, :, z], - fineBlockSize, - offset=0)) + filters.threshold_local( + nucleiImages[:, :, z], + fineBlockSize, + offset=0)) thresholdingMask[:, :, z] = (coarseThresholdingMask * fineThresholdingMask) thresholdingMask[:, :, z] = binary_fill_holes( @@ -266,8 +268,9 @@ def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: for z in range(len(self.dataSet.get_z_positions())): fineHessian = hessian(nucleiImages[:, :, z]) fineHessianMask[:, :, z] = fineHessian == fineHessian.max() - fineHessianMask[:, :, z] = binary_closing(fineHessianMask[:, :, z], - selem.disk(5)) + fineHessianMask[:, :, z] = morphology.binary_closing( + fineHessianMask[:, :, z], + morphology.selem.disk(5)) fineHessianMask[:, :, z] = fineHessianMask[:, :, z] * borderMask fineHessianMask[:, :, z] = binary_fill_holes( fineHessianMask[:, :, z]) @@ -275,12 +278,13 @@ def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: # generate dapi mask from hessian, coarse coarseHessianMask = np.zeros(nucleiImages.shape) for z in range(len(self.dataSet.get_z_positions())): - coarseHessian = hessian(nucleiImages[:, :, z] - - white_tophat(nucleiImages[:, :, z], - selem.disk(20))) + coarseHessian = filters.hessian(nucleiImages[:, :, z] - + morphology.white_tophat( + nucleiImages[:, :, z], + morphology.selem.disk(20))) coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() - coarseHessianMask[:, :, z] = binary_closing( - coarseHessianMask[:, :, z], selem.disk(5)) + coarseHessianMask[:, :, z] = morphology.binary_closing( + coarseHessianMask[:, :, z], morphology.selem.disk(5)) coarseHessianMask[:, :, z] = (coarseHessianMask[:, :, z] * borderMask) coarseHessianMask[:, :, z] = binary_fill_holes( @@ -302,11 +306,14 @@ def _get_watershed_markers(self, nucleiImages: np.ndarray, # generate areas of sure bg and fg, as well as the area of # unknown classification - background = sm.dilation(nucleiMask[:, :, z], sm.selem.disk(15)) - membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), - sm.selem.disk(10)) - foreground = sm.erosion(nucleiMask[:, :, z] * ~ membraneDilated, - sm.selem.disk(5)) + background = morphology.dilation(nucleiMask[:, :, z], + morphology.selem.disk(15)) + membraneDilated = morphology.dilation( + membraneMask[:, :, z].astype('bool'), + morphology.selem.disk(10)) + foreground = morphology.erosion(nucleiMask[:, :, z] * ~ + membraneDilated, + morphology.selem.disk(5)) unknown = background * ~ foreground background = np.uint8(background) * 255 From 25f14ec5e4744095e2bf22321eb1166d78338712 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 17:55:00 -0500 Subject: [PATCH 168/419] pep8 compliance --- merlin/analysis/segment.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 4a7f5b36..80e732b4 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -222,14 +222,14 @@ def _get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: for z in range(len(self.dataSet.get_z_positions())): mask[:, :, z] = (membraneImages[:, :, z] > filters.threshold_local(membraneImages[:, :, z], - fineBlockSize, - offset=0)) + fineBlockSize, + offset=0)) mask[:, :, z] = morphology.remove_small_objects( membraneImages[:, :, z].astype('bool'), min_size=100, connectivity=1) mask[:, :, z] = morphology.binary_closing(membraneImages[:, :, z], - morphology.selem.disk(5)) + morphology.selem.disk(5)) mask[:, :, z] = morphology.skeletonize(membraneImages[:, :, z]) # combine masks @@ -279,7 +279,7 @@ def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: coarseHessianMask = np.zeros(nucleiImages.shape) for z in range(len(self.dataSet.get_z_positions())): coarseHessian = filters.hessian(nucleiImages[:, :, z] - - morphology.white_tophat( + morphology.white_tophat( nucleiImages[:, :, z], morphology.selem.disk(20))) coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() From 929a8b67b8403ed243164c2fc652821a3b62068d Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 18:06:02 -0500 Subject: [PATCH 169/419] correct skimage function names --- merlin/analysis/segment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 80e732b4..5ca586c1 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -266,7 +266,7 @@ def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: # generate nuclei mask from hessian, fine fineHessianMask = np.zeros(nucleiImages.shape) for z in range(len(self.dataSet.get_z_positions())): - fineHessian = hessian(nucleiImages[:, :, z]) + fineHessian = filters.hessian(nucleiImages[:, :, z]) fineHessianMask[:, :, z] = fineHessian == fineHessian.max() fineHessianMask[:, :, z] = morphology.binary_closing( fineHessianMask[:, :, z], From a745ffcc886d917efd4eef5846bd521b2c3e96ed Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sun, 26 Jan 2020 09:04:09 -0500 Subject: [PATCH 170/419] changed Image dimension order to fit MERlin's --- merlin/analysis/segment.py | 89 ++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 43 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 5ca586c1..b88ab8e4 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -149,7 +149,7 @@ def __init__(self, dataSet, parameters=None, analysisName=None): if 'membrane_channel_name' not in self.parameters: self.parameters['membrane_channel_name'] = 'ConA' if 'nuclei_channel_name' not in self.parameters: - self.parameters['nucleichannel_name'] = 'DAPI' + self.parameters['nuclei_channel_name'] = 'DAPI' def fragment_count(self): return len(self.dataSet.get_fovs()) @@ -188,6 +188,9 @@ def _run_analysis(self, fragmentIndex): membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) nucleiImages = self._read_image_stack(fragmentIndex, nucleiIndex) + print('membraneImages = ' + str(membraneImages.shape)) + print('nucleiImages = ' + str(nucleiImages.shape)) + # Prepare masks for cv2 watershed watershedMarkers = self._get_watershed_markers(nucleiImages, membraneImages) @@ -220,17 +223,17 @@ def _get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: mask = np.zeros(membraneImages.shape) fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - mask[:, :, z] = (membraneImages[:, :, z] > - filters.threshold_local(membraneImages[:, :, z], + mask[z, :, :] = (membraneImages[z, :, :] > + filters.threshold_local(membraneImages[z, :, :], fineBlockSize, offset=0)) - mask[:, :, z] = morphology.remove_small_objects( - membraneImages[:, :, z].astype('bool'), + mask[z, :, :] = morphology.remove_small_objects( + membraneImages[z, :, :].astype('bool'), min_size=100, connectivity=1) - mask[:, :, z] = morphology.binary_closing(membraneImages[:, :, z], + mask[z, :, :] = morphology.binary_closing(membraneImages[z, :, :], morphology.selem.disk(5)) - mask[:, :, z] = morphology.skeletonize(membraneImages[:, :, z]) + mask[z, :, :] = morphology.skeletonize(membraneImages[z, :, :]) # combine masks return mask @@ -241,20 +244,20 @@ def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: coarseBlockSize = 241 fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = (nucleiImages[:, :, z] > + coarseThresholdingMask = (nucleiImages[z, :, :] > filters.threshold_local( - nucleiImages[:, :, z], + nucleiImages[z, :, :], coarseBlockSize, offset=0)) - fineThresholdingMask = (nucleiImages[:, :, z] > + fineThresholdingMask = (nucleiImages[z, :, :] > filters.threshold_local( - nucleiImages[:, :, z], + nucleiImages[z, :, :], fineBlockSize, offset=0)) - thresholdingMask[:, :, z] = (coarseThresholdingMask * + thresholdingMask[z, :, :] = (coarseThresholdingMask * fineThresholdingMask) - thresholdingMask[:, :, z] = binary_fill_holes( - thresholdingMask[:, :, z]) + thresholdingMask[z, :, :] = binary_fill_holes( + thresholdingMask[z, :, :]) # generate border mask, necessary to avoid making a single # connected component when using binary_fill_holes below @@ -266,29 +269,29 @@ def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: # generate nuclei mask from hessian, fine fineHessianMask = np.zeros(nucleiImages.shape) for z in range(len(self.dataSet.get_z_positions())): - fineHessian = filters.hessian(nucleiImages[:, :, z]) - fineHessianMask[:, :, z] = fineHessian == fineHessian.max() - fineHessianMask[:, :, z] = morphology.binary_closing( - fineHessianMask[:, :, z], + fineHessian = filters.hessian(nucleiImages[z, :, :]) + fineHessianMask[z, :, :] = fineHessian == fineHessian.max() + fineHessianMask[z, :, :] = morphology.binary_closing( + fineHessianMask[z, :, :], morphology.selem.disk(5)) - fineHessianMask[:, :, z] = fineHessianMask[:, :, z] * borderMask - fineHessianMask[:, :, z] = binary_fill_holes( - fineHessianMask[:, :, z]) + fineHessianMask[z, :, :] = fineHessianMask[z, :, :] * borderMask + fineHessianMask[z, :, :] = binary_fill_holes( + fineHessianMask[z, :, :]) # generate dapi mask from hessian, coarse coarseHessianMask = np.zeros(nucleiImages.shape) for z in range(len(self.dataSet.get_z_positions())): - coarseHessian = filters.hessian(nucleiImages[:, :, z] - + coarseHessian = filters.hessian(nucleiImages[z, :, :] - morphology.white_tophat( - nucleiImages[:, :, z], + nucleiImages[z, :, :], morphology.selem.disk(20))) - coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() - coarseHessianMask[:, :, z] = morphology.binary_closing( - coarseHessianMask[:, :, z], morphology.selem.disk(5)) - coarseHessianMask[:, :, z] = (coarseHessianMask[:, :, z] * + coarseHessianMask[z, :, :] = coarseHessian == coarseHessian.max() + coarseHessianMask[z, :, :] = morphology.binary_closing( + coarseHessianMask[z, :, :], morphology.selem.disk(5)) + coarseHessianMask[z, :, :] = (coarseHessianMask[z, :, :] * borderMask) - coarseHessianMask[:, :, z] = binary_fill_holes( - coarseHessianMask[:, :, z]) + coarseHessianMask[z, :, :] = binary_fill_holes( + coarseHessianMask[z, :, :]) # combine masks nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask @@ -306,12 +309,12 @@ def _get_watershed_markers(self, nucleiImages: np.ndarray, # generate areas of sure bg and fg, as well as the area of # unknown classification - background = morphology.dilation(nucleiMask[:, :, z], + background = morphology.dilation(nucleiMask[z, :, :], morphology.selem.disk(15)) membraneDilated = morphology.dilation( - membraneMask[:, :, z].astype('bool'), + membraneMask[z, :, :].astype('bool'), morphology.selem.disk(10)) - foreground = morphology.erosion(nucleiMask[:, :, z] * ~ + foreground = morphology.erosion(nucleiMask[z, :, :] * ~ membraneDilated, morphology.selem.disk(5)) unknown = background * ~ foreground @@ -329,7 +332,7 @@ def _get_watershed_markers(self, nucleiImages: np.ndarray, # Now, mark the region of unknown with zero markers[unknown == 255] = 0 - watershedMarker[:, :, z] = markers + watershedMarker[z, :, :] = markers return watershedMarker @@ -359,11 +362,11 @@ def _apply_watershed(self, nucleiImages: np.ndarray, watershedOutput = np.zeros(watershedMarkers.shape) for z in range(len(self.dataSet.get_z_positions())): - rgbImage = _convert_grayscale_to_rgb(nucleiImages[:, :, z]) - watershedOutput[:, :, z] = cv2.watershed(rgbImage, - watershedMarkers[:, :, z]. + rgbImage = _convert_grayscale_to_rgb(nucleiImages[z, :, :]) + watershedOutput[z, :, :] = cv2.watershed(rgbImage, + watershedMarkers[z, :, :]. astype('int32')) - watershedOutput[:, :, z][watershedOutput[:, :, z] <= 100] = 0 + watershedOutput[z, :, :][watershedOutput[z, :, :] <= 100] = 0 return watershedOutput @@ -416,19 +419,19 @@ def _combine_watershed_z_positions(self, watershedCombinedZ = np.zeros(watershedOutput.shape) # copy the mask of the section farthest to the coverslip - watershedCombinedZ[:, :, -1] = watershedOutput[:, :, -1] + watershedCombinedZ[-1, :, :] = watershedOutput[-1, :, :] # starting far from coverslip for z in range(len(self.dataSet.get_z_positions())-1, 0, -1): - zNucleiIndex = np.unique(watershedOutput[:, :, z])[ - np.unique(watershedOutput[:, :, z]) > 100] + zNucleiIndex = np.unique(watershedOutput[z, :, :])[ + np.unique(watershedOutput[z, :, :]) > 100] for n0 in zNucleiIndex: - n1, f0, f1 = _get_overlapping_nuclei(watershedCombinedZ[:, :, z], - watershedOutput[:, :, z-1], + n1, f0, f1 = _get_overlapping_nuclei(watershedCombinedZ[z, :, :], + watershedOutput[z-1, :, :], n0) if n1: - watershedCombinedZ[:, :, z-1][(watershedOutput[:, :, z-1] == + watershedCombinedZ[z-1, :, :][(watershedOutput[z-1, :, :] == n1)] = n0 return watershedCombinedZ From 2580e3d320b2b6e6a0f4bf216356c7e0d83dc182 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sun, 26 Jan 2020 09:32:42 -0500 Subject: [PATCH 171/419] change variable name --- merlin/analysis/segment.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index b88ab8e4..27e4f64d 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -228,12 +228,12 @@ def _get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: fineBlockSize, offset=0)) mask[z, :, :] = morphology.remove_small_objects( - membraneImages[z, :, :].astype('bool'), + mask[z, :, :].astype('bool'), min_size=100, connectivity=1) - mask[z, :, :] = morphology.binary_closing(membraneImages[z, :, :], + mask[z, :, :] = morphology.binary_closing(mask[z, :, :], morphology.selem.disk(5)) - mask[z, :, :] = morphology.skeletonize(membraneImages[z, :, :]) + mask[z, :, :] = morphology.skeletonize(mask[z, :, :]) # combine masks return mask From ff03eee531179a93c6838af29c5c7a358a77a49a Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sun, 26 Jan 2020 09:51:46 -0500 Subject: [PATCH 172/419] added missing self. in method call --- merlin/analysis/segment.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 27e4f64d..aa183d92 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -362,7 +362,7 @@ def _apply_watershed(self, nucleiImages: np.ndarray, watershedOutput = np.zeros(watershedMarkers.shape) for z in range(len(self.dataSet.get_z_positions())): - rgbImage = _convert_grayscale_to_rgb(nucleiImages[z, :, :]) + rgbImage = self._convert_grayscale_to_rgb(nucleiImages[z, :, :]) watershedOutput[z, :, :] = cv2.watershed(rgbImage, watershedMarkers[z, :, :]. astype('int32')) @@ -427,9 +427,10 @@ def _combine_watershed_z_positions(self, np.unique(watershedOutput[z, :, :]) > 100] for n0 in zNucleiIndex: - n1, f0, f1 = _get_overlapping_nuclei(watershedCombinedZ[z, :, :], - watershedOutput[z-1, :, :], - n0) + n1, f0, f1 = self._get_overlapping_nuclei( + watershedCombinedZ[z, :, :], + watershedOutput[z-1, :, :], + n0) if n1: watershedCombinedZ[z-1, :, :][(watershedOutput[z-1, :, :] == n1)] = n0 From 80efa299c4c71168fc389481827045733ffd1b08 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sun, 26 Jan 2020 13:50:42 -0500 Subject: [PATCH 173/419] adding print statements for debugging --- merlin/analysis/segment.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index aa183d92..993a3bba 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -17,6 +17,7 @@ from merlin.util import watershed import pandas import networkx as nx +import time class FeatureSavingAnalysisTask(analysistask.ParallelAnalysisTask): @@ -171,9 +172,14 @@ def get_cell_boundaries(self) -> List[spatialfeature.SpatialFeature]: return featureDB.read_features() def _run_analysis(self, fragmentIndex): + startTime = time.time() + globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) + + print('reading indexes for fov ' + str(fragmentIndex) ) + # read membrane (seed) and nuclei (watershed) indexes membraneIndex = self.dataSet \ .get_data_organization() \ @@ -184,25 +190,42 @@ def _run_analysis(self, fragmentIndex): .get_data_channel_index( self.parameters['nuclei_channel_name']) + endTime = time.time() + print("Indexes read, ET {:.2f} min"\ + .format((endTime - startTime) / 60)) + # read membrane (seed) and nuclei (watershed) images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) nucleiImages = self._read_image_stack(fragmentIndex, nucleiIndex) - print('membraneImages = ' + str(membraneImages.shape)) - print('nucleiImages = ' + str(nucleiImages.shape)) + endTime = time.time() + print("Images read, ET {:.2f} min" \ + .format((endTime - startTime) / 60)) # Prepare masks for cv2 watershed watershedMarkers = self._get_watershed_markers(nucleiImages, membraneImages) + endTime = time.time() + print("Markers calculated, ET {:.2f} min" \ + .format((endTime - startTime) / 60)) + # perform watershed in individual z positions watershedOutput = self._apply_watershed(nucleiImages, watershedMarkers) + endTime = time.time() + print("watershed calculated, ET {:.2f} min" \ + .format((endTime - startTime) / 60)) + # combine all z positions in watershed watershedCombinedOutput = self._combine_watershed_z_positions( watershedOutput) + endTime = time.time() + print("watershed z positions combined, ET {:.2f} min" \ + .format((endTime - startTime) / 60)) + zPos = np.array(self.dataSet.get_data_organization().get_z_positions()) featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( (watershedCombinedOutput == i), fragmentIndex, @@ -212,6 +235,10 @@ def _run_analysis(self, fragmentIndex): featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) + endTime = time.time() + print("features written, ET {:.2f} min" \ + .format((endTime - startTime) / 60)) + def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) From 4cf60dbc6dc82ca6443366b4499c5302ecac5962 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 15:29:03 -0500 Subject: [PATCH 174/419] moving utility functions from segment.py to watershed.py --- merlin/analysis/segment.py | 2 + merlin/util/watershed.py | 233 +++++++++++++++++++++++++++++++++++++ 2 files changed, 235 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 993a3bba..7425eed6 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -226,6 +226,8 @@ def _run_analysis(self, fragmentIndex): print("watershed z positions combined, ET {:.2f} min" \ .format((endTime - startTime) / 60)) + # get features from mask. This is the slowestart (6 min for the + # previous part, 15+ for the rest, for a 7 frame Image. zPos = np.array(self.dataSet.get_data_organization().get_z_positions()) featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( (watershedCombinedOutput == i), fragmentIndex, diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 809dfc5b..3b5563f3 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -1,6 +1,7 @@ import numpy as np import cv2 from scipy import ndimage +from scipy.ndimage.morphology import binary_fill_holes from skimage import morphology from skimage import filters from skimage import measure @@ -139,3 +140,235 @@ def prepare_watershed_images(watershedImageStack: np.ndarray normalizedWatershed[np.invert(watershedMask)] = 1 return normalizedWatershed, watershedMask + +def get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: + """Calculate binary mask with 1's in membrane pixels and 0 otherwise. + The images expected are some type of membrane label (WGA, ConA, + Lamin, Cadherins) + + Args: + membraneImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) + """ + mask = np.zeros(membraneImages.shape) + fineBlockSize = 61 + for z in range(len(self.dataSet.get_z_positions())): + mask[z, :, :] = (membraneImages[z, :, :] > + filters.threshold_local(membraneImages[z, :, :], + fineBlockSize, + offset=0)) + mask[z, :, :] = morphology.remove_small_objects( + mask[z, :, :].astype('bool'), + min_size=100, + connectivity=1) + mask[z, :, :] = morphology.binary_closing(mask[z, :, :], + morphology.selem.disk(5)) + mask[z, :, :] = morphology.skeletonize(mask[z, :, :]) + + # combine masks + return mask + +def get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: + + # TO DO: Add description + + # generate nuclei mask based on thresholding + thresholdingMask = np.zeros(nucleiImages.shape) + coarseBlockSize = 241 + fineBlockSize = 61 + for z in range(len(self.dataSet.get_z_positions())): + coarseThresholdingMask = (nucleiImages[z, :, :] > + filters.threshold_local( + nucleiImages[z, :, :], + coarseBlockSize, + offset=0)) + fineThresholdingMask = (nucleiImages[z, :, :] > + filters.threshold_local( + nucleiImages[z, :, :], + fineBlockSize, + offset=0)) + thresholdingMask[z, :, :] = (coarseThresholdingMask * + fineThresholdingMask) + thresholdingMask[z, :, :] = binary_fill_holes( + thresholdingMask[z, :, :]) + + # generate border mask, necessary to avoid making a single + # connected component when using binary_fill_holes below + borderMask = np.zeros((2048, 2048)) + borderMask[25:2023, 25:2023] = 1 + + # TODO - use the image size variable for borderMask + + # generate nuclei mask from hessian, fine + fineHessianMask = np.zeros(nucleiImages.shape) + for z in range(len(self.dataSet.get_z_positions())): + fineHessian = filters.hessian(nucleiImages[z, :, :]) + fineHessianMask[z, :, :] = fineHessian == fineHessian.max() + fineHessianMask[z, :, :] = morphology.binary_closing( + fineHessianMask[z, :, :], + morphology.selem.disk(5)) + fineHessianMask[z, :, :] = fineHessianMask[z, :, :] * borderMask + fineHessianMask[z, :, :] = binary_fill_holes( + fineHessianMask[z, :, :]) + + # generate dapi mask from hessian, coarse + coarseHessianMask = np.zeros(nucleiImages.shape) + for z in range(len(self.dataSet.get_z_positions())): + coarseHessian = filters.hessian(nucleiImages[z, :, :] - + morphology.white_tophat( + nucleiImages[z, :, :], + morphology.selem.disk(20))) + coarseHessianMask[z, :, :] = coarseHessian == coarseHessian.max() + coarseHessianMask[z, :, :] = morphology.binary_closing( + coarseHessianMask[z, :, :], morphology.selem.disk(5)) + coarseHessianMask[z, :, :] = (coarseHessianMask[z, :, :] * + borderMask) + coarseHessianMask[z, :, :] = binary_fill_holes( + coarseHessianMask[z, :, :]) + + # combine masks + nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask + return binary_fill_holes(nucleiMask) + +def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, + membraneImages: np.ndarray) -> np.ndarray: + # TO DO: Add description + + nucleiMask = self.get_nuclei_mask(nucleiImages) + membraneMask = self.get_membrane_mask(membraneImages) + + watershedMarker = np.zeros(nucleiMask.shape) + + for z in range(len(self.dataSet.get_z_positions())): + + # generate areas of sure bg and fg, as well as the area of + # unknown classification + background = morphology.dilation(nucleiMask[z, :, :], + morphology.selem.disk(15)) + membraneDilated = morphology.dilation( + membraneMask[z, :, :].astype('bool'), + morphology.selem.disk(10)) + foreground = morphology.erosion(nucleiMask[z, :, :] * ~ + membraneDilated, + morphology.selem.disk(5)) + unknown = background * ~ foreground + + background = np.uint8(background) * 255 + foreground = np.uint8(foreground) * 255 + unknown = np.uint8(unknown) * 255 + + # Marker labelling + ret, markers = cv2.connectedComponents(foreground) + + # Add one to all labels so that sure background is not 0, but 1 + markers = markers + 100 + + # Now, mark the region of unknown with zero + markers[unknown == 255] = 0 + + watershedMarker[z, :, :] = markers + + return watershedMarker + +def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: + # cv2 only works in 3D images of 8bit. Make a 3D grayscale by + # using the same grayscale image in each of the rgb channels + # code below based on https://stackoverflow.com/questions/ + # 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv + + # invert image + uint16Image = 2**16 - uint16Image + + # convert to uint8 + ratio = np.amax(uint16Image) / 256 + uint8Image = (uint16Image / ratio).astype('uint8') + + rgbImage = np.zeros((2048, 2048, 3)) + rgbImage[:, :, 0] = uint8Image + rgbImage[:, :, 1] = uint8Image + rgbImage[:, :, 2] = uint8Image + rgbImage = rgbImage.astype('uint8') + + return rgbImage + +def apply_watershed(self, nucleiImages: np.ndarray, + watershedMarkers: np.ndarray) -> np.ndarray: + + watershedOutput = np.zeros(watershedMarkers.shape) + for z in range(len(self.dataSet.get_z_positions())): + rgbImage = self.convert_grayscale_to_rgb(nucleiImages[z, :, :]) + watershedOutput[z, :, :] = cv2.watershed(rgbImage, + watershedMarkers[z, :, :]. + astype('int32')) + watershedOutput[z, :, :][watershedOutput[z, :, :] <= 100] = 0 + + return watershedOutput + +def get_overlapping_nuclei(self, watershedZ0: np.ndarray, + watershedZ1: np.ndarray, n0: int): + z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) + z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes > 100] + + if z1NucleiIndexes.shape[0] > 0: + + # calculate overlap fraction + n0Area = np.count_nonzero(watershedZ0 == n0) + n1Area = np.zeros(len(z1NucleiIndexes)) + overlapArea = np.zeros(len(z1NucleiIndexes)) + + for ii in range(len(z1NucleiIndexes)): + n1 = z1NucleiIndexes[ii] + n1Area[ii] = np.count_nonzero(watershedZ1 == n1) + overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * + (watershedZ1 == n1)) + + n0OverlapFraction = np.asarray(overlapArea / n0Area) + n1OverlapFraction = np.asarray(overlapArea / n1Area) + index = list(range(len(n0OverlapFraction))) + + # select the nuclei that has the highest fraction in n0 and n1 + r1, r2, indexSorted = zip(*sorted(zip(n0OverlapFraction, + n1OverlapFraction, + index), + reverse=True)) + + if (n0OverlapFraction[indexSorted[0]] > 0.2 and + n1OverlapFraction[indexSorted[0]] > 0.5): + return m1NucleiIndexes[indexSorted[0]], + n0OverlapFraction[indexSorted[0]], + n1OverlapFraction[indexSorted[0]] + else: + return False, False, False + else: + return False, False, False + +def combine_2d_segmentation_masks_into_3d(self, + watershedOutput: + np.ndarray) -> np.ndarray: + # TO DO: Add description + + # TO DO: this implementation is very rough, needs to be improved. + # good just for testing purposes + + # Initialize empty array with size as watershedOutput array + watershedCombinedZ = np.zeros(watershedOutput.shape) + + # copy the mask of the section farthest to the coverslip + watershedCombinedZ[-1, :, :] = watershedOutput[-1, :, :] + + # starting far from coverslip + for z in range(len(self.dataSet.get_z_positions())-1, 0, -1): + zNucleiIndex = np.unique(watershedOutput[z, :, :])[ + np.unique(watershedOutput[z, :, :]) > 100] + + for n0 in zNucleiIndex: + n1, f0, f1 = self.get_overlapping_nuclei( + watershedCombinedZ[z, :, :], + watershedOutput[z-1, :, :], + n0) + if n1: + watershedCombinedZ[z-1, :, :][(watershedOutput[z-1, :, :] == + n1)] = n0 + return watershedCombinedZ From dafa7b122524593b7bca6bd7b96588880a1b4b4a Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 15:30:14 -0500 Subject: [PATCH 175/419] removing utility functions from segment.py --- merlin/analysis/segment.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 7425eed6..8bcaec6f 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -9,7 +9,6 @@ from shapely import geometry from typing import List, Dict from scipy.spatial import cKDTree -from scipy.ndimage.morphology import binary_fill_holes from merlin.core import dataset from merlin.core import analysistask @@ -174,11 +173,13 @@ def get_cell_boundaries(self) -> List[spatialfeature.SpatialFeature]: def _run_analysis(self, fragmentIndex): startTime = time.time() + print('Entered the _run_analysis method, FOV ' + str(fragmentIndex) ) + globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) - print('reading indexes for fov ' + str(fragmentIndex) ) + print(' globalTask loaded') # read membrane (seed) and nuclei (watershed) indexes membraneIndex = self.dataSet \ @@ -191,7 +192,7 @@ def _run_analysis(self, fragmentIndex): self.parameters['nuclei_channel_name']) endTime = time.time() - print("Indexes read, ET {:.2f} min"\ + print(" image indexes read, ET {:.2f} min"\ .format((endTime - startTime) / 60)) # read membrane (seed) and nuclei (watershed) images @@ -199,31 +200,31 @@ def _run_analysis(self, fragmentIndex): nucleiImages = self._read_image_stack(fragmentIndex, nucleiIndex) endTime = time.time() - print("Images read, ET {:.2f} min" \ + print(" images read, ET {:.2f} min" \ .format((endTime - startTime) / 60)) # Prepare masks for cv2 watershed - watershedMarkers = self._get_watershed_markers(nucleiImages, - membraneImages) + watershedMarkers = watershed.get_cv2_watershed_markers(nucleiImages, + membraneImages) endTime = time.time() - print("Markers calculated, ET {:.2f} min" \ + print(" markers calculated, ET {:.2f} min" \ .format((endTime - startTime) / 60)) # perform watershed in individual z positions - watershedOutput = self._apply_watershed(nucleiImages, - watershedMarkers) + watershedOutput = watershed.apply_cv2_watershed(nucleiImages, + watershedMarkers) endTime = time.time() - print("watershed calculated, ET {:.2f} min" \ + print(" watershed calculated, ET {:.2f} min" \ .format((endTime - startTime) / 60)) # combine all z positions in watershed - watershedCombinedOutput = self._combine_watershed_z_positions( - watershedOutput) + watershedCombinedOutput = watershed \ + .combine_2d_segmentation_masks_into_3d(watershedOutput) endTime = time.time() - print("watershed z positions combined, ET {:.2f} min" \ + print(" watershed z positions combined, ET {:.2f} min" \ .format((endTime - startTime) / 60)) # get features from mask. This is the slowestart (6 min for the @@ -238,7 +239,7 @@ def _run_analysis(self, fragmentIndex): featureDB.write_features(featureList, fragmentIndex) endTime = time.time() - print("features written, ET {:.2f} min" \ + print(" features written, ET {:.2f} min" \ .format((endTime - startTime) / 60)) def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: @@ -246,7 +247,7 @@ def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: self.parameters['warp_task']) return np.array([warpTask.get_aligned_image(fov, channelIndex, z) for z in range(len(self.dataSet.get_z_positions()))]) - +""" def _get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: # generate mask based on thresholding mask = np.zeros(membraneImages.shape) @@ -464,6 +465,7 @@ def _combine_watershed_z_positions(self, watershedCombinedZ[z-1, :, :][(watershedOutput[z-1, :, :] == n1)] = n0 return watershedCombinedZ +""" class CleanCellBoundaries(analysistask.ParallelAnalysisTask): ''' From 3e1bddd03c0bfc7061ce51d015588514eb8be71d Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 16:39:26 -0500 Subject: [PATCH 176/419] add comments to the header of multiple methods. --- merlin/util/watershed.py | 83 ++++++++++++++++++++++++++++++++++------ 1 file changed, 71 insertions(+), 12 deletions(-) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 3b5563f3..d9a820ff 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -171,8 +171,15 @@ def get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: return mask def get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: - - # TO DO: Add description + """Calculate binary mask with 1's in membrane pixels and 0 otherwise. + The images expected are some type of Nuclei label (e.g. DAPI) + + Args: + membraneImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) + """ # generate nuclei mask based on thresholding thresholdingMask = np.zeros(nucleiImages.shape) @@ -234,7 +241,18 @@ def get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, membraneImages: np.ndarray) -> np.ndarray: - # TO DO: Add description + """Combine membrane and nuclei markers into a single multilabel mask + for CV2 watershed + + Args: + nucleiImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + membraneImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) of + cv2-compatible watershed markers + """ nucleiMask = self.get_nuclei_mask(nucleiImages) membraneMask = self.get_membrane_mask(membraneImages) @@ -273,10 +291,16 @@ def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, return watershedMarker def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: - # cv2 only works in 3D images of 8bit. Make a 3D grayscale by - # using the same grayscale image in each of the rgb channels - # code below based on https://stackoverflow.com/questions/ - # 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv + """Convert a 16 bit 2D grayscale image into a 3D 8-bit RGB image. + cv2 only works in 8-bit. Based on https://stackoverflow.com/questions/ + 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv3D + + Args: + uint16Image: a 2 dimensional numpy array containing the 16-bit + image + Returns: + ndarray containing a 3 dimensional 8-bit image stack + """ # invert image uint16Image = 2**16 - uint16Image @@ -293,8 +317,20 @@ def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: return rgbImage -def apply_watershed(self, nucleiImages: np.ndarray, +def apply_cv2_watershed(self, nucleiImages: np.ndarray, watershedMarkers: np.ndarray) -> np.ndarray: + """Perform watershed using cv2 + + Args: + nucleiImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + watershedMarkers: a 3 dimensional numpy array containing the cv2 + markers arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) of + segmented cells. masks in different z positions are + independent + """ watershedOutput = np.zeros(watershedMarkers.shape) for z in range(len(self.dataSet.get_z_positions())): @@ -308,6 +344,22 @@ def apply_watershed(self, nucleiImages: np.ndarray, def get_overlapping_nuclei(self, watershedZ0: np.ndarray, watershedZ1: np.ndarray, n0: int): + """Perform watershed using cv2 + + Args: + watershedZ0: a 2 dimensional numpy array containing a + segmentation mask + watershedZ1: a 2 dimensional numpy array containing a + segmentation mask adjacent to watershedZ1 + n0: an integer with the index of the cell/nuclei to be compared + between the provided watershed segmentation masks + Returns: + a tuple (n1, f0, f1) containing the label of the cell in Z1 + overlapping n0 (n1), the fraction of n0 overlaping n1 (f0) and + the fraction of n1 overlapping n0 (f1) + + """ + z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes > 100] @@ -336,7 +388,7 @@ def get_overlapping_nuclei(self, watershedZ0: np.ndarray, if (n0OverlapFraction[indexSorted[0]] > 0.2 and n1OverlapFraction[indexSorted[0]] > 0.5): - return m1NucleiIndexes[indexSorted[0]], + return z1NucleiIndexes[indexSorted[0]], n0OverlapFraction[indexSorted[0]], n1OverlapFraction[indexSorted[0]] else: @@ -347,10 +399,17 @@ def get_overlapping_nuclei(self, watershedZ0: np.ndarray, def combine_2d_segmentation_masks_into_3d(self, watershedOutput: np.ndarray) -> np.ndarray: - # TO DO: Add description + """Take a 3 dimensional watershed masks and relabel them so that + nuclei in adjacent sections have the same label if the area their + overlap surpases certain threshold - # TO DO: this implementation is very rough, needs to be improved. - # good just for testing purposes + Args: + watershedOutput: a 3 dimensional numpy array containing the + segmentation masks arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) of + relabeled segmented cells + """ # Initialize empty array with size as watershedOutput array watershedCombinedZ = np.zeros(watershedOutput.shape) From 53529381c1ea1e4e9fc3fe23840d9c06703cfadf Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 16:44:13 -0500 Subject: [PATCH 177/419] remove commented methods --- merlin/analysis/segment.py | 220 +------------------------------------ 1 file changed, 1 insertion(+), 219 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 8bcaec6f..7534bb9e 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -247,225 +247,7 @@ def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: self.parameters['warp_task']) return np.array([warpTask.get_aligned_image(fov, channelIndex, z) for z in range(len(self.dataSet.get_z_positions()))]) -""" - def _get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: - # generate mask based on thresholding - mask = np.zeros(membraneImages.shape) - fineBlockSize = 61 - for z in range(len(self.dataSet.get_z_positions())): - mask[z, :, :] = (membraneImages[z, :, :] > - filters.threshold_local(membraneImages[z, :, :], - fineBlockSize, - offset=0)) - mask[z, :, :] = morphology.remove_small_objects( - mask[z, :, :].astype('bool'), - min_size=100, - connectivity=1) - mask[z, :, :] = morphology.binary_closing(mask[z, :, :], - morphology.selem.disk(5)) - mask[z, :, :] = morphology.skeletonize(mask[z, :, :]) - - # combine masks - return mask - - def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: - # generate nuclei mask based on thresholding - thresholdingMask = np.zeros(nucleiImages.shape) - coarseBlockSize = 241 - fineBlockSize = 61 - for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = (nucleiImages[z, :, :] > - filters.threshold_local( - nucleiImages[z, :, :], - coarseBlockSize, - offset=0)) - fineThresholdingMask = (nucleiImages[z, :, :] > - filters.threshold_local( - nucleiImages[z, :, :], - fineBlockSize, - offset=0)) - thresholdingMask[z, :, :] = (coarseThresholdingMask * - fineThresholdingMask) - thresholdingMask[z, :, :] = binary_fill_holes( - thresholdingMask[z, :, :]) - - # generate border mask, necessary to avoid making a single - # connected component when using binary_fill_holes below - borderMask = np.zeros((2048, 2048)) - borderMask[25:2023, 25:2023] = 1 - - # TODO - use the image size variable for borderMask - - # generate nuclei mask from hessian, fine - fineHessianMask = np.zeros(nucleiImages.shape) - for z in range(len(self.dataSet.get_z_positions())): - fineHessian = filters.hessian(nucleiImages[z, :, :]) - fineHessianMask[z, :, :] = fineHessian == fineHessian.max() - fineHessianMask[z, :, :] = morphology.binary_closing( - fineHessianMask[z, :, :], - morphology.selem.disk(5)) - fineHessianMask[z, :, :] = fineHessianMask[z, :, :] * borderMask - fineHessianMask[z, :, :] = binary_fill_holes( - fineHessianMask[z, :, :]) - - # generate dapi mask from hessian, coarse - coarseHessianMask = np.zeros(nucleiImages.shape) - for z in range(len(self.dataSet.get_z_positions())): - coarseHessian = filters.hessian(nucleiImages[z, :, :] - - morphology.white_tophat( - nucleiImages[z, :, :], - morphology.selem.disk(20))) - coarseHessianMask[z, :, :] = coarseHessian == coarseHessian.max() - coarseHessianMask[z, :, :] = morphology.binary_closing( - coarseHessianMask[z, :, :], morphology.selem.disk(5)) - coarseHessianMask[z, :, :] = (coarseHessianMask[z, :, :] * - borderMask) - coarseHessianMask[z, :, :] = binary_fill_holes( - coarseHessianMask[z, :, :]) - - # combine masks - nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask - return binary_fill_holes(nucleiMask) - - def _get_watershed_markers(self, nucleiImages: np.ndarray, - membraneImages: np.ndarray) -> np.ndarray: - - nucleiMask = self._get_nuclei_mask(nucleiImages) - membraneMask = self._get_membrane_mask(membraneImages) - - watershedMarker = np.zeros(nucleiMask.shape) - - for z in range(len(self.dataSet.get_z_positions())): - - # generate areas of sure bg and fg, as well as the area of - # unknown classification - background = morphology.dilation(nucleiMask[z, :, :], - morphology.selem.disk(15)) - membraneDilated = morphology.dilation( - membraneMask[z, :, :].astype('bool'), - morphology.selem.disk(10)) - foreground = morphology.erosion(nucleiMask[z, :, :] * ~ - membraneDilated, - morphology.selem.disk(5)) - unknown = background * ~ foreground - - background = np.uint8(background) * 255 - foreground = np.uint8(foreground) * 255 - unknown = np.uint8(unknown) * 255 - - # Marker labelling - ret, markers = cv2.connectedComponents(foreground) - - # Add one to all labels so that sure background is not 0, but 1 - markers = markers + 100 - - # Now, mark the region of unknown with zero - markers[unknown == 255] = 0 - - watershedMarker[z, :, :] = markers - - return watershedMarker - - def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: - # cv2 only works in 3D images of 8bit. Make a 3D grayscale by - # using the same grayscale image in each of the rgb channels - # code below based on https://stackoverflow.com/questions/ - # 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv - - # invert image - uint16Image = 2**16 - uint16Image - - # convert to uint8 - ratio = np.amax(uint16Image) / 256 - uint8Image = (uint16Image / ratio).astype('uint8') - - rgbImage = np.zeros((2048, 2048, 3)) - rgbImage[:, :, 0] = uint8Image - rgbImage[:, :, 1] = uint8Image - rgbImage[:, :, 2] = uint8Image - rgbImage = rgbImage.astype('uint8') - - return rgbImage - - def _apply_watershed(self, nucleiImages: np.ndarray, - watershedMarkers: np.ndarray) -> np.ndarray: - - watershedOutput = np.zeros(watershedMarkers.shape) - for z in range(len(self.dataSet.get_z_positions())): - rgbImage = self._convert_grayscale_to_rgb(nucleiImages[z, :, :]) - watershedOutput[z, :, :] = cv2.watershed(rgbImage, - watershedMarkers[z, :, :]. - astype('int32')) - watershedOutput[z, :, :][watershedOutput[z, :, :] <= 100] = 0 - - return watershedOutput - - def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, - watershedZ1: np.ndarray, n0: int): - z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) - z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes > 100] - - if z1NucleiIndexes.shape[0] > 0: - - # calculate overlap fraction - n0Area = np.count_nonzero(watershedZ0 == n0) - n1Area = np.zeros(len(z1NucleiIndexes)) - overlapArea = np.zeros(len(z1NucleiIndexes)) - - for ii in range(len(z1NucleiIndexes)): - n1 = z1NucleiIndexes[ii] - n1Area[ii] = np.count_nonzero(watershedZ1 == n1) - overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * - (watershedZ1 == n1)) - - n0OverlapFraction = np.asarray(overlapArea / n0Area) - n1OverlapFraction = np.asarray(overlapArea / n1Area) - index = list(range(len(n0OverlapFraction))) - - # select the nuclei that has the highest fraction in n0 and n1 - r1, r2, indexSorted = zip(*sorted(zip(n0OverlapFraction, - n1OverlapFraction, - index), - reverse=True)) - - if (n0OverlapFraction[indexSorted[0]] > 0.2 and - n1OverlapFraction[indexSorted[0]] > 0.5): - return m1NucleiIndexes[indexSorted[0]], - n0OverlapFraction[indexSorted[0]], - n1OverlapFraction[indexSorted[0]] - else: - return False, False, False - else: - return False, False, False - - def _combine_watershed_z_positions(self, - watershedOutput: - np.ndarray) -> np.ndarray: - - # TO DO: this implementation is very rough, needs to be improved. - # good just for testing purposes - - # Initialize empty array with size as watershedOutput array - watershedCombinedZ = np.zeros(watershedOutput.shape) - - # copy the mask of the section farthest to the coverslip - watershedCombinedZ[-1, :, :] = watershedOutput[-1, :, :] - - # starting far from coverslip - for z in range(len(self.dataSet.get_z_positions())-1, 0, -1): - zNucleiIndex = np.unique(watershedOutput[z, :, :])[ - np.unique(watershedOutput[z, :, :]) > 100] - - for n0 in zNucleiIndex: - n1, f0, f1 = self._get_overlapping_nuclei( - watershedCombinedZ[z, :, :], - watershedOutput[z-1, :, :], - n0) - if n1: - watershedCombinedZ[z-1, :, :][(watershedOutput[z-1, :, :] == - n1)] = n0 - return watershedCombinedZ -""" + class CleanCellBoundaries(analysistask.ParallelAnalysisTask): ''' From 8333d39bbb20c67422068b0a76ee19c809344443 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 16:59:49 -0500 Subject: [PATCH 178/419] pep8 compliance --- merlin/analysis/segment.py | 23 +++++++++++------------ merlin/util/watershed.py | 28 ++++++++++++++-------------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 7534bb9e..fb87ceac 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -16,7 +16,7 @@ from merlin.util import watershed import pandas import networkx as nx -import time +import time class FeatureSavingAnalysisTask(analysistask.ParallelAnalysisTask): @@ -173,12 +173,11 @@ def get_cell_boundaries(self) -> List[spatialfeature.SpatialFeature]: def _run_analysis(self, fragmentIndex): startTime = time.time() - print('Entered the _run_analysis method, FOV ' + str(fragmentIndex) ) + print('Entered the _run_analysis method, FOV ' + str(fragmentIndex)) globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) - print(' globalTask loaded') # read membrane (seed) and nuclei (watershed) indexes @@ -192,8 +191,8 @@ def _run_analysis(self, fragmentIndex): self.parameters['nuclei_channel_name']) endTime = time.time() - print(" image indexes read, ET {:.2f} min"\ - .format((endTime - startTime) / 60)) + print(" image indexes read, ET {:.2f} min" \ + .format((endTime - startTime) / 60)) # read membrane (seed) and nuclei (watershed) images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) @@ -201,7 +200,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" images read, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + .format((endTime - startTime) / 60)) # Prepare masks for cv2 watershed watershedMarkers = watershed.get_cv2_watershed_markers(nucleiImages, @@ -209,7 +208,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" markers calculated, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + .format((endTime - startTime) / 60)) # perform watershed in individual z positions watershedOutput = watershed.apply_cv2_watershed(nucleiImages, @@ -217,7 +216,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" watershed calculated, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + .format((endTime - startTime) / 60)) # combine all z positions in watershed watershedCombinedOutput = watershed \ @@ -225,10 +224,10 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" watershed z positions combined, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + .format((endTime - startTime) / 60)) - # get features from mask. This is the slowestart (6 min for the - # previous part, 15+ for the rest, for a 7 frame Image. + # get features from mask. This is the slowestart (6 min for the + # previous part, 15+ for the rest, for a 7 frame Image. zPos = np.array(self.dataSet.get_data_organization().get_z_positions()) featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( (watershedCombinedOutput == i), fragmentIndex, @@ -240,7 +239,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" features written, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + .format((endTime - startTime) / 60)) def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index d9a820ff..d6d4be07 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -143,13 +143,13 @@ def prepare_watershed_images(watershedImageStack: np.ndarray def get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: """Calculate binary mask with 1's in membrane pixels and 0 otherwise. - The images expected are some type of membrane label (WGA, ConA, + The images expected are some type of membrane label (WGA, ConA, Lamin, Cadherins) Args: membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). - Returns: + Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) """ mask = np.zeros(membraneImages.shape) @@ -177,7 +177,7 @@ def get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: Args: membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). - Returns: + Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) """ @@ -291,15 +291,15 @@ def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, return watershedMarker def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: - """Convert a 16 bit 2D grayscale image into a 3D 8-bit RGB image. + """Convert a 16 bit 2D grayscale image into a 3D 8-bit RGB image. cv2 only works in 8-bit. Based on https://stackoverflow.com/questions/ - 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv3D + 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv3D Args: - uint16Image: a 2 dimensional numpy array containing the 16-bit + uint16Image: a 2 dimensional numpy array containing the 16-bit image Returns: - ndarray containing a 3 dimensional 8-bit image stack + ndarray containing a 3 dimensional 8-bit image stack """ # invert image @@ -347,15 +347,15 @@ def get_overlapping_nuclei(self, watershedZ0: np.ndarray, """Perform watershed using cv2 Args: - watershedZ0: a 2 dimensional numpy array containing a + watershedZ0: a 2 dimensional numpy array containing a segmentation mask watershedZ1: a 2 dimensional numpy array containing a segmentation mask adjacent to watershedZ1 n0: an integer with the index of the cell/nuclei to be compared between the provided watershed segmentation masks Returns: - a tuple (n1, f0, f1) containing the label of the cell in Z1 - overlapping n0 (n1), the fraction of n0 overlaping n1 (f0) and + a tuple (n1, f0, f1) containing the label of the cell in Z1 + overlapping n0 (n1), the fraction of n0 overlaping n1 (f0) and the fraction of n1 overlapping n0 (f1) """ @@ -399,12 +399,12 @@ def get_overlapping_nuclei(self, watershedZ0: np.ndarray, def combine_2d_segmentation_masks_into_3d(self, watershedOutput: np.ndarray) -> np.ndarray: - """Take a 3 dimensional watershed masks and relabel them so that - nuclei in adjacent sections have the same label if the area their - overlap surpases certain threshold + """Take a 3 dimensional watershed masks and relabel them so that + nuclei in adjacent sections have the same label if the area their + overlap surpases certain threshold Args: - watershedOutput: a 3 dimensional numpy array containing the + watershedOutput: a 3 dimensional numpy array containing the segmentation masks arranged as (z, x, y). Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) of From 6e0e6f9b51a5ccd61983326f145406fa9029431c Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 17:04:07 -0500 Subject: [PATCH 179/419] pep8 compliance --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index fb87ceac..d7e7a7ba 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -191,8 +191,8 @@ def _run_analysis(self, fragmentIndex): self.parameters['nuclei_channel_name']) endTime = time.time() - print(" image indexes read, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + print(" image indexes read, ET {:.2f} min".format( + (endTime - startTime) / 60)) # read membrane (seed) and nuclei (watershed) images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) From bbcf37d41e98acaf7cbf9d69131eb2ce1aa639d7 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 17:11:46 -0500 Subject: [PATCH 180/419] pep8 compliance --- merlin/analysis/segment.py | 20 ++++++++++---------- merlin/util/watershed.py | 18 ++++++++++++------ 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index d7e7a7ba..a70af875 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -199,32 +199,32 @@ def _run_analysis(self, fragmentIndex): nucleiImages = self._read_image_stack(fragmentIndex, nucleiIndex) endTime = time.time() - print(" images read, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + print(" images read, ET {:.2f} min".format( + (endTime - startTime) / 60)) # Prepare masks for cv2 watershed watershedMarkers = watershed.get_cv2_watershed_markers(nucleiImages, membraneImages) endTime = time.time() - print(" markers calculated, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + print(" markers calculated, ET {:.2f} min".format( + (endTime - startTime) / 60)) # perform watershed in individual z positions watershedOutput = watershed.apply_cv2_watershed(nucleiImages, watershedMarkers) endTime = time.time() - print(" watershed calculated, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + print(" watershed calculated, ET {:.2f} min".format( + (endTime - startTime) / 60)) # combine all z positions in watershed watershedCombinedOutput = watershed \ .combine_2d_segmentation_masks_into_3d(watershedOutput) endTime = time.time() - print(" watershed z positions combined, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + print(" watershed z positions combined, ET {:.2f} min".format( + (endTime - startTime) / 60)) # get features from mask. This is the slowestart (6 min for the # previous part, 15+ for the rest, for a 7 frame Image. @@ -238,8 +238,8 @@ def _run_analysis(self, fragmentIndex): featureDB.write_features(featureList, fragmentIndex) endTime = time.time() - print(" features written, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + print(" features written, ET {:.2f} min".format( + (endTime - startTime) / 60)) def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index d6d4be07..a79f25cd 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -141,6 +141,7 @@ def prepare_watershed_images(watershedImageStack: np.ndarray return normalizedWatershed, watershedMask + def get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: """Calculate binary mask with 1's in membrane pixels and 0 otherwise. The images expected are some type of membrane label (WGA, ConA, @@ -170,6 +171,7 @@ def get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: # combine masks return mask + def get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: """Calculate binary mask with 1's in membrane pixels and 0 otherwise. The images expected are some type of Nuclei label (e.g. DAPI) @@ -239,6 +241,7 @@ def get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask return binary_fill_holes(nucleiMask) + def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, membraneImages: np.ndarray) -> np.ndarray: """Combine membrane and nuclei markers into a single multilabel mask @@ -290,6 +293,7 @@ def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, return watershedMarker + def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: """Convert a 16 bit 2D grayscale image into a 3D 8-bit RGB image. cv2 only works in 8-bit. Based on https://stackoverflow.com/questions/ @@ -298,7 +302,7 @@ def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: Args: uint16Image: a 2 dimensional numpy array containing the 16-bit image - Returns: + Returns: ndarray containing a 3 dimensional 8-bit image stack """ @@ -317,8 +321,9 @@ def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: return rgbImage + def apply_cv2_watershed(self, nucleiImages: np.ndarray, - watershedMarkers: np.ndarray) -> np.ndarray: + watershedMarkers: np.ndarray) -> np.ndarray: """Perform watershed using cv2 Args: @@ -326,9 +331,9 @@ def apply_cv2_watershed(self, nucleiImages: np.ndarray, arranged as (z, x, y). watershedMarkers: a 3 dimensional numpy array containing the cv2 markers arranged as (z, x, y). - Returns: + Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) of - segmented cells. masks in different z positions are + segmented cells. masks in different z positions are independent """ @@ -342,6 +347,7 @@ def apply_cv2_watershed(self, nucleiImages: np.ndarray, return watershedOutput + def get_overlapping_nuclei(self, watershedZ0: np.ndarray, watershedZ1: np.ndarray, n0: int): """Perform watershed using cv2 @@ -357,7 +363,6 @@ def get_overlapping_nuclei(self, watershedZ0: np.ndarray, a tuple (n1, f0, f1) containing the label of the cell in Z1 overlapping n0 (n1), the fraction of n0 overlaping n1 (f0) and the fraction of n1 overlapping n0 (f1) - """ z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) @@ -396,6 +401,7 @@ def get_overlapping_nuclei(self, watershedZ0: np.ndarray, else: return False, False, False + def combine_2d_segmentation_masks_into_3d(self, watershedOutput: np.ndarray) -> np.ndarray: @@ -406,7 +412,7 @@ def combine_2d_segmentation_masks_into_3d(self, Args: watershedOutput: a 3 dimensional numpy array containing the segmentation masks arranged as (z, x, y). - Returns: + Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) of relabeled segmented cells """ From 5bef0ce3cda65b5d591b68cbe46c5fc748e4c5a2 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 17:12:35 -0500 Subject: [PATCH 181/419] pep8 compliance --- merlin/util/watershed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index a79f25cd..9e40d7bf 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -252,7 +252,7 @@ def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, arranged as (z, x, y). membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). - Returns: + Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) of cv2-compatible watershed markers """ From 0b584ffdfea2f28c574100bf546c1cb174fe242b Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 12 Feb 2020 13:03:26 -0500 Subject: [PATCH 182/419] add george's modifications to dataportal --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c65e9715..dfc4de44 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,4 +28,4 @@ tables boto3 xmltodict google-cloud-storage -docutils<0.16,>=0.10 \ No newline at end of file +docutils<0.16,>=0.10 From 3a60dc8c29e89dc9c4b736e568f5dc277429ed80 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 15 Feb 2020 09:38:38 -0500 Subject: [PATCH 183/419] adding additional modifications to aws --- merlin/util/dataportal.py | 1 - 1 file changed, 1 deletion(-) diff --git a/merlin/util/dataportal.py b/merlin/util/dataportal.py index 3c8d2bb6..bba38f97 100755 --- a/merlin/util/dataportal.py +++ b/merlin/util/dataportal.py @@ -375,6 +375,5 @@ def read_file_bytes(self, startByte, endByte): endByte=endByte-1) return file - def close(self) -> None: pass From ba4935ef5d10761ef1bcbd3d13ca47095996a448 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 18 Feb 2020 14:45:04 -0500 Subject: [PATCH 184/419] fixing indentation --- merlin/util/segmentation.py | 574 ++++++++++++++++++++++++++++++++++++ 1 file changed, 574 insertions(+) create mode 100755 merlin/util/segmentation.py diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py new file mode 100755 index 00000000..461e4fee --- /dev/null +++ b/merlin/util/segmentation.py @@ -0,0 +1,574 @@ +import numpy as np +import cv2 +from scipy import ndimage +from scipy.ndimage.morphology import binary_fill_holes +from skimage import morphology +from skimage import filters +from skimage import measure +from skimage import feature +from pyclustering.cluster import kmedoids +from typing import Tuple + +from merlin.util import matlab + +from cellpose import models + + +""" +This module contains utility functions for preparing images for +watershed segmentation, as well as functions to perform segmentation +using machine learning approaches +""" + +# To match Matlab's strel('disk', 20) +diskStruct = morphology.diamond(28)[9:48, 9:48] + + +def extract_seeds(seedImageStackIn: np.ndarray) -> np.ndarray: + """Determine seed positions from the input images. + + The initial seeds are determined by finding the regional intensity maximums + after erosion and filtering with an adaptive threshold. These initial + seeds are then expanded by dilation. + + Args: + seedImageStackIn: a 3 dimensional numpy array arranged as (z,x,y) + Returns: a boolean numpy array with the same dimensions as seedImageStackIn + where a given (z,x,y) coordinate is True if it corresponds to a seed + position and false otherwise. + """ + seedImages = seedImageStackIn.copy() + + seedImages = ndimage.grey_erosion( + seedImages, + footprint=ndimage.morphology.generate_binary_structure(3, 1)) + seedImages = np.array([cv2.erode(x, diskStruct, + borderType=cv2.BORDER_REFLECT) + for x in seedImages]) + + thresholdFilterSize = int(2 * np.floor(seedImages.shape[1] / 16) + 1) + seedMask = np.array([x < 1.1 * filters.threshold_local( + x, thresholdFilterSize, method='mean', mode='nearest') + for x in seedImages]) + + seedImages[seedMask] = 0 + + seeds = morphology.local_maxima(seedImages, allow_borders=True) + + seeds = ndimage.morphology.binary_dilation( + seeds, structure=ndimage.morphology.generate_binary_structure(3, 1)) + seeds = np.array([ndimage.morphology.binary_dilation( + x, structure=morphology.diamond(28)[9:48, 9:48]) for x in seeds]) + + return seeds + + +def separate_merged_seeds(seedsIn: np.ndarray) -> np.ndarray: + """Separate seeds that are merged in 3 dimensions but are separated + in some 2 dimensional slices. + + Args: + seedsIn: a 3 dimensional binary numpy array arranged as (z,x,y) where + True indicates the pixel corresponds with a seed. + Returns: a 3 dimensional binary numpy array of the same size as seedsIn + indicating the positions of seeds after processing. + """ + + def create_region_image(shape, c): + region = np.zeros(shape) + for x in c.coords: + region[x[0], x[1], x[2]] = 1 + return region + + components = measure.regionprops(measure.label(seedsIn)) + seeds = np.zeros(seedsIn.shape) + for c in components: + seedImage = create_region_image(seeds.shape, c) + localProps = [measure.regionprops(measure.label(x)) for x in seedImage] + seedCounts = [len(x) for x in localProps] + + if all([x < 2 for x in seedCounts]): + goodFrames = [i for i, x in enumerate(seedCounts) if x == 1] + goodProperties = [y for x in goodFrames for y in localProps[x]] + seedPositions = np.round([np.median( + [x.centroid for x in goodProperties], axis=0)]).astype(int) + else: + goodFrames = [i for i, x in enumerate(seedCounts) if x > 1] + goodProperties = [y for x in goodFrames for y in localProps[x]] + goodCentroids = [x.centroid for x in goodProperties] + km = kmedoids.kmedoids( + goodCentroids, + np.random.choice(np.arange(len(goodCentroids)), + size=np.max(seedCounts))) + km.process() + seedPositions = np.round( + [goodCentroids[x] for x in km.get_medoids()]).astype(int) + + for s in seedPositions: + for f in goodFrames: + seeds[f, s[0], s[1]] = 1 + + seeds = ndimage.morphology.binary_dilation( + seeds, structure=ndimage.morphology.generate_binary_structure(3, 1)) + seeds = np.array([ndimage.morphology.binary_dilation( + x, structure=diskStruct) for x in seeds]) + + return seeds + + +def prepare_watershed_images(watershedImageStack: np.ndarray + ) -> Tuple[np.ndarray, np.ndarray]: + """Prepare the given images as the input image for watershedding. + + A watershed mask is determined using an adaptive threshold and the watershed + images are inverted so the largest values in the watershed images become + minima and then the image stack is normalized to have values between 0 + and 1. + + Args: + watershedImageStack: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + Returns: a tuple containing the normalized watershed images and the + calculated watershed mask + """ + filterSize = int(2 * np.floor(watershedImageStack.shape[1] / 16) + 1) + + watershedMask = np.array([ndimage.morphology.binary_fill_holes( + x > 1.1 * filters.threshold_local(x, filterSize, method='mean', + mode='nearest')) + for x in watershedImageStack]) + + normalizedWatershed = 1 - (watershedImageStack + - np.min(watershedImageStack)) / \ + (np.max(watershedImageStack) + - np.min(watershedImageStack)) + normalizedWatershed[np.invert(watershedMask)] = 1 + + return normalizedWatershed, watershedMask + + +def get_membrane_mask(membraneImages: np.ndarray, + compartmentChannelName: str, + membraneChannelName: str) -> np.ndarray: + """Calculate binary mask with 1's in membrane pixels and 0 otherwise. + The images expected are some type of membrane label (WGA, ConA, + Lamin, Cadherins) or compartment images (DAPI, CD45, polyT) + + Args: + membraneImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + membraneChannelName: A string with the name of a membrane channel. + compartmentChannelName: A string with the name of the compartment + channel + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) + """ + mask = np.zeros(membraneImages.shape) + if membraneChannelName != compartmentChannelName: + fineBlockSize = 61 + for z in range(membraneImages.shape[0]): + mask[z, :, :] = (membraneImages[z, :, :] > + filters.threshold_local(membraneImages[z, :, :], + fineBlockSize, + offset=0)) + mask[z, :, :] = morphology.remove_small_objects( + mask[z, :, :].astype('bool'), + min_size=100, + connectivity=1) + mask[z, :, :] = morphology.binary_closing(mask[z, :, :], + morphology.selem.disk(5)) + mask[z, :, :] = morphology.skeletonize(mask[z, :, :]) + else: + filterSigma2 = 5 + filterSize2 = int(2*np.ceil(2*filterSigma2)+1) + edgeSigma = 2# 1 #2 + lowThresh = 0.1# 0.5 #0.2 + hiThresh = 0.5# 0.7 #0.6 + for z in range(membraneImages.shape[0]): + blurredImage = cv2.GaussianBlur(membraneImages[z, :, :], + (filterSize2, filterSize2), + filterSigma2) + edge0 = feature.canny(membraneImages[z, :, :], + sigma=edgeSigma, + use_quantiles=True, + low_threshold=lowThresh, + high_threshold=hiThresh) + edge0 = morphology.dilation(edge0, morphology.selem.disk(10)) + + edge1 = feature.canny(blurredImage, + sigma=edgeSigma, + use_quantiles=True, + low_threshold=lowThresh, + high_threshold=hiThresh) + edge1 = morphology.dilation(edge1, morphology.selem.disk(10)) + + mask[z, :, :] = edge0 + edge1 + + mask[z, :, :] = morphology.skeletonize(mask[z, :, :]) + + return mask + + +def get_compartment_mask(compartmentImages: np.ndarray) -> np.ndarray: + """Calculate binary mask with 1's in compartment (nuclei or cytoplasm) + pixels and 0 otherwise. The images expected are some type of compartment + label (e.g. Nuclei: DAPI, Cytoplasm: PolyT, CD45, etc) + + Args: + compartmentImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) + """ + + # generate compartment mask based on thresholding + thresholdingMask = np.zeros(compartmentImages.shape) + coarseBlockSize = 241 + fineBlockSize = 61 + for z in range(compartmentImages.shape[0]): + coarseThresholdingMask = (compartmentImages[z, :, :] > + filters.threshold_local( + compartmentImages[z, :, :], + coarseBlockSize, + offset=0)) + fineThresholdingMask = (compartmentImages[z, :, :] > + filters.threshold_local( + compartmentImages[z, :, :], + fineBlockSize, + offset=0)) + thresholdingMask[z, :, :] = (coarseThresholdingMask * + fineThresholdingMask) + thresholdingMask[z, :, :] = binary_fill_holes( + thresholdingMask[z, :, :]) + + # generate border mask, necessary to avoid making a single + # connected component when using binary_fill_holes below + borderMask = np.zeros((compartmentImages.shape[1], + compartmentImages.shape[2])) + borderMask[25:(compartmentImages.shape[1]-25), + 25:(compartmentImages.shape[2]-25)] = 1 + + # generate compartment mask from hessian, fine + fineHessianMask = np.zeros(compartmentImages.shape) + for z in range(compartmentImages.shape[0]): + fineHessian = filters.hessian(compartmentImages[z, :, :]) + fineHessianMask[z, :, :] = fineHessian == fineHessian.max() + fineHessianMask[z, :, :] = morphology.binary_closing( + fineHessianMask[z, :, :], + morphology.selem.disk(5)) + fineHessianMask[z, :, :] = fineHessianMask[z, :, :] * borderMask + fineHessianMask[z, :, :] = binary_fill_holes( + fineHessianMask[z, :, :]) + + # generate compartment mask from hessian, coarse + coarseHessianMask = np.zeros(compartmentImages.shape) + for z in range(compartmentImages.shape[0]): + coarseHessian = filters.hessian(compartmentImages[z, :, :] - + morphology.white_tophat( + compartmentImages[z, :, :], + morphology.selem.disk(20))) + coarseHessianMask[z, :, :] = coarseHessian == coarseHessian.max() + coarseHessianMask[z, :, :] = morphology.binary_closing( + coarseHessianMask[z, :, :], morphology.selem.disk(5)) + coarseHessianMask[z, :, :] = (coarseHessianMask[z, :, :] * + borderMask) + coarseHessianMask[z, :, :] = binary_fill_holes( + coarseHessianMask[z, :, :]) + + # combine masks + compartmentMask = thresholdingMask + fineHessianMask + coarseHessianMask + return binary_fill_holes(compartmentMask) + + +def get_cv2_watershed_markers(compartmentImages: np.ndarray, + membraneImages: np.ndarray, + compartmentChannelName: str, + membraneChannelName: str) -> np.ndarray: + """Combine membrane and compartment markers into a single multilabel mask + for CV2 watershed + + Args: + compartmentImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + membraneImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + compartmentChannelName: str with the name of the compartment channel + to use + membraneChannelName: str with the name of the membrane channel + to use + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) of + cv2-compatible watershed markers + """ + + compartmentMask = get_compartment_mask(compartmentImages) + membraneMask = get_membrane_mask(membraneImages, + compartmentChannelName, + membraneChannelName) + + watershedMarker = np.zeros(compartmentMask.shape) + + for z in range(compartmentImages.shape[0]): + + # generate areas of sure bg and fg, as well as the area of + # unknown classification + background = morphology.dilation(compartmentMask[z, :, :], + morphology.selem.disk(15)) + membraneDilated = morphology.dilation( + membraneMask[z, :, :].astype('bool'), + morphology.selem.disk(10)) + foreground = morphology.erosion(compartmentMask[z, :, :] * ~ + membraneDilated, + morphology.selem.disk(5)) + unknown = background * ~ foreground + + background = np.uint8(background) * 255 + foreground = np.uint8(foreground) * 255 + unknown = np.uint8(unknown) * 255 + + # Marker labelling + ret, markers = cv2.connectedComponents(foreground) + + # Add one to all labels so that sure background is not 0, but 1 + markers = markers + 1 + + # Now, mark the region of unknown with zero + markers[unknown == 255] = 0 + + watershedMarker[z, :, :] = markers + + return watershedMarker + + +def convert_grayscale_to_rgb(uint16Image: np.ndarray) -> np.ndarray: + """Convert a 16 bit 2D grayscale image into a 3D 8-bit RGB image. + cv2 only works in 8-bit. Based on https://stackoverflow.com/questions/ + 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv3D + + Args: + uint16Image: a 2 dimensional numpy array containing the 16-bit + image + Returns: + ndarray containing a 3 dimensional 8-bit image stack + """ + + # invert image + uint16Image = 2**16 - uint16Image + + # convert to uint8 + ratio = np.amax(uint16Image) / 256 + uint8Image = (uint16Image / ratio).astype('uint8') + + rgbImage = np.zeros((2048, 2048, 3)) + rgbImage[:, :, 0] = uint8Image + rgbImage[:, :, 1] = uint8Image + rgbImage[:, :, 2] = uint8Image + rgbImage = rgbImage.astype('uint8') + + return rgbImage + + +def apply_cv2_watershed(compartmentImages: np.ndarray, + watershedMarkers: np.ndarray) -> np.ndarray: + """Perform watershed using cv2 + + Args: + compartmentImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + watershedMarkers: a 3 dimensional numpy array containing the cv2 + markers arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) of + segmented cells. masks in different z positions are + independent + """ + + watershedOutput = np.zeros(watershedMarkers.shape) + for z in range(compartmentImages.shape[0]): + rgbImage = convert_grayscale_to_rgb(compartmentImages[z, :, :]) + watershedOutput[z, :, :] = cv2.watershed(rgbImage, + watershedMarkers[z, :, :]. + astype('int32')) + watershedOutput[z, :, :][watershedOutput[z, :, :] <= 100] = 0 + + return watershedOutput + + +def get_overlapping_objects(segmentationZ0: np.ndarray, + segmentationZ1: np.ndarray, + n0: int) -> Tuple[np.float64, + np.float64, np.float64]: + """compare cell labels in adjacent image masks + + Args: + segmentationZ0: a 2 dimensional numpy array containing a + segmentation mask in position Z + segmentationZ1: a 2 dimensional numpy array containing a + segmentation mask adjacent tosegmentationZ0 + n0: an integer with the index of the object (cell/nuclei) + to be compared between the provided segmentation masks + Returns: + a tuple (n1, f0, f1) containing the label of the cell in Z1 + overlapping n0 (n1), the fraction of n0 overlaping n1 (f0) and + the fraction of n1 overlapping n0 (f1) + """ + + z1Indexes = np.unique(segmentationZ1[segmentationZ0 == n0]) + z1Indexes = z1Indexes[z1Indexes > 0] + + if z1Indexes.shape[0] > 0: + + # calculate overlap fraction + n0Area = np.count_nonzero(segmentationZ0 == n0) + n1Area = np.zeros(len(z1Indexes)) + overlapArea = np.zeros(len(z1Indexes)) + + for ii in range(len(z1Indexes)): + n1 = z1Indexes[ii] + n1Area[ii] = np.count_nonzero(segmentationZ1 == n1) + overlapArea[ii] = np.count_nonzero((segmentationZ0 == n0) * + (segmentationZ1 == n1)) + + n0OverlapFraction = np.asarray(overlapArea / n0Area) + n1OverlapFraction = np.asarray(overlapArea / n1Area) + index = list(range(len(n0OverlapFraction))) + + # select the nuclei that has the highest fraction in n0 and n1 + r1, r2, indexSorted = zip(*sorted(zip(n0OverlapFraction, + n1OverlapFraction, + index), + reverse=True)) + + if (n0OverlapFraction[indexSorted[0]] > 0.2 and + n1OverlapFraction[indexSorted[0]] > 0.5): + return (z1Indexes[indexSorted[0]], + n0OverlapFraction[indexSorted[0]], + n1OverlapFraction[indexSorted[0]]) + else: + return (False, False, False) + else: + return (False, False, False) + + +def combine_2d_segmentation_masks_into_3d(segmentationOutput: + np.ndarray) -> np.ndarray: + """Take a 3 dimensional segmentation masks and relabel them so that + nuclei in adjacent sections have the same label if the area their + overlap surpases certain threshold + + Args: + segmentationOutput: a 3 dimensional numpy array containing the + segmentation masks arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) of + relabeled segmented cells + """ + + # Initialize empty array with size as segmentationOutput array + segmentationCombinedZ = np.zeros(segmentationOutput.shape) + + # copy the mask of the section farthest to the coverslip to start + segmentationCombinedZ[-1, :, :] = segmentationOutput[-1, :, :] + + # starting far from coverslip + for z in range(segmentationOutput.shape[0]-1, 0, -1): + + # get non-background cell indexes + zIndex = np.unique(segmentationOutput[z, :, :])[ + np.unique(segmentationOutput[z, :, :]) > 0] + + # compare each cell in z0 + for n0 in zIndex: + n1, f0, f1 = get_overlapping_objects(segmentationCombinedZ[z, :, :], + segmentationOutput[z-1, :, :], + n0) + if n1: + segmentationCombinedZ[z-1, :, :][ + (segmentationOutput[z-1, :, :] == n1)] = n0 + return segmentationCombinedZ + + +def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: + return None + + +def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: + return None + + +def segment_using_cellpose(imageStackIn: np.ndarray, + params: dict) -> np.ndarray: + """Perform segmentation using cellpose. Code adapted from + https://nbviewer.jupyter.org/github/MouseLand/cellpose/blob/ + master/notebooks/run_cellpose.ipynb + Args: + imageStackIn: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + params: a dictionary with the parameters for segmentation + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) + """ + channelName = params['channel'].lower() + + # Define cellpose model + if any([channelName == 'dapi', + channelName == 'lamin']): + model = models.Cellpose(gpu=False, model_type='nuclei') + if any([channelName == 'polyt', + channelName == 'polya', + channelName == 'ecadherin', + channelName == 'cd45', + channelName == 'wga', + channelName == 'cona']): + model = models.Cellpose(gpu=False, model_type='cyto') + + # define CHANNELS to run segementation on + # grayscale=0, R=1, G=2, B=3 + # channels = [cytoplasm, nucleus] + # if NUCLEUS channel does not exist, set the second channel to 0 + # channels = [0,0] + # IF ALL YOUR IMAGES ARE THE SAME TYPE, you can give a list with 2 elements + channels = [0, 0] # IF YOU HAVE GRAYSCALE + # channels = [2,3] # IF YOU HAVE G=cytoplasm and B=nucleus + # channels = [2,1] # IF YOU HAVE G=cytoplasm and R=nucleus + + # or if you have different types of channels in each image + # channels = [[0,0],[0,0]] + + # if diameter is set to None, the size of the cells is estimated on a per + # image basis you can set the average cell `diameter` in pixels yourself + # (recommended) diameter can be a list or a single number for all images + + # put list of images in cellpose format + imageList = np.split(imageStackIn, imageStackIn.shape[0]) + + masks, flows, styles, diams = model.eval(imageList, + diameter=params['diameter'], + channels=channels) + # combine masks into array + masksArray = np.stack(masks) + + return masksArray + + +def apply_machine_learning_segmentation(imageStackIn: np.ndarray, + params: dict) -> np.ndarray: + """Select segmentation algorithm to use + Args: + imageStackIn: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + params: dictionary with key:value pairs with parameters to be passed + to the segmentation code. Keys used are 'method', 'diameter', + 'channel' + + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) + """ + if params['method'] == 'ilastik': + segmentOutput = segment_using_ilastik(imageStackIn, params) + elif params['method'] == 'cellpose': + segmentOutput = segment_using_cellpose(imageStackIn, params) + elif params['method'] == 'unet': + segmentOutput = segment_using_unet(imageStackIn, params) + + return segmentOutput From b9e01ebd1034b701c57a6775c5fb540a2b4fba25 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 18 Feb 2020 14:53:18 -0500 Subject: [PATCH 185/419] pep8 compliance --- merlin/util/dataportal.py | 1 + 1 file changed, 1 insertion(+) diff --git a/merlin/util/dataportal.py b/merlin/util/dataportal.py index bba38f97..c01e7a17 100755 --- a/merlin/util/dataportal.py +++ b/merlin/util/dataportal.py @@ -344,6 +344,7 @@ def get_sibling_with_extension(self, newExtension: str): def _error_tolerant_reading(self, method, startByte=None, endByte=None): + backoffSeries = [1, 2, 4, 8, 16, 32, 64, 128, 256] for sleepDuration in backoffSeries: try: From 2cd12e3b1eae7bdaf40ce20a842e3fae9da15fd1 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Mon, 2 Mar 2020 14:03:44 -0500 Subject: [PATCH 186/419] added printing for debuging --- merlin/analysis/segment.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index a70af875..a5f81214 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -201,6 +201,8 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) + print(" membraneImages Type: " + type(membraneImages)) + print(" nucleiImages Type: " + type(nucleiImages)) # Prepare masks for cv2 watershed watershedMarkers = watershed.get_cv2_watershed_markers(nucleiImages, From e2c466ed77284299b9ff9bccb928eb00bcbe4308 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Mon, 2 Mar 2020 14:26:51 -0500 Subject: [PATCH 187/419] added printing for debuging --- merlin/analysis/segment.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index a5f81214..0e6b01ee 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -201,8 +201,16 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) - print(" membraneImages Type: " + type(membraneImages)) - print(" nucleiImages Type: " + type(nucleiImages)) + print(" membraneImages Type: " + str(type(membraneImages))) + print(" membraneImages Size: [" + + str(membraneImages.shape[0]) + + "," + str(membraneImages.shape[1]) + + "," + str(membraneImages.shape[2]) "]" ) + print(" nucleiImages Type: " + str(type(nucleiImages))) + print(" nucleiImages Size: [" + + str(nucleiImages.shape[0]) + + "," + str(nucleiImages.shape[1]) + + "," + str(nucleiImages.shape[2]) "]" ) # Prepare masks for cv2 watershed watershedMarkers = watershed.get_cv2_watershed_markers(nucleiImages, From eef6d9295e1ecec4e6975f86d45006667b3a939c Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Mon, 2 Mar 2020 14:31:30 -0500 Subject: [PATCH 188/419] added printing for debuging --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 0e6b01ee..52a9f517 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -205,12 +205,12 @@ def _run_analysis(self, fragmentIndex): print(" membraneImages Size: [" + str(membraneImages.shape[0]) + "," + str(membraneImages.shape[1]) - + "," + str(membraneImages.shape[2]) "]" ) + + "," + str(membraneImages.shape[2]) + "]" ) print(" nucleiImages Type: " + str(type(nucleiImages))) print(" nucleiImages Size: [" + str(nucleiImages.shape[0]) + "," + str(nucleiImages.shape[1]) - + "," + str(nucleiImages.shape[2]) "]" ) + + "," + str(nucleiImages.shape[2]) + "]" ) # Prepare masks for cv2 watershed watershedMarkers = watershed.get_cv2_watershed_markers(nucleiImages, From ce8f8c152da19742fb205852cf546924d264852f Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Mon, 2 Mar 2020 16:22:34 -0500 Subject: [PATCH 189/419] removing self calls --- merlin/util/watershed.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 9e40d7bf..0ccac583 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -142,7 +142,7 @@ def prepare_watershed_images(watershedImageStack: np.ndarray return normalizedWatershed, watershedMask -def get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: +def get_membrane_mask(membraneImages: np.ndarray) -> np.ndarray: """Calculate binary mask with 1's in membrane pixels and 0 otherwise. The images expected are some type of membrane label (WGA, ConA, Lamin, Cadherins) @@ -172,7 +172,7 @@ def get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: return mask -def get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: +def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: """Calculate binary mask with 1's in membrane pixels and 0 otherwise. The images expected are some type of Nuclei label (e.g. DAPI) @@ -242,7 +242,7 @@ def get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: return binary_fill_holes(nucleiMask) -def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, +def get_cv2_watershed_markers(nucleiImages: np.ndarray, membraneImages: np.ndarray) -> np.ndarray: """Combine membrane and nuclei markers into a single multilabel mask for CV2 watershed @@ -294,7 +294,7 @@ def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, return watershedMarker -def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: +def convert_grayscale_to_rgb(uint16Image: np.ndarray) -> np.ndarray: """Convert a 16 bit 2D grayscale image into a 3D 8-bit RGB image. cv2 only works in 8-bit. Based on https://stackoverflow.com/questions/ 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv3D @@ -322,7 +322,7 @@ def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: return rgbImage -def apply_cv2_watershed(self, nucleiImages: np.ndarray, +def apply_cv2_watershed(nucleiImages: np.ndarray, watershedMarkers: np.ndarray) -> np.ndarray: """Perform watershed using cv2 @@ -348,7 +348,7 @@ def apply_cv2_watershed(self, nucleiImages: np.ndarray, return watershedOutput -def get_overlapping_nuclei(self, watershedZ0: np.ndarray, +def get_overlapping_nuclei(watershedZ0: np.ndarray, watershedZ1: np.ndarray, n0: int): """Perform watershed using cv2 @@ -402,8 +402,7 @@ def get_overlapping_nuclei(self, watershedZ0: np.ndarray, return False, False, False -def combine_2d_segmentation_masks_into_3d(self, - watershedOutput: +def combine_2d_segmentation_masks_into_3d(watershedOutput: np.ndarray) -> np.ndarray: """Take a 3 dimensional watershed masks and relabel them so that nuclei in adjacent sections have the same label if the area their From 70b65918102042a7b85a13002e903b47bb757de5 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 3 Mar 2020 09:02:53 -0500 Subject: [PATCH 190/419] remove self calls --- merlin/util/watershed.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 0ccac583..60268345 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -11,7 +11,7 @@ from merlin.util import matlab """ -This module contains utility functions for preparing imagmes for +This module contains utility functions for preparing imagmes for watershed segmentation. """ @@ -155,7 +155,7 @@ def get_membrane_mask(membraneImages: np.ndarray) -> np.ndarray: """ mask = np.zeros(membraneImages.shape) fineBlockSize = 61 - for z in range(len(self.dataSet.get_z_positions())): + for z in range(membraneImages.shape[0]): mask[z, :, :] = (membraneImages[z, :, :] > filters.threshold_local(membraneImages[z, :, :], fineBlockSize, @@ -187,7 +187,7 @@ def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: thresholdingMask = np.zeros(nucleiImages.shape) coarseBlockSize = 241 fineBlockSize = 61 - for z in range(len(self.dataSet.get_z_positions())): + for z in range(nucleiImages.shape[0]): coarseThresholdingMask = (nucleiImages[z, :, :] > filters.threshold_local( nucleiImages[z, :, :], @@ -212,7 +212,7 @@ def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: # generate nuclei mask from hessian, fine fineHessianMask = np.zeros(nucleiImages.shape) - for z in range(len(self.dataSet.get_z_positions())): + for z in range(nucleiImages.shape[0]): fineHessian = filters.hessian(nucleiImages[z, :, :]) fineHessianMask[z, :, :] = fineHessian == fineHessian.max() fineHessianMask[z, :, :] = morphology.binary_closing( @@ -224,7 +224,7 @@ def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: # generate dapi mask from hessian, coarse coarseHessianMask = np.zeros(nucleiImages.shape) - for z in range(len(self.dataSet.get_z_positions())): + for z in range(nucleiImages.shape[0]): coarseHessian = filters.hessian(nucleiImages[z, :, :] - morphology.white_tophat( nucleiImages[z, :, :], @@ -257,12 +257,12 @@ def get_cv2_watershed_markers(nucleiImages: np.ndarray, cv2-compatible watershed markers """ - nucleiMask = self.get_nuclei_mask(nucleiImages) - membraneMask = self.get_membrane_mask(membraneImages) + nucleiMask = get_nuclei_mask(nucleiImages) + membraneMask = get_membrane_mask(membraneImages) watershedMarker = np.zeros(nucleiMask.shape) - for z in range(len(self.dataSet.get_z_positions())): + for z in range(nucleiImages.shape[0]): # generate areas of sure bg and fg, as well as the area of # unknown classification @@ -338,8 +338,8 @@ def apply_cv2_watershed(nucleiImages: np.ndarray, """ watershedOutput = np.zeros(watershedMarkers.shape) - for z in range(len(self.dataSet.get_z_positions())): - rgbImage = self.convert_grayscale_to_rgb(nucleiImages[z, :, :]) + for z in range(nucleiImages.shape[0]): + rgbImage = convert_grayscale_to_rgb(nucleiImages[z, :, :]) watershedOutput[z, :, :] = cv2.watershed(rgbImage, watershedMarkers[z, :, :]. astype('int32')) @@ -423,13 +423,12 @@ def combine_2d_segmentation_masks_into_3d(watershedOutput: watershedCombinedZ[-1, :, :] = watershedOutput[-1, :, :] # starting far from coverslip - for z in range(len(self.dataSet.get_z_positions())-1, 0, -1): + for z in range(watershedOutput.shape[0]-1, 0, -1): zNucleiIndex = np.unique(watershedOutput[z, :, :])[ np.unique(watershedOutput[z, :, :]) > 100] for n0 in zNucleiIndex: - n1, f0, f1 = self.get_overlapping_nuclei( - watershedCombinedZ[z, :, :], + n1, f0, f1 = get_overlapping_nuclei(watershedCombinedZ[z, :, :], watershedOutput[z-1, :, :], n0) if n1: From f134a3ebd7f26cdfbc2081a8e7d93139d3cf8347 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 3 Mar 2020 10:50:52 -0500 Subject: [PATCH 191/419] pep8 compliance --- merlin/analysis/segment.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 52a9f517..d982a9ab 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -202,15 +202,15 @@ def _run_analysis(self, fragmentIndex): print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) print(" membraneImages Type: " + str(type(membraneImages))) - print(" membraneImages Size: [" - + str(membraneImages.shape[0]) - + "," + str(membraneImages.shape[1]) - + "," + str(membraneImages.shape[2]) + "]" ) + print(" membraneImages Size: [" + + str(membraneImages.shape[0]) + + "," + str(membraneImages.shape[1]) + + "," + str(membraneImages.shape[2]) + "]") print(" nucleiImages Type: " + str(type(nucleiImages))) - print(" nucleiImages Size: [" - + str(nucleiImages.shape[0]) - + "," + str(nucleiImages.shape[1]) - + "," + str(nucleiImages.shape[2]) + "]" ) + print(" nucleiImages Size: [" + + str(nucleiImages.shape[0]) + + "," + str(nucleiImages.shape[1]) + + "," + str(nucleiImages.shape[2]) + "]") # Prepare masks for cv2 watershed watershedMarkers = watershed.get_cv2_watershed_markers(nucleiImages, From 493e72d861b123ad53fc9812665eb0b9c14d7ecf Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 22 Apr 2020 21:44:41 -0400 Subject: [PATCH 192/419] change WatershedSegmentNucleiCV2 to WatershedSegmentCV2, modify accordingly to allow for cytoplasmic or nuclei segmentation --- merlin/analysis/segment.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index d982a9ab..3534b599 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -124,7 +124,7 @@ def _read_and_filter_image_stack(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions()))]) -class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): +class WatershedSegmentCV2(FeatureSavingAnalysisTask): """ An analysis task that determines the boundaries of features in the @@ -136,7 +136,12 @@ class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): py_tutorials/py_imgproc/py_watershed/py_watershed.html. The watershed segmentation is performed in each z-position - independently and combined into 3D objects in a later step + independently and combined into 3D objects in a later step + + The class can be used to segment either nuclear or cytoplasmic + compartments. If both the compartment and membrane channels are the + same, the membrane channel is calculated from the edge transform of + the provided channel. Since each field of view is analyzed individually, the segmentation results should be cleaned in order to merge cells that cross the @@ -145,11 +150,11 @@ class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): def __init__(self, dataSet, parameters=None, analysisName=None): super().__init__(dataSet, parameters, analysisName) - + if 'membrane_channel_name' not in self.parameters: - self.parameters['membrane_channel_name'] = 'ConA' - if 'nuclei_channel_name' not in self.parameters: - self.parameters['nuclei_channel_name'] = 'DAPI' + self.parameters['membrane_channel_name'] = 'DAPI' + if 'compartment_channel_name' not in self.parameters: + self.parameters['compartment_channel_name'] = 'DAPI' def fragment_count(self): return len(self.dataSet.get_fovs()) From 1a8d8e53d135a703219b19d418e2f8c5e45912d6 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 22 Apr 2020 21:57:33 -0400 Subject: [PATCH 193/419] change nuclei to compartment --- merlin/analysis/segment.py | 43 ++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 3534b599..8dee98f1 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -138,9 +138,9 @@ class WatershedSegmentCV2(FeatureSavingAnalysisTask): The watershed segmentation is performed in each z-position independently and combined into 3D objects in a later step - The class can be used to segment either nuclear or cytoplasmic - compartments. If both the compartment and membrane channels are the - same, the membrane channel is calculated from the edge transform of + The class can be used to segment either nuclear or cytoplasmic + compartments. If both the compartment and membrane channels are the + same, the membrane channel is calculated from the edge transform of the provided channel. Since each field of view is analyzed individually, the segmentation @@ -185,23 +185,30 @@ def _run_analysis(self, fragmentIndex): print(' globalTask loaded') - # read membrane (seed) and nuclei (watershed) indexes + # read membrane and compartment indexes membraneIndex = self.dataSet \ .get_data_organization() \ .get_data_channel_index( self.parameters['membrane_channel_name']) - nucleiIndex = self.dataSet \ - .get_data_organization() \ - .get_data_channel_index( - self.parameters['nuclei_channel_name']) + compartmentIndex = self.dataSet \ + .get_data_organization() \ + .get_data_channel_index( + self.parameters['compartment_channel_name']) + + if self.parameters['membrane_channel_name'] == + self.parameters['compartment_channel_name']: + membraneFlag = 0 + else: + membraneFlag = 1 endTime = time.time() print(" image indexes read, ET {:.2f} min".format( (endTime - startTime) / 60)) - # read membrane (seed) and nuclei (watershed) images + # read membrane and compartment images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) - nucleiImages = self._read_image_stack(fragmentIndex, nucleiIndex) + compartmentImages = self._read_image_stack(fragmentIndex, + compartmentIndex) endTime = time.time() print(" images read, ET {:.2f} min".format( @@ -211,22 +218,22 @@ def _run_analysis(self, fragmentIndex): + str(membraneImages.shape[0]) + "," + str(membraneImages.shape[1]) + "," + str(membraneImages.shape[2]) + "]") - print(" nucleiImages Type: " + str(type(nucleiImages))) - print(" nucleiImages Size: [" - + str(nucleiImages.shape[0]) - + "," + str(nucleiImages.shape[1]) - + "," + str(nucleiImages.shape[2]) + "]") + print(" compartmentImages Type: " + str(type(compartmentImages))) + print(" compartmentImages Size: [" + + str(compartmentImages.shape[0]) + + "," + str(compartmentImages.shape[1]) + + "," + str(compartmentImages.shape[2]) + "]") # Prepare masks for cv2 watershed - watershedMarkers = watershed.get_cv2_watershed_markers(nucleiImages, - membraneImages) + watershedMarkers = watershed.get_cv2_watershed_markers( + compartmentImages, membraneImages) endTime = time.time() print(" markers calculated, ET {:.2f} min".format( (endTime - startTime) / 60)) # perform watershed in individual z positions - watershedOutput = watershed.apply_cv2_watershed(nucleiImages, + watershedOutput = watershed.apply_cv2_watershed(compartmentImages, watershedMarkers) endTime = time.time() From 39b5f060feb75eafc6d64d5ec0578c603a3ae988 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 22 Apr 2020 22:08:40 -0400 Subject: [PATCH 194/419] change nuclei to compartment --- merlin/analysis/segment.py | 4 ++- merlin/util/watershed.py | 56 ++++++++++++++++++++------------------ 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 8dee98f1..d80d8ec3 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -226,7 +226,9 @@ def _run_analysis(self, fragmentIndex): # Prepare masks for cv2 watershed watershedMarkers = watershed.get_cv2_watershed_markers( - compartmentImages, membraneImages) + compartmentImages, + membraneImages, + membraneFlag) endTime = time.time() print(" markers calculated, ET {:.2f} min".format( diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 60268345..16bf72bd 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -242,36 +242,39 @@ def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: return binary_fill_holes(nucleiMask) -def get_cv2_watershed_markers(nucleiImages: np.ndarray, - membraneImages: np.ndarray) -> np.ndarray: - """Combine membrane and nuclei markers into a single multilabel mask +def get_cv2_watershed_markers(compartmentImages: np.ndarray, + membraneImages: np.ndarray, + membraneFlag: int) -> np.ndarray: + """Combine membrane and compartment markers into a single multilabel mask for CV2 watershed Args: - nucleiImages: a 3 dimensional numpy array containing the images + compartmentImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). + membraneFlag: 0 if compartment and membrane images are the same, 1 + otherwise Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) of cv2-compatible watershed markers """ - nucleiMask = get_nuclei_mask(nucleiImages) + compartmentMask = get_nuclei_mask(compartmentImages) membraneMask = get_membrane_mask(membraneImages) - watershedMarker = np.zeros(nucleiMask.shape) + watershedMarker = np.zeros(compartmentMask.shape) - for z in range(nucleiImages.shape[0]): + for z in range(compartmentImages.shape[0]): # generate areas of sure bg and fg, as well as the area of # unknown classification - background = morphology.dilation(nucleiMask[z, :, :], + background = morphology.dilation(compartmentMask[z, :, :], morphology.selem.disk(15)) membraneDilated = morphology.dilation( membraneMask[z, :, :].astype('bool'), morphology.selem.disk(10)) - foreground = morphology.erosion(nucleiMask[z, :, :] * ~ + foreground = morphology.erosion(compartmentMask[z, :, :] * ~ membraneDilated, morphology.selem.disk(5)) unknown = background * ~ foreground @@ -322,12 +325,12 @@ def convert_grayscale_to_rgb(uint16Image: np.ndarray) -> np.ndarray: return rgbImage -def apply_cv2_watershed(nucleiImages: np.ndarray, +def apply_cv2_watershed(compartmentImages: np.ndarray, watershedMarkers: np.ndarray) -> np.ndarray: """Perform watershed using cv2 Args: - nucleiImages: a 3 dimensional numpy array containing the images + compartmentImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). watershedMarkers: a 3 dimensional numpy array containing the cv2 markers arranged as (z, x, y). @@ -339,7 +342,7 @@ def apply_cv2_watershed(nucleiImages: np.ndarray, watershedOutput = np.zeros(watershedMarkers.shape) for z in range(nucleiImages.shape[0]): - rgbImage = convert_grayscale_to_rgb(nucleiImages[z, :, :]) + rgbImage = convert_grayscale_to_rgb(compartmentImages[z, :, :]) watershedOutput[z, :, :] = cv2.watershed(rgbImage, watershedMarkers[z, :, :]. astype('int32')) @@ -348,7 +351,7 @@ def apply_cv2_watershed(nucleiImages: np.ndarray, return watershedOutput -def get_overlapping_nuclei(watershedZ0: np.ndarray, +def get_overlapping_objects(watershedZ0: np.ndarray, watershedZ1: np.ndarray, n0: int): """Perform watershed using cv2 @@ -357,26 +360,27 @@ def get_overlapping_nuclei(watershedZ0: np.ndarray, segmentation mask watershedZ1: a 2 dimensional numpy array containing a segmentation mask adjacent to watershedZ1 - n0: an integer with the index of the cell/nuclei to be compared - between the provided watershed segmentation masks + n0: an integer with the index of the object (cell/nuclei) + to be compared between the provided watershed + segmentation masks Returns: a tuple (n1, f0, f1) containing the label of the cell in Z1 overlapping n0 (n1), the fraction of n0 overlaping n1 (f0) and the fraction of n1 overlapping n0 (f1) """ - z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) - z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes > 100] + z1Indexes = np.unique(watershedZ1[watershedZ0 == n0]) + z1Indexes = z1Indexes[z1NucleiIndexes > 100] - if z1NucleiIndexes.shape[0] > 0: + if z1Indexes.shape[0] > 0: # calculate overlap fraction n0Area = np.count_nonzero(watershedZ0 == n0) - n1Area = np.zeros(len(z1NucleiIndexes)) - overlapArea = np.zeros(len(z1NucleiIndexes)) + n1Area = np.zeros(len(z1Indexes)) + overlapArea = np.zeros(len(z1Indexes)) - for ii in range(len(z1NucleiIndexes)): - n1 = z1NucleiIndexes[ii] + for ii in range(len(z1Indexes)): + n1 = z1Indexes[ii] n1Area[ii] = np.count_nonzero(watershedZ1 == n1) overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * (watershedZ1 == n1)) @@ -393,7 +397,7 @@ def get_overlapping_nuclei(watershedZ0: np.ndarray, if (n0OverlapFraction[indexSorted[0]] > 0.2 and n1OverlapFraction[indexSorted[0]] > 0.5): - return z1NucleiIndexes[indexSorted[0]], + return z1Indexes[indexSorted[0]], n0OverlapFraction[indexSorted[0]], n1OverlapFraction[indexSorted[0]] else: @@ -428,9 +432,9 @@ def combine_2d_segmentation_masks_into_3d(watershedOutput: np.unique(watershedOutput[z, :, :]) > 100] for n0 in zNucleiIndex: - n1, f0, f1 = get_overlapping_nuclei(watershedCombinedZ[z, :, :], - watershedOutput[z-1, :, :], - n0) + n1, f0, f1 = get_overlapping_objects(watershedCombinedZ[z, :, :], + watershedOutput[z-1, :, :], + n0) if n1: watershedCombinedZ[z-1, :, :][(watershedOutput[z-1, :, :] == n1)] = n0 From 7e94d77f32ab54106325088c6d3800509fb133d0 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 22 Apr 2020 22:10:53 -0400 Subject: [PATCH 195/419] added framework for MachineLearningSegment class --- merlin/analysis/segment.py | 147 +++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index d80d8ec3..3bf3e4c4 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -271,6 +271,153 @@ def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: return np.array([warpTask.get_aligned_image(fov, channelIndex, z) for z in range(len(self.dataSet.get_z_positions()))]) +class MachineLearningSegment(FeatureSavingAnalysisTask): + + """ + An analysis task that determines the boundaries of features in the + image data in each field of view using a watershed algorithm + implemented in CV2. + + A tutorial explaining the general scheme of the method can be + found in https://opencv-python-tutroals.readthedocs.io/en/latest/ + py_tutorials/py_imgproc/py_watershed/py_watershed.html. + + The watershed segmentation is performed in each z-position + independently and combined into 3D objects in a later step + + The class can be used to segment either nuclear or cytoplasmic + compartments. If both the compartment and membrane channels are the + same, the membrane channel is calculated from the edge transform of + the provided channel. + + Since each field of view is analyzed individually, the segmentation + results should be cleaned in order to merge cells that cross the + field of view boundary. + """ + + def __init__(self, dataSet, parameters=None, analysisName=None): + super().__init__(dataSet, parameters, analysisName) + + if 'membrane_channel_name' not in self.parameters: + self.parameters['membrane_channel_name'] = 'DAPI' + if 'compartment_channel_name' not in self.parameters: + self.parameters['compartment_channel_name'] = 'DAPI' + + def fragment_count(self): + return len(self.dataSet.get_fovs()) + + def get_estimated_memory(self): + # TODO - refine estimate + return 2048 + + def get_estimated_time(self): + # TODO - refine estimate + return 5 + + def get_dependencies(self): + return [self.parameters['warp_task'], + self.parameters['global_align_task']] + + def get_cell_boundaries(self) -> List[spatialfeature.SpatialFeature]: + featureDB = self.get_feature_database() + return featureDB.read_features() + + def _run_analysis(self, fragmentIndex): + startTime = time.time() + + print('Entered the _run_analysis method, FOV ' + str(fragmentIndex)) + + globalTask = self.dataSet.load_analysis_task( + self.parameters['global_align_task']) + + print(' globalTask loaded') + + # read membrane and compartment indexes + membraneIndex = self.dataSet \ + .get_data_organization() \ + .get_data_channel_index( + self.parameters['membrane_channel_name']) + compartmentIndex = self.dataSet \ + .get_data_organization() \ + .get_data_channel_index( + self.parameters['compartment_channel_name']) + + if self.parameters['membrane_channel_name'] == + self.parameters['compartment_channel_name']: + membraneFlag = 0 + else: + membraneFlag = 1 + + endTime = time.time() + print(" image indexes read, ET {:.2f} min".format( + (endTime - startTime) / 60)) + + # read membrane and compartment images + membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) + compartmentImages = self._read_image_stack(fragmentIndex, + compartmentIndex) + + endTime = time.time() + print(" images read, ET {:.2f} min".format( + (endTime - startTime) / 60)) + print(" membraneImages Type: " + str(type(membraneImages))) + print(" membraneImages Size: [" + + str(membraneImages.shape[0]) + + "," + str(membraneImages.shape[1]) + + "," + str(membraneImages.shape[2]) + "]") + print(" compartmentImages Type: " + str(type(compartmentImages))) + print(" compartmentImages Size: [" + + str(compartmentImages.shape[0]) + + "," + str(compartmentImages.shape[1]) + + "," + str(compartmentImages.shape[2]) + "]") + + # Prepare masks for cv2 watershed + watershedMarkers = watershed.get_cv2_watershed_markers( + compartmentImages, + membraneImages, + membraneFlag) + + endTime = time.time() + print(" markers calculated, ET {:.2f} min".format( + (endTime - startTime) / 60)) + + # perform watershed in individual z positions + watershedOutput = watershed.apply_cv2_watershed(compartmentImages, + watershedMarkers) + + endTime = time.time() + print(" watershed calculated, ET {:.2f} min".format( + (endTime - startTime) / 60)) + + # combine all z positions in watershed + watershedCombinedOutput = watershed \ + .combine_2d_segmentation_masks_into_3d(watershedOutput) + + endTime = time.time() + print(" watershed z positions combined, ET {:.2f} min".format( + (endTime - startTime) / 60)) + + # get features from mask. This is the slowestart (6 min for the + # previous part, 15+ for the rest, for a 7 frame Image. + zPos = np.array(self.dataSet.get_data_organization().get_z_positions()) + featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( + (watershedCombinedOutput == i), fragmentIndex, + globalTask.fov_to_global_transform(fragmentIndex), zPos) + for i in np.unique(watershedOutput) if i != 0] + + featureDB = self.get_feature_database() + featureDB.write_features(featureList, fragmentIndex) + + endTime = time.time() + print(" features written, ET {:.2f} min".format( + (endTime - startTime) / 60)) + + def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: + warpTask = self.dataSet.load_analysis_task( + self.parameters['warp_task']) + return np.array([warpTask.get_aligned_image(fov, channelIndex, z) + for z in range(len(self.dataSet.get_z_positions()))]) + class CleanCellBoundaries(analysistask.ParallelAnalysisTask): ''' From bdcaa7a69a39317951d23731b0adf2747afce829 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Apr 2020 12:02:57 -0400 Subject: [PATCH 196/419] adding mls utils function --- merlin/analysis/segment.py | 43 ++++++++++++++-------- merlin/util/machinelearningsegmentation.py | 11 ++++++ 2 files changed, 38 insertions(+), 16 deletions(-) create mode 100755 merlin/util/machinelearningsegmentation.py diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 3bf3e4c4..50338905 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -275,8 +275,15 @@ class MachineLearningSegment(FeatureSavingAnalysisTask): """ An analysis task that determines the boundaries of features in the - image data in each field of view using a watershed algorithm - implemented in CV2. + image data in each field of view using a the specified machine learning + method. The available methods are: + + unet: + + ilastik: + + cellpose: + A tutorial explaining the general scheme of the method can be found in https://opencv-python-tutroals.readthedocs.io/en/latest/ @@ -298,8 +305,8 @@ class MachineLearningSegment(FeatureSavingAnalysisTask): def __init__(self, dataSet, parameters=None, analysisName=None): super().__init__(dataSet, parameters, analysisName) - if 'membrane_channel_name' not in self.parameters: - self.parameters['membrane_channel_name'] = 'DAPI' + if 'method' not in self.parameters: + self.parameters['method'] = 'ilastik' if 'compartment_channel_name' not in self.parameters: self.parameters['compartment_channel_name'] = 'DAPI' @@ -326,6 +333,7 @@ def _run_analysis(self, fragmentIndex): startTime = time.time() print('Entered the _run_analysis method, FOV ' + str(fragmentIndex)) + print('Using ' + self.parameters['method'] + ' method.') globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) @@ -333,30 +341,32 @@ def _run_analysis(self, fragmentIndex): print(' globalTask loaded') # read membrane and compartment indexes - membraneIndex = self.dataSet \ - .get_data_organization() \ - .get_data_channel_index( - self.parameters['membrane_channel_name']) compartmentIndex = self.dataSet \ .get_data_organization() \ .get_data_channel_index( self.parameters['compartment_channel_name']) - if self.parameters['membrane_channel_name'] == - self.parameters['compartment_channel_name']: - membraneFlag = 0 - else: - membraneFlag = 1 - endTime = time.time() print(" image indexes read, ET {:.2f} min".format( (endTime - startTime) / 60)) - # read membrane and compartment images - membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) + # Read images and perform segmentation compartmentImages = self._read_image_stack(fragmentIndex, compartmentIndex) + endTime = time.time() + print(" images read, ET {:.2f} min".format( + (endTime - startTime) / 60)) + + segmentationOutput = machinelearningsegmentation. + apply_machine_learning_segmentation( + compartmentImages, + self.parameters['method']) + + endTime = time.time() + print(" Segmentation finished, ET {:.2f} min".format( + (endTime - startTime) / 60)) +""" endTime = time.time() print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) @@ -396,6 +406,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" watershed z positions combined, ET {:.2f} min".format( (endTime - startTime) / 60)) +""" # get features from mask. This is the slowestart (6 min for the # previous part, 15+ for the rest, for a 7 frame Image. diff --git a/merlin/util/machinelearningsegmentation.py b/merlin/util/machinelearningsegmentation.py new file mode 100755 index 00000000..f882a06e --- /dev/null +++ b/merlin/util/machinelearningsegmentation.py @@ -0,0 +1,11 @@ +import numpy as np +import cv2 +from scipy import ndimage +from scipy.ndimage.morphology import binary_fill_holes +from skimage import morphology +from skimage import filters +from skimage import measure +from pyclustering.cluster import kmedoids +from typing import Tuple + +from merlin.util import matlab \ No newline at end of file From 23f6cb4ea2eaa8e300ff4e62fdb6e425923ce443 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Apr 2020 12:19:53 -0400 Subject: [PATCH 197/419] add function definitions to mls.py --- merlin/util/machinelearningsegmentation.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/merlin/util/machinelearningsegmentation.py b/merlin/util/machinelearningsegmentation.py index f882a06e..fbc05f0e 100755 --- a/merlin/util/machinelearningsegmentation.py +++ b/merlin/util/machinelearningsegmentation.py @@ -8,4 +8,19 @@ from pyclustering.cluster import kmedoids from typing import Tuple -from merlin.util import matlab \ No newline at end of file +from merlin.util import matlab + +""" +This module contains utility functions for preparing imagmes for performing +segmentation using machine learning approaches +""" + +def apply_machine_learning_segmentation(imageStackIn: np.ndarray, + method: str) -> np.ndarray: + +def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: + +def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: + +def segment_using_cellpose(imageStackIn: np.ndarray) -> np.ndarray: + From a0b75ebb72d5da8f99246dec9620f0c89e78499e Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Apr 2020 22:16:23 -0400 Subject: [PATCH 198/419] add body of apply_machine_learning_segmentation function --- merlin/analysis/segment.py | 16 +------------- merlin/util/machinelearningsegmentation.py | 25 +++++++++++++++++++--- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 50338905..29778cd5 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -284,22 +284,8 @@ class MachineLearningSegment(FeatureSavingAnalysisTask): cellpose: + TODO: ADD FLAT FIELD CORRECTION TASK - A tutorial explaining the general scheme of the method can be - found in https://opencv-python-tutroals.readthedocs.io/en/latest/ - py_tutorials/py_imgproc/py_watershed/py_watershed.html. - - The watershed segmentation is performed in each z-position - independently and combined into 3D objects in a later step - - The class can be used to segment either nuclear or cytoplasmic - compartments. If both the compartment and membrane channels are the - same, the membrane channel is calculated from the edge transform of - the provided channel. - - Since each field of view is analyzed individually, the segmentation - results should be cleaned in order to merge cells that cross the - field of view boundary. """ def __init__(self, dataSet, parameters=None, analysisName=None): diff --git a/merlin/util/machinelearningsegmentation.py b/merlin/util/machinelearningsegmentation.py index fbc05f0e..9a0076c4 100755 --- a/merlin/util/machinelearningsegmentation.py +++ b/merlin/util/machinelearningsegmentation.py @@ -13,14 +13,33 @@ """ This module contains utility functions for preparing imagmes for performing segmentation using machine learning approaches +MAYBE COMBINE WITH WATERSHED.PY INTO A SINGLE FILE, SEGMENTATION.PY """ +def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: + +def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: + +def segment_using_cellpose(imageStackIn: np.ndarray) -> np.ndarray: + def apply_machine_learning_segmentation(imageStackIn: np.ndarray, method: str) -> np.ndarray: + """Calculate binary mask with 1's in membrane pixels and 0 otherwise. + The images expected are some type of Nuclei label (e.g. DAPI) -def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: + Args: + membraneImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) + """ + if method == 'ilastik': + segmentOutput = segment_using_ilastik(imageStackIn) + elif method == 'cellpose': + segmentOutput = segment_using_cellpose(imageStackIn) + elif method == 'unet' + segmentOutput = segment_using_unet(imageStackIn) -def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: + return segmentOutput -def segment_using_cellpose(imageStackIn: np.ndarray) -> np.ndarray: From 8f12840b63af8fdfe5cdeac33e5df1a1da059f47 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Apr 2020 16:48:14 -0400 Subject: [PATCH 199/419] change nuclei to compartment in cv2 segmentation functions --- merlin/util/machinelearningsegmentation.py | 5 +- merlin/util/watershed.py | 55 +++++++++++----------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/merlin/util/machinelearningsegmentation.py b/merlin/util/machinelearningsegmentation.py index 9a0076c4..e11ab397 100755 --- a/merlin/util/machinelearningsegmentation.py +++ b/merlin/util/machinelearningsegmentation.py @@ -24,11 +24,10 @@ def segment_using_cellpose(imageStackIn: np.ndarray) -> np.ndarray: def apply_machine_learning_segmentation(imageStackIn: np.ndarray, method: str) -> np.ndarray: - """Calculate binary mask with 1's in membrane pixels and 0 otherwise. - The images expected are some type of Nuclei label (e.g. DAPI) + """Calculate Args: - membraneImages: a 3 dimensional numpy array containing the images + imageStackIn: a 3 dimensional numpy array containing the images arranged as (z, x, y). Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 16bf72bd..6e57d1e5 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -172,30 +172,31 @@ def get_membrane_mask(membraneImages: np.ndarray) -> np.ndarray: return mask -def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: - """Calculate binary mask with 1's in membrane pixels and 0 otherwise. - The images expected are some type of Nuclei label (e.g. DAPI) +def get_compartment_mask(compartmentImages: np.ndarray) -> np.ndarray: + """Calculate binary mask with 1's in compartment (nuclei or cytoplasm) + pixels and 0 otherwise. The images expected are some type of compartment + label (e.g. Nuclei: DAPI, Cytoplasm: PolyT, CD45, etc) Args: - membraneImages: a 3 dimensional numpy array containing the images + compartmentImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) """ - # generate nuclei mask based on thresholding - thresholdingMask = np.zeros(nucleiImages.shape) + # generate compartment mask based on thresholding + thresholdingMask = np.zeros(compartmentImages.shape) coarseBlockSize = 241 fineBlockSize = 61 - for z in range(nucleiImages.shape[0]): - coarseThresholdingMask = (nucleiImages[z, :, :] > + for z in range(compartmentImages.shape[0]): + coarseThresholdingMask = (compartmentImages[z, :, :] > filters.threshold_local( - nucleiImages[z, :, :], + compartmentImages[z, :, :], coarseBlockSize, offset=0)) - fineThresholdingMask = (nucleiImages[z, :, :] > + fineThresholdingMask = (compartmentImages[z, :, :] > filters.threshold_local( - nucleiImages[z, :, :], + compartmentImages[z, :, :], fineBlockSize, offset=0)) thresholdingMask[z, :, :] = (coarseThresholdingMask * @@ -205,15 +206,15 @@ def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: # generate border mask, necessary to avoid making a single # connected component when using binary_fill_holes below - borderMask = np.zeros((2048, 2048)) - borderMask[25:2023, 25:2023] = 1 + borderMask = np.zeros((compartmentImages.shape[1], + compartmentImages.shape[2])) + borderMask[25:(compartmentImages.shape[1]-25), + 25:(compartmentImages.shape[2]-25)] = 1 - # TODO - use the image size variable for borderMask - - # generate nuclei mask from hessian, fine - fineHessianMask = np.zeros(nucleiImages.shape) - for z in range(nucleiImages.shape[0]): - fineHessian = filters.hessian(nucleiImages[z, :, :]) + # generate compartment mask from hessian, fine + fineHessianMask = np.zeros(compartmentImages.shape) + for z in range(compartmentImages.shape[0]): + fineHessian = filters.hessian(compartmentImages[z, :, :]) fineHessianMask[z, :, :] = fineHessian == fineHessian.max() fineHessianMask[z, :, :] = morphology.binary_closing( fineHessianMask[z, :, :], @@ -222,12 +223,12 @@ def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: fineHessianMask[z, :, :] = binary_fill_holes( fineHessianMask[z, :, :]) - # generate dapi mask from hessian, coarse - coarseHessianMask = np.zeros(nucleiImages.shape) - for z in range(nucleiImages.shape[0]): - coarseHessian = filters.hessian(nucleiImages[z, :, :] - + # generate compartment mask from hessian, coarse + coarseHessianMask = np.zeros(comapartImages.shape) + for z in range(compartmentImages.shape[0]): + coarseHessian = filters.hessian(compartmentImages[z, :, :] - morphology.white_tophat( - nucleiImages[z, :, :], + compartmentImages[z, :, :], morphology.selem.disk(20))) coarseHessianMask[z, :, :] = coarseHessian == coarseHessian.max() coarseHessianMask[z, :, :] = morphology.binary_closing( @@ -238,8 +239,8 @@ def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: coarseHessianMask[z, :, :]) # combine masks - nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask - return binary_fill_holes(nucleiMask) + compartmentMask = thresholdingMask + fineHessianMask + coarseHessianMask + return binary_fill_holes(compartmentMask) def get_cv2_watershed_markers(compartmentImages: np.ndarray, @@ -260,7 +261,7 @@ def get_cv2_watershed_markers(compartmentImages: np.ndarray, cv2-compatible watershed markers """ - compartmentMask = get_nuclei_mask(compartmentImages) + compartmentMask = get_compartment_mask(compartmentImages) membraneMask = get_membrane_mask(membraneImages) watershedMarker = np.zeros(compartmentMask.shape) From 4fac64173e6654ba67af452df736183068f155b1 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Apr 2020 17:34:33 -0400 Subject: [PATCH 200/419] adding edge memebrane calculation to CV2 segmenetation --- merlin/analysis/segment.py | 8 +-- merlin/util/machinelearningsegmentation.py | 3 +- merlin/util/watershed.py | 78 ++++++++++++++++------ 3 files changed, 59 insertions(+), 30 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 29778cd5..13a7e495 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -195,12 +195,6 @@ def _run_analysis(self, fragmentIndex): .get_data_channel_index( self.parameters['compartment_channel_name']) - if self.parameters['membrane_channel_name'] == - self.parameters['compartment_channel_name']: - membraneFlag = 0 - else: - membraneFlag = 1 - endTime = time.time() print(" image indexes read, ET {:.2f} min".format( (endTime - startTime) / 60)) @@ -228,7 +222,7 @@ def _run_analysis(self, fragmentIndex): watershedMarkers = watershed.get_cv2_watershed_markers( compartmentImages, membraneImages, - membraneFlag) + self.parameters['membrane_channel_name']) endTime = time.time() print(" markers calculated, ET {:.2f} min".format( diff --git a/merlin/util/machinelearningsegmentation.py b/merlin/util/machinelearningsegmentation.py index e11ab397..085fa4df 100755 --- a/merlin/util/machinelearningsegmentation.py +++ b/merlin/util/machinelearningsegmentation.py @@ -24,8 +24,7 @@ def segment_using_cellpose(imageStackIn: np.ndarray) -> np.ndarray: def apply_machine_learning_segmentation(imageStackIn: np.ndarray, method: str) -> np.ndarray: - """Calculate - + """Select segmentation algorithm to use Args: imageStackIn: a 3 dimensional numpy array containing the images arranged as (z, x, y). diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 6e57d1e5..bdfd2d1b 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -5,6 +5,7 @@ from skimage import morphology from skimage import filters from skimage import measure +from skimage import feature from pyclustering.cluster import kmedoids from typing import Tuple @@ -142,33 +143,68 @@ def prepare_watershed_images(watershedImageStack: np.ndarray return normalizedWatershed, watershedMask -def get_membrane_mask(membraneImages: np.ndarray) -> np.ndarray: +def get_membrane_mask(membraneImages: np.ndarray, + membraneChannelName: str, + compartmentChannelName: str) -> np.ndarray: """Calculate binary mask with 1's in membrane pixels and 0 otherwise. The images expected are some type of membrane label (WGA, ConA, - Lamin, Cadherins) + Lamin, Cadherins) or compartment images (DAPI, CD45, polyT) Args: membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). + membraneChannelName: A string with the name of a membrane channel. + compartmentChannelName: A string with the name of the compartment + channel Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) """ mask = np.zeros(membraneImages.shape) - fineBlockSize = 61 - for z in range(membraneImages.shape[0]): - mask[z, :, :] = (membraneImages[z, :, :] > - filters.threshold_local(membraneImages[z, :, :], - fineBlockSize, - offset=0)) - mask[z, :, :] = morphology.remove_small_objects( - mask[z, :, :].astype('bool'), - min_size=100, - connectivity=1) - mask[z, :, :] = morphology.binary_closing(mask[z, :, :], - morphology.selem.disk(5)) - mask[z, :, :] = morphology.skeletonize(mask[z, :, :]) - - # combine masks + if membraneChannelName != compartmentChannelName: + fineBlockSize = 61 + for z in range(membraneImages.shape[0]): + mask[z, :, :] = (membraneImages[z, :, :] > + filters.threshold_local(membraneImages[z, :, :], + fineBlockSize, + offset=0)) + mask[z, :, :] = morphology.remove_small_objects( + mask[z, :, :].astype('bool'), + min_size=100, + connectivity=1) + mask[z, :, :] = morphology.binary_closing(mask[z, :, :], + morphology.selem.disk(5)) + mask[z, :, :] = morphology.skeletonize(mask[z, :, :]) + else: + filterSigma2 = 5 + filterSize2 = int(2*np.ceil(2*filterSigma2)+1) + edgeSigma = 2 #1 #2 + lowThresh = 0.1 #0.5 #0.2 + hiThresh = 0.5 #0.7 #0.6 + for z in range(membraneImages.shape[0]): + blurredImage = cv2.GaussianBlur(img[:,:,1], + (filterSize2,filterSize2), + filterSigma2) + edge0 = feature.canny(membraneImages[z, :, :], + sigma=edgeSigma, + use_quantiles=True, + low_threshold=lowThresh, + high_threshold=hiThresh) + edge0 = morphology.remove_small_objects( + mask[z, :, :].astype('bool'), + min_size=20, + connectivity=1) + edge1 = feature.canny(blurredImage, + sigma=edgeSigma, + use_quantiles=True, + low_threshold=lowThresh, + high_threshold=hiThresh) + edge1 = morphology.remove_small_objects( + mask[z, :, :].astype('bool'), + min_size=20, + connectivity=1) + + mask[z, :, :] = edge0 + edge1 + return mask @@ -262,7 +298,7 @@ def get_cv2_watershed_markers(compartmentImages: np.ndarray, """ compartmentMask = get_compartment_mask(compartmentImages) - membraneMask = get_membrane_mask(membraneImages) + membraneMask = get_membrane_mask(membraneImages, membraneFlag) watershedMarker = np.zeros(compartmentMask.shape) @@ -371,7 +407,7 @@ def get_overlapping_objects(watershedZ0: np.ndarray, """ z1Indexes = np.unique(watershedZ1[watershedZ0 == n0]) - z1Indexes = z1Indexes[z1NucleiIndexes > 100] + z1Indexes = z1Indexes[z1Indexes > 100] if z1Indexes.shape[0] > 0: @@ -429,10 +465,10 @@ def combine_2d_segmentation_masks_into_3d(watershedOutput: # starting far from coverslip for z in range(watershedOutput.shape[0]-1, 0, -1): - zNucleiIndex = np.unique(watershedOutput[z, :, :])[ + zIndex = np.unique(watershedOutput[z, :, :])[ np.unique(watershedOutput[z, :, :]) > 100] - for n0 in zNucleiIndex: + for n0 in zIndex: n1, f0, f1 = get_overlapping_objects(watershedCombinedZ[z, :, :], watershedOutput[z-1, :, :], n0) From 034b4ec55ea07b0864a22dcf960845bba388676f Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sun, 26 Apr 2020 10:55:13 -0400 Subject: [PATCH 201/419] modify get_membrane_mask function --- merlin/util/watershed.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index bdfd2d1b..53b0c7cf 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -181,30 +181,27 @@ def get_membrane_mask(membraneImages: np.ndarray, lowThresh = 0.1 #0.5 #0.2 hiThresh = 0.5 #0.7 #0.6 for z in range(membraneImages.shape[0]): - blurredImage = cv2.GaussianBlur(img[:,:,1], + blurredImage = cv2.GaussianBlur(membraneImages[z, :, :], (filterSize2,filterSize2), filterSigma2) - edge0 = feature.canny(membraneImages[z, :, :], + edge0 = feature.canny(membraneImages[z, :, :], sigma=edgeSigma, use_quantiles=True, low_threshold=lowThresh, high_threshold=hiThresh) - edge0 = morphology.remove_small_objects( - mask[z, :, :].astype('bool'), - min_size=20, - connectivity=1) + edge0 = morphology.dilation(edge0,morphology.selem.disk(15)) + edge1 = feature.canny(blurredImage, sigma=edgeSigma, use_quantiles=True, low_threshold=lowThresh, high_threshold=hiThresh) - edge1 = morphology.remove_small_objects( - mask[z, :, :].astype('bool'), - min_size=20, - connectivity=1) + edge1 = morphology.dilation(edge1,morphology.selem.disk(15)) mask[z, :, :] = edge0 + edge1 - + + mask[z, :, :] = morphology.skeletonize(mask[z, :, :]) + return mask @@ -260,7 +257,7 @@ def get_compartment_mask(compartmentImages: np.ndarray) -> np.ndarray: fineHessianMask[z, :, :]) # generate compartment mask from hessian, coarse - coarseHessianMask = np.zeros(comapartImages.shape) + coarseHessianMask = np.zeros(compartmentImages.shape) for z in range(compartmentImages.shape[0]): coarseHessian = filters.hessian(compartmentImages[z, :, :] - morphology.white_tophat( From de95313f71bdc9f6d9fed88d03236d96e91f3b80 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sun, 26 Apr 2020 17:41:25 -0400 Subject: [PATCH 202/419] change watershed.py to segmentation.py, put contents of mls.py into segmentation.py --- merlin/analysis/segment.py | 33 +++++++++-------- merlin/util/machinelearningsegmentation.py | 43 ---------------------- merlin/util/segmentation.py | 1 + merlin/util/watershed.py | 4 +- 4 files changed, 21 insertions(+), 60 deletions(-) delete mode 100755 merlin/util/machinelearningsegmentation.py diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 13a7e495..f0ab0b39 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -1,7 +1,7 @@ import cv2 import numpy as np from skimage import measure -from skimage import segmentation +from skimage import segmentation as skiseg from skimage import morphology from skimage import feature from skimage import filters @@ -13,7 +13,7 @@ from merlin.core import dataset from merlin.core import analysistask from merlin.util import spatialfeature -from merlin.util import watershed +from merlin.util import segmentation import pandas import networkx as nx import time @@ -94,13 +94,13 @@ def _run_analysis(self, fragmentIndex): .get_data_channel_index(self.parameters['watershed_channel_name']) watershedImages = self._read_and_filter_image_stack(fragmentIndex, watershedIndex, 5) - seeds = watershed.separate_merged_seeds( - watershed.extract_seeds(seedImages)) - normalizedWatershed, watershedMask = watershed.prepare_watershed_images( + seeds = segmentation.separate_merged_seeds( + segmentation.extract_seeds(seedImages)) + normalizedWatershed, watershedMask = segmentation.prepare_watershed_images( watershedImages) seeds[np.invert(watershedMask)] = 0 - watershedOutput = segmentation.watershed( + watershedOutput = skiseg.watershed( normalizedWatershed, measure.label(seeds), mask=watershedMask, connectivity=np.ones((3, 3, 3)), watershed_line=True) @@ -219,7 +219,7 @@ def _run_analysis(self, fragmentIndex): + "," + str(compartmentImages.shape[2]) + "]") # Prepare masks for cv2 watershed - watershedMarkers = watershed.get_cv2_watershed_markers( + watershedMarkers = segmentation.get_cv2_watershed_markers( compartmentImages, membraneImages, self.parameters['membrane_channel_name']) @@ -229,15 +229,15 @@ def _run_analysis(self, fragmentIndex): (endTime - startTime) / 60)) # perform watershed in individual z positions - watershedOutput = watershed.apply_cv2_watershed(compartmentImages, - watershedMarkers) + watershedOutput = segmentation.apply_cv2_watershed(compartmentImages, + watershedMarkers) endTime = time.time() print(" watershed calculated, ET {:.2f} min".format( (endTime - startTime) / 60)) # combine all z positions in watershed - watershedCombinedOutput = watershed \ + watershedCombinedOutput = segmentation \ .combine_2d_segmentation_masks_into_3d(watershedOutput) endTime = time.time() @@ -286,9 +286,12 @@ def __init__(self, dataSet, parameters=None, analysisName=None): super().__init__(dataSet, parameters, analysisName) if 'method' not in self.parameters: - self.parameters['method'] = 'ilastik' + self.parameters['method'] = 'cellpose' if 'compartment_channel_name' not in self.parameters: self.parameters['compartment_channel_name'] = 'DAPI' + if 'compartment_channel_type' not in self.parameters: + self.parameters['compartment_channel_type'] = 'cytoplasm' # nuclei + def fragment_count(self): return len(self.dataSet.get_fovs()) @@ -338,10 +341,10 @@ def _run_analysis(self, fragmentIndex): print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) - segmentationOutput = machinelearningsegmentation. - apply_machine_learning_segmentation( - compartmentImages, - self.parameters['method']) + segmentationOutput = segmentation.apply_machine_learning_segmentation( + compartmentImages, + self.parameters['method'], + self.parameters['compartment_channel_name']) endTime = time.time() print(" Segmentation finished, ET {:.2f} min".format( diff --git a/merlin/util/machinelearningsegmentation.py b/merlin/util/machinelearningsegmentation.py deleted file mode 100755 index 085fa4df..00000000 --- a/merlin/util/machinelearningsegmentation.py +++ /dev/null @@ -1,43 +0,0 @@ -import numpy as np -import cv2 -from scipy import ndimage -from scipy.ndimage.morphology import binary_fill_holes -from skimage import morphology -from skimage import filters -from skimage import measure -from pyclustering.cluster import kmedoids -from typing import Tuple - -from merlin.util import matlab - -""" -This module contains utility functions for preparing imagmes for performing -segmentation using machine learning approaches -MAYBE COMBINE WITH WATERSHED.PY INTO A SINGLE FILE, SEGMENTATION.PY -""" -def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: - -def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: - -def segment_using_cellpose(imageStackIn: np.ndarray) -> np.ndarray: - - -def apply_machine_learning_segmentation(imageStackIn: np.ndarray, - method: str) -> np.ndarray: - """Select segmentation algorithm to use - Args: - imageStackIn: a 3 dimensional numpy array containing the images - arranged as (z, x, y). - Returns: - ndarray containing a 3 dimensional mask arranged as (z, x, y) - """ - if method == 'ilastik': - segmentOutput = segment_using_ilastik(imageStackIn) - elif method == 'cellpose': - segmentOutput = segment_using_cellpose(imageStackIn) - elif method == 'unet' - segmentOutput = segment_using_unet(imageStackIn) - - return segmentOutput - - diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 461e4fee..3c1c1a49 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -407,6 +407,7 @@ def get_overlapping_objects(segmentationZ0: np.ndarray, segmentation mask adjacent tosegmentationZ0 n0: an integer with the index of the object (cell/nuclei) to be compared between the provided segmentation masks + Returns: a tuple (n1, f0, f1) containing the label of the cell in Z1 overlapping n0 (n1), the fraction of n0 overlaping n1 (f0) and diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 53b0c7cf..f41a404b 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -189,14 +189,14 @@ def get_membrane_mask(membraneImages: np.ndarray, use_quantiles=True, low_threshold=lowThresh, high_threshold=hiThresh) - edge0 = morphology.dilation(edge0,morphology.selem.disk(15)) + edge0 = morphology.dilation(edge0,morphology.selem.disk(10)) edge1 = feature.canny(blurredImage, sigma=edgeSigma, use_quantiles=True, low_threshold=lowThresh, high_threshold=hiThresh) - edge1 = morphology.dilation(edge1,morphology.selem.disk(15)) + edge1 = morphology.dilation(edge1,morphology.selem.disk(10)) mask[z, :, :] = edge0 + edge1 From e1999affc9b7b50c929ca880becbd1576294d09f Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 6 May 2020 10:48:45 -0400 Subject: [PATCH 203/419] consolidate functions into segment_using_cellpose --- merlin/analysis/segment.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index f0ab0b39..7626e5c6 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -289,9 +289,6 @@ def __init__(self, dataSet, parameters=None, analysisName=None): self.parameters['method'] = 'cellpose' if 'compartment_channel_name' not in self.parameters: self.parameters['compartment_channel_name'] = 'DAPI' - if 'compartment_channel_type' not in self.parameters: - self.parameters['compartment_channel_type'] = 'cytoplasm' # nuclei - def fragment_count(self): return len(self.dataSet.get_fovs()) From 9e2689f03ff6533d84b28d51280b3783c218f101 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 18 Jun 2020 12:23:26 -0400 Subject: [PATCH 204/419] put segmentation params into dict --- merlin/analysis/segment.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 7626e5c6..c42df126 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -287,6 +287,8 @@ def __init__(self, dataSet, parameters=None, analysisName=None): if 'method' not in self.parameters: self.parameters['method'] = 'cellpose' + if 'diameter' not in self.parameters: + self.parameters['diameter'] = 50 if 'compartment_channel_name' not in self.parameters: self.parameters['compartment_channel_name'] = 'DAPI' @@ -338,10 +340,15 @@ def _run_analysis(self, fragmentIndex): print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) + if self.parameters['method'] == 'cellpose': + segParameters = dict({ + 'method':'cellpose', + 'diameter':self.parameters['diameter'], + 'channel':self.parameters['compartment_channel_name'] + }) + segmentationOutput = segmentation.apply_machine_learning_segmentation( - compartmentImages, - self.parameters['method'], - self.parameters['compartment_channel_name']) + compartmentImages,segParameters) endTime = time.time() print(" Segmentation finished, ET {:.2f} min".format( From c8878876161146683dccf42e9425180d45ee6629 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 19 Jun 2020 08:23:26 -0400 Subject: [PATCH 205/419] add parameters as dict --- merlin/analysis/segment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index c42df126..57236425 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -348,7 +348,7 @@ def _run_analysis(self, fragmentIndex): }) segmentationOutput = segmentation.apply_machine_learning_segmentation( - compartmentImages,segParameters) + compartmentImages, segParameters) endTime = time.time() print(" Segmentation finished, ET {:.2f} min".format( From 5a46da5ab030a71c3e02f4ca63a929c4d563d37d Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 19 Jun 2020 09:31:49 -0400 Subject: [PATCH 206/419] change indentation --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 57236425..fe479331 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -353,7 +353,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" Segmentation finished, ET {:.2f} min".format( (endTime - startTime) / 60)) -""" + """ endTime = time.time() print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) @@ -393,7 +393,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" watershed z positions combined, ET {:.2f} min".format( (endTime - startTime) / 60)) -""" + """ # get features from mask. This is the slowestart (6 min for the # previous part, 15+ for the rest, for a 7 frame Image. From 432188eb3f4cee46cd86ad28a47a5687628d87d1 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 19 Jun 2020 13:19:11 -0400 Subject: [PATCH 207/419] return watershed class name to previous state --- merlin/analysis/segment.py | 2 +- merlin/core/dataset.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index fe479331..0977614b 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -124,7 +124,7 @@ def _read_and_filter_image_stack(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions()))]) -class WatershedSegmentCV2(FeatureSavingAnalysisTask): +class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): """ An analysis task that determines the boundaries of features in the diff --git a/merlin/core/dataset.py b/merlin/core/dataset.py index bc120af6..1a411619 100755 --- a/merlin/core/dataset.py +++ b/merlin/core/dataset.py @@ -616,7 +616,7 @@ def load_analysis_task(self, analysisTaskName: str) \ -> analysistask.AnalysisTask: loadName = os.sep.join([self.get_task_subdirectory( analysisTaskName), 'task.json']) - + print(loadName) with open(loadName, 'r') as inFile: parameters = json.load(inFile) analysisModule = importlib.import_module(parameters['module']) From 7bd766f294e85b962b06d1652bbe4136e3fad099 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 19 Jun 2020 13:58:55 -0400 Subject: [PATCH 208/419] add 2D>3D segmentation mask --- merlin/analysis/segment.py | 41 ++------------------------------------ 1 file changed, 2 insertions(+), 39 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 0977614b..f7364c1d 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -353,47 +353,10 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" Segmentation finished, ET {:.2f} min".format( (endTime - startTime) / 60)) - """ - endTime = time.time() - print(" images read, ET {:.2f} min".format( - (endTime - startTime) / 60)) - print(" membraneImages Type: " + str(type(membraneImages))) - print(" membraneImages Size: [" - + str(membraneImages.shape[0]) - + "," + str(membraneImages.shape[1]) - + "," + str(membraneImages.shape[2]) + "]") - print(" compartmentImages Type: " + str(type(compartmentImages))) - print(" compartmentImages Size: [" - + str(compartmentImages.shape[0]) - + "," + str(compartmentImages.shape[1]) - + "," + str(compartmentImages.shape[2]) + "]") - - # Prepare masks for cv2 watershed - watershedMarkers = watershed.get_cv2_watershed_markers( - compartmentImages, - membraneImages, - membraneFlag) - - endTime = time.time() - print(" markers calculated, ET {:.2f} min".format( - (endTime - startTime) / 60)) - - # perform watershed in individual z positions - watershedOutput = watershed.apply_cv2_watershed(compartmentImages, - watershedMarkers) - - endTime = time.time() - print(" watershed calculated, ET {:.2f} min".format( - (endTime - startTime) / 60)) # combine all z positions in watershed - watershedCombinedOutput = watershed \ - .combine_2d_segmentation_masks_into_3d(watershedOutput) - - endTime = time.time() - print(" watershed z positions combined, ET {:.2f} min".format( - (endTime - startTime) / 60)) - """ + watershedCombinedOutput = segmentation \ + .combine_2d_segmentation_masks_into_3d(segmentationOutput) # get features from mask. This is the slowestart (6 min for the # previous part, 15+ for the rest, for a 7 frame Image. From c4675cba53e37fe2016a4869b23b6facbc704cf2 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 19 Jun 2020 14:58:36 -0400 Subject: [PATCH 209/419] changed segmentation output variable name --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index f7364c1d..b2aae913 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -250,7 +250,7 @@ def _run_analysis(self, fragmentIndex): featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( (watershedCombinedOutput == i), fragmentIndex, globalTask.fov_to_global_transform(fragmentIndex), zPos) - for i in np.unique(watershedOutput) if i != 0] + for i in np.unique(watershedCombinedOutput) if i != 0] featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) @@ -364,7 +364,7 @@ def _run_analysis(self, fragmentIndex): featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( (watershedCombinedOutput == i), fragmentIndex, globalTask.fov_to_global_transform(fragmentIndex), zPos) - for i in np.unique(watershedOutput) if i != 0] + for i in np.unique(watershedCombinedOutput) if i != 0] featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) From 8c342338ca5daee3b43f2c5a2aaa38f8e59c50e1 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 24 Jun 2020 10:52:08 -0400 Subject: [PATCH 210/419] changed value of the background mask from 100 to 0 --- merlin/util/segmentation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 3c1c1a49..9a0bff1d 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -415,6 +415,7 @@ def get_overlapping_objects(segmentationZ0: np.ndarray, """ z1Indexes = np.unique(segmentationZ1[segmentationZ0 == n0]) + z1Indexes = z1Indexes[z1Indexes > 0] if z1Indexes.shape[0] > 0: From e87a903e49c436c42cf65fb4b8aaa5841c9d1a93 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 24 Jun 2020 12:20:33 -0400 Subject: [PATCH 211/419] modifying tuple output in 2d>3d --- merlin/util/segmentation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 9a0bff1d..43c3c535 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -398,6 +398,7 @@ def get_overlapping_objects(segmentationZ0: np.ndarray, segmentationZ1: np.ndarray, n0: int) -> Tuple[np.float64, np.float64, np.float64]: + """compare cell labels in adjacent image masks Args: From 5f1b5804829d455e5cbc27dc3ce3d6369d765089 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 1 Jul 2020 16:47:50 -0400 Subject: [PATCH 212/419] pep8 compliance --- merlin/analysis/segment.py | 6 +++--- merlin/util/segmentation.py | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index b2aae913..e4333a75 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -96,8 +96,8 @@ def _run_analysis(self, fragmentIndex): watershedIndex, 5) seeds = segmentation.separate_merged_seeds( segmentation.extract_seeds(seedImages)) - normalizedWatershed, watershedMask = segmentation.prepare_watershed_images( - watershedImages) + normalizedWatershed, watershedMask = segmentation\ + .prepare_watershed_images(watershedImages) seeds[np.invert(watershedMask)] = 0 watershedOutput = skiseg.watershed( @@ -322,7 +322,7 @@ def _run_analysis(self, fragmentIndex): print(' globalTask loaded') - # read membrane and compartment indexes + # read membrane and compartment indexes compartmentIndex = self.dataSet \ .get_data_organization() \ .get_data_channel_index( diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 43c3c535..1a02f5f2 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -296,6 +296,7 @@ def get_cv2_watershed_markers(compartmentImages: np.ndarray, to use membraneChannelName: str with the name of the membrane channel to use + Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) of cv2-compatible watershed markers @@ -488,6 +489,7 @@ def combine_2d_segmentation_masks_into_3d(segmentationOutput: if n1: segmentationCombinedZ[z-1, :, :][ (segmentationOutput[z-1, :, :] == n1)] = n0 + return segmentationCombinedZ From ac617b376c3c6703db4c3726c1c0beb3da34380a Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 1 Jul 2020 17:02:22 -0400 Subject: [PATCH 213/419] pep8 compliance --- merlin/analysis/segment.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index e4333a75..46de62e9 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -150,7 +150,7 @@ class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): def __init__(self, dataSet, parameters=None, analysisName=None): super().__init__(dataSet, parameters, analysisName) - + if 'membrane_channel_name' not in self.parameters: self.parameters['membrane_channel_name'] = 'DAPI' if 'compartment_channel_name' not in self.parameters: @@ -265,6 +265,7 @@ def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: return np.array([warpTask.get_aligned_image(fov, channelIndex, z) for z in range(len(self.dataSet.get_z_positions()))]) + class MachineLearningSegment(FeatureSavingAnalysisTask): """ @@ -273,9 +274,9 @@ class MachineLearningSegment(FeatureSavingAnalysisTask): method. The available methods are: unet: - + ilastik: - + cellpose: TODO: ADD FLAT FIELD CORRECTION TASK @@ -284,7 +285,7 @@ class MachineLearningSegment(FeatureSavingAnalysisTask): def __init__(self, dataSet, parameters=None, analysisName=None): super().__init__(dataSet, parameters, analysisName) - + if 'method' not in self.parameters: self.parameters['method'] = 'cellpose' if 'diameter' not in self.parameters: @@ -342,9 +343,9 @@ def _run_analysis(self, fragmentIndex): if self.parameters['method'] == 'cellpose': segParameters = dict({ - 'method':'cellpose', - 'diameter':self.parameters['diameter'], - 'channel':self.parameters['compartment_channel_name'] + 'method': 'cellpose', + 'diameter': self.parameters['diameter'], + 'channel': self.parameters['compartment_channel_name'] }) segmentationOutput = segmentation.apply_machine_learning_segmentation( From 066eb6b3d124e0a097526f2f91d9a207c32997ea Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 1 Jul 2020 18:14:37 -0400 Subject: [PATCH 214/419] pep8 compliance --- merlin/util/watershed.py | 44 ++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index f41a404b..11870e8d 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -148,13 +148,13 @@ def get_membrane_mask(membraneImages: np.ndarray, compartmentChannelName: str) -> np.ndarray: """Calculate binary mask with 1's in membrane pixels and 0 otherwise. The images expected are some type of membrane label (WGA, ConA, - Lamin, Cadherins) or compartment images (DAPI, CD45, polyT) + Lamin, Cadherins) or compartment images (DAPI, CD45, polyT) Args: membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). membraneChannelName: A string with the name of a membrane channel. - compartmentChannelName: A string with the name of the compartment + compartmentChannelName: A string with the name of the compartment channel Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) @@ -177,26 +177,26 @@ def get_membrane_mask(membraneImages: np.ndarray, else: filterSigma2 = 5 filterSize2 = int(2*np.ceil(2*filterSigma2)+1) - edgeSigma = 2 #1 #2 - lowThresh = 0.1 #0.5 #0.2 - hiThresh = 0.5 #0.7 #0.6 + edgeSigma = 2 #1 #2 + lowThresh = 0.1 #0.5 #0.2 + hiThresh = 0.5 #0.7 #0.6 for z in range(membraneImages.shape[0]): blurredImage = cv2.GaussianBlur(membraneImages[z, :, :], - (filterSize2,filterSize2), + (filterSize2, filterSize2), filterSigma2) edge0 = feature.canny(membraneImages[z, :, :], sigma=edgeSigma, use_quantiles=True, low_threshold=lowThresh, high_threshold=hiThresh) - edge0 = morphology.dilation(edge0,morphology.selem.disk(10)) - - edge1 = feature.canny(blurredImage, - sigma=edgeSigma, - use_quantiles=True, - low_threshold=lowThresh, - high_threshold=hiThresh) - edge1 = morphology.dilation(edge1,morphology.selem.disk(10)) + edge0 = morphology.dilation(edge0, morphology.selem.disk(10)) + + edge1 = feature.canny(blurredImage, + sigma=edgeSigma, + use_quantiles=True, + low_threshold=lowThresh, + high_threshold=hiThresh) + edge1 = morphology.dilation(edge1, morphology.selem.disk(10)) mask[z, :, :] = edge0 + edge1 @@ -206,8 +206,8 @@ def get_membrane_mask(membraneImages: np.ndarray, def get_compartment_mask(compartmentImages: np.ndarray) -> np.ndarray: - """Calculate binary mask with 1's in compartment (nuclei or cytoplasm) - pixels and 0 otherwise. The images expected are some type of compartment + """Calculate binary mask with 1's in compartment (nuclei or cytoplasm) + pixels and 0 otherwise. The images expected are some type of compartment label (e.g. Nuclei: DAPI, Cytoplasm: PolyT, CD45, etc) Args: @@ -239,9 +239,9 @@ def get_compartment_mask(compartmentImages: np.ndarray) -> np.ndarray: # generate border mask, necessary to avoid making a single # connected component when using binary_fill_holes below - borderMask = np.zeros((compartmentImages.shape[1], + borderMask = np.zeros((compartmentImages.shape[1], compartmentImages.shape[2])) - borderMask[25:(compartmentImages.shape[1]-25), + borderMask[25:(compartmentImages.shape[1]-25), 25:(compartmentImages.shape[2]-25)] = 1 # generate compartment mask from hessian, fine @@ -287,7 +287,7 @@ def get_cv2_watershed_markers(compartmentImages: np.ndarray, arranged as (z, x, y). membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). - membraneFlag: 0 if compartment and membrane images are the same, 1 + membraneFlag: 0 if compartment and membrane images are the same, 1 otherwise Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) of @@ -386,7 +386,7 @@ def apply_cv2_watershed(compartmentImages: np.ndarray, def get_overlapping_objects(watershedZ0: np.ndarray, - watershedZ1: np.ndarray, n0: int): + watershedZ1: np.ndarray, n0: int): """Perform watershed using cv2 Args: @@ -394,8 +394,8 @@ def get_overlapping_objects(watershedZ0: np.ndarray, segmentation mask watershedZ1: a 2 dimensional numpy array containing a segmentation mask adjacent to watershedZ1 - n0: an integer with the index of the object (cell/nuclei) - to be compared between the provided watershed + n0: an integer with the index of the object (cell/nuclei) + to be compared between the provided watershed segmentation masks Returns: a tuple (n1, f0, f1) containing the label of the cell in Z1 From a215a208c4ac7869971924dbfd5c9a65f0d4c991 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 1 Jul 2020 18:18:52 -0400 Subject: [PATCH 215/419] pep8 compliance --- merlin/util/watershed.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 11870e8d..762c3e4d 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -177,18 +177,18 @@ def get_membrane_mask(membraneImages: np.ndarray, else: filterSigma2 = 5 filterSize2 = int(2*np.ceil(2*filterSigma2)+1) - edgeSigma = 2 #1 #2 - lowThresh = 0.1 #0.5 #0.2 - hiThresh = 0.5 #0.7 #0.6 + edgeSigma = 2 # 1, 2 + lowThresh = 0.1 # 0.5, 0.2 + hiThresh = 0.5 # 0.7, 0.6 for z in range(membraneImages.shape[0]): blurredImage = cv2.GaussianBlur(membraneImages[z, :, :], (filterSize2, filterSize2), filterSigma2) edge0 = feature.canny(membraneImages[z, :, :], - sigma=edgeSigma, - use_quantiles=True, - low_threshold=lowThresh, - high_threshold=hiThresh) + sigma=edgeSigma, + use_quantiles=True, + low_threshold=lowThresh, + high_threshold=hiThresh) edge0 = morphology.dilation(edge0, morphology.selem.disk(10)) edge1 = feature.canny(blurredImage, From 838737023e948f63bd959b674ec3bbb8010c9759 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 1 Jul 2020 18:36:19 -0400 Subject: [PATCH 216/419] added Machine Learning Segment and CV2 to tests --- .../test_analysis_parameters.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/auxiliary_files/test_analysis_parameters.json b/test/auxiliary_files/test_analysis_parameters.json index 2e9f9212..e426e860 100755 --- a/test/auxiliary_files/test_analysis_parameters.json +++ b/test/auxiliary_files/test_analysis_parameters.json @@ -115,6 +115,25 @@ "global_align_task": "SimpleGlobalAlignment" } }, + { + "task": "WatershedSegmentNucleiCV2", + "module": "merlin.analysis.segment", + "parameters": { + "warp_task": "FiducialCorrelationWarp", + "global_align_task": "SimpleGlobalAlignment" + } + }, + { + "task": "MachineLearningSegment", + "module": "merlin.analysis.segment", + "parameters": { + "warp_task": "FiducialCorrelationWarp", + "global_align_task": "SimpleGlobalAlignment", + "diameter": 50, + "method": "cellpose", + "compartment_channel_name": "DAPI" + } + }, { "task": "CleanCellBoundaries", "module": "merlin.analysis.segment", From f48e67d3c2b8fcced08f2b749b256d73be64e8fc Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 1 Jul 2020 19:16:44 -0400 Subject: [PATCH 217/419] remove print statements for timing --- merlin/analysis/segment.py | 72 ++------------------------------------ 1 file changed, 3 insertions(+), 69 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 46de62e9..e964e45f 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -178,13 +178,9 @@ def get_cell_boundaries(self) -> List[spatialfeature.SpatialFeature]: def _run_analysis(self, fragmentIndex): startTime = time.time() - print('Entered the _run_analysis method, FOV ' + str(fragmentIndex)) - globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) - print(' globalTask loaded') - # read membrane and compartment indexes membraneIndex = self.dataSet \ .get_data_organization() \ @@ -195,55 +191,25 @@ def _run_analysis(self, fragmentIndex): .get_data_channel_index( self.parameters['compartment_channel_name']) - endTime = time.time() - print(" image indexes read, ET {:.2f} min".format( - (endTime - startTime) / 60)) - # read membrane and compartment images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) compartmentImages = self._read_image_stack(fragmentIndex, compartmentIndex) - endTime = time.time() - print(" images read, ET {:.2f} min".format( - (endTime - startTime) / 60)) - print(" membraneImages Type: " + str(type(membraneImages))) - print(" membraneImages Size: [" - + str(membraneImages.shape[0]) - + "," + str(membraneImages.shape[1]) - + "," + str(membraneImages.shape[2]) + "]") - print(" compartmentImages Type: " + str(type(compartmentImages))) - print(" compartmentImages Size: [" - + str(compartmentImages.shape[0]) - + "," + str(compartmentImages.shape[1]) - + "," + str(compartmentImages.shape[2]) + "]") - # Prepare masks for cv2 watershed watershedMarkers = segmentation.get_cv2_watershed_markers( compartmentImages, membraneImages, self.parameters['membrane_channel_name']) - endTime = time.time() - print(" markers calculated, ET {:.2f} min".format( - (endTime - startTime) / 60)) - # perform watershed in individual z positions watershedOutput = segmentation.apply_cv2_watershed(compartmentImages, watershedMarkers) - endTime = time.time() - print(" watershed calculated, ET {:.2f} min".format( - (endTime - startTime) / 60)) - # combine all z positions in watershed watershedCombinedOutput = segmentation \ .combine_2d_segmentation_masks_into_3d(watershedOutput) - endTime = time.time() - print(" watershed z positions combined, ET {:.2f} min".format( - (endTime - startTime) / 60)) - # get features from mask. This is the slowestart (6 min for the # previous part, 15+ for the rest, for a 7 frame Image. zPos = np.array(self.dataSet.get_data_organization().get_z_positions()) @@ -255,10 +221,6 @@ def _run_analysis(self, fragmentIndex): featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) - endTime = time.time() - print(" features written, ET {:.2f} min".format( - (endTime - startTime) / 60)) - def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) @@ -271,16 +233,10 @@ class MachineLearningSegment(FeatureSavingAnalysisTask): """ An analysis task that determines the boundaries of features in the image data in each field of view using a the specified machine learning - method. The available methods are: - - unet: - - ilastik: - - cellpose: - - TODO: ADD FLAT FIELD CORRECTION TASK + method. The available method is cellpose (https://github.com/MouseLand/ + cellpose). + TODO: implement unets / Ilastik """ def __init__(self, dataSet, parameters=None, analysisName=None): @@ -313,34 +269,20 @@ def get_cell_boundaries(self) -> List[spatialfeature.SpatialFeature]: return featureDB.read_features() def _run_analysis(self, fragmentIndex): - startTime = time.time() - - print('Entered the _run_analysis method, FOV ' + str(fragmentIndex)) - print('Using ' + self.parameters['method'] + ' method.') globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) - print(' globalTask loaded') - # read membrane and compartment indexes compartmentIndex = self.dataSet \ .get_data_organization() \ .get_data_channel_index( self.parameters['compartment_channel_name']) - endTime = time.time() - print(" image indexes read, ET {:.2f} min".format( - (endTime - startTime) / 60)) - # Read images and perform segmentation compartmentImages = self._read_image_stack(fragmentIndex, compartmentIndex) - endTime = time.time() - print(" images read, ET {:.2f} min".format( - (endTime - startTime) / 60)) - if self.parameters['method'] == 'cellpose': segParameters = dict({ 'method': 'cellpose', @@ -351,10 +293,6 @@ def _run_analysis(self, fragmentIndex): segmentationOutput = segmentation.apply_machine_learning_segmentation( compartmentImages, segParameters) - endTime = time.time() - print(" Segmentation finished, ET {:.2f} min".format( - (endTime - startTime) / 60)) - # combine all z positions in watershed watershedCombinedOutput = segmentation \ .combine_2d_segmentation_masks_into_3d(segmentationOutput) @@ -370,10 +308,6 @@ def _run_analysis(self, fragmentIndex): featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) - endTime = time.time() - print(" features written, ET {:.2f} min".format( - (endTime - startTime) / 60)) - def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) From fca3e8e61ccdd008e5a547319dd991f9eca0bd82 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 07:12:40 -0400 Subject: [PATCH 218/419] copied dataportal.py from master --- merlin/util/dataportal.py | 1 - 1 file changed, 1 deletion(-) diff --git a/merlin/util/dataportal.py b/merlin/util/dataportal.py index c01e7a17..bba38f97 100755 --- a/merlin/util/dataportal.py +++ b/merlin/util/dataportal.py @@ -344,7 +344,6 @@ def get_sibling_with_extension(self, newExtension: str): def _error_tolerant_reading(self, method, startByte=None, endByte=None): - backoffSeries = [1, 2, 4, 8, 16, 32, 64, 128, 256] for sleepDuration in backoffSeries: try: From 8ab8b3cafa4d1fbdff5684b8feec35ceb1ffe8f6 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 07:33:30 -0400 Subject: [PATCH 219/419] added explicit tuple output --- merlin/util/segmentation.py | 1 - 1 file changed, 1 deletion(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 1a02f5f2..e7291a05 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -399,7 +399,6 @@ def get_overlapping_objects(segmentationZ0: np.ndarray, segmentationZ1: np.ndarray, n0: int) -> Tuple[np.float64, np.float64, np.float64]: - """compare cell labels in adjacent image masks Args: From 8cbc8ab57877ce68af536c563fc15fe0b7de7713 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 07:56:34 -0400 Subject: [PATCH 220/419] add cellpose to requirements --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index dfc4de44..35d39c48 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,3 +29,4 @@ boto3 xmltodict google-cloud-storage docutils<0.16,>=0.10 + From f57bb92ec793eb7acfff88738d20d646130e0c2e Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 08:04:52 -0400 Subject: [PATCH 221/419] updated docutils and pillow requirements --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 35d39c48..dfc4de44 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,4 +29,3 @@ boto3 xmltodict google-cloud-storage docutils<0.16,>=0.10 - From 0e7536ebec27215cbd9c092269d06b80d09cdc4e Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 10:21:12 -0400 Subject: [PATCH 222/419] removed docutils requirement to avoid conflict with v017 --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index dfc4de44..35d39c48 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,3 +29,4 @@ boto3 xmltodict google-cloud-storage docutils<0.16,>=0.10 + From ebccbd2b9a85d34f332611b47d90869ffafbc575 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 11:23:03 -0400 Subject: [PATCH 223/419] added input variables to get_membrane_mask --- requirements.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/requirements.txt b/requirements.txt index 35d39c48..5f41daa4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,4 +29,9 @@ boto3 xmltodict google-cloud-storage docutils<0.16,>=0.10 +<<<<<<< HEAD +======= +pillow<=7.0.0 +cellpose +>>>>>>> added input variables to get_membrane_mask From 21ef8658ecbafe32cfaeb3ba89bc05505f29995a Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 12:49:56 -0400 Subject: [PATCH 224/419] added additional variable --- merlin/analysis/segment.py | 1 + 1 file changed, 1 insertion(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index e964e45f..e7aede09 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -200,6 +200,7 @@ def _run_analysis(self, fragmentIndex): watershedMarkers = segmentation.get_cv2_watershed_markers( compartmentImages, membraneImages, + self.parameters['compartment_channel_name'], self.parameters['membrane_channel_name']) # perform watershed in individual z positions From c7d70abd8cbc7dc5dbe3a31ee66fada5d9f29deb Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Mon, 6 Jul 2020 18:13:42 -0400 Subject: [PATCH 225/419] added pillow and cellpose requirements --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index dfc4de44..e4dc2d75 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,3 +29,5 @@ boto3 xmltodict google-cloud-storage docutils<0.16,>=0.10 +pillow<=7.0.0 +cellpose \ No newline at end of file From a472f97bd3d3ee3faea975ba55d96568b3d71324 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 22 Jan 2020 14:44:24 -0500 Subject: [PATCH 226/419] Adding _get_membrane_mask and _get_nuclei_mask functions to WatershedSegmentNucleiCV2 class --- merlin/analysis/segment.py | 209 +++++++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 786cb0d9..d90401ea 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -2,10 +2,14 @@ import numpy as np from skimage import measure from skimage import segmentation +from skimage import morphology +from skimage import feature +from skimage import filters import rtree from shapely import geometry from typing import List, Dict from scipy.spatial import cKDTree +from scipy.ndimage.morphology import binary_fill_holes from merlin.core import dataset from merlin.core import analysistask @@ -119,6 +123,211 @@ def _read_and_filter_image_stack(self, fov: int, channelIndex: int, (filterSize, filterSize), filterSigma) for z in range(len(self.dataSet.get_z_positions()))]) +class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): + + """ + An analysis task that determines the boundaries of features in the + image data in each field of view using a watershed algorithm + implemented in CV2. + + A tutorial explaining the general scheme of the method can be + found in https://opencv-python-tutroals.readthedocs.io/en/latest/ + py_tutorials/py_imgproc/py_watershed/py_watershed.html. + + The watershed segmentation is performed in each z-position independently and + combined into 3D objects in a later step + + Since each field of view is analyzed individually, the segmentation results + should be cleaned in order to merge cells that cross the field of + view boundary. + """ + + def __init__(self, dataSet, parameters=None, analysisName=None): + super().__init__(dataSet, parameters, analysisName) + + if 'seed_channel_name' not in self.parameters: + self.parameters['seed_channel_name'] = 'WGA' + if 'watershed_channel_name' not in self.parameters: + self.parameters['watershed_channel_name'] = 'DAPI' + + def fragment_count(self): + return len(self.dataSet.get_fovs()) + + def get_estimated_memory(self): + # TODO - refine estimate + return 2048 + + def get_estimated_time(self): + # TODO - refine estimate + return 5 + + def get_dependencies(self): + return [self.parameters['warp_task'], + self.parameters['global_align_task']] + + def get_cell_boundaries(self) -> List[spatialfeature.SpatialFeature]: + featureDB = self.get_feature_database() + return featureDB.read_features() + + def _run_analysis(self, fragmentIndex): + globalTask = self.dataSet.load_analysis_task( + self.parameters['global_align_task']) + + seedIndex = self.dataSet.get_data_organization().get_data_channel_index( + self.parameters['seed_channel_name']) + seedImages = self._read_and_filter_image_stack(fragmentIndex, + seedIndex, 5) + + watershedIndex = self.dataSet.get_data_organization() \ + .get_data_channel_index(self.parameters['watershed_channel_name']) + watershedImages = self._read_and_filter_image_stack(fragmentIndex, + watershedIndex, 5) + seeds = watershed.separate_merged_seeds( + watershed.extract_seeds(seedImages)) + normalizedWatershed, watershedMask = watershed.prepare_watershed_images( + watershedImages) + + seeds[np.invert(watershedMask)] = 0 + watershedOutput = segmentation.watershed( + normalizedWatershed, measure.label(seeds), mask=watershedMask, + connectivity=np.ones((3, 3, 3)), watershed_line=True) + + zPos = np.array(self.dataSet.get_data_organization().get_z_positions()) + featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( + (watershedOutput == i), fragmentIndex, + globalTask.fov_to_global_transform(fragmentIndex), zPos) + for i in np.unique(watershedOutput) if i != 0] + + featureDB = self.get_feature_database() + featureDB.write_features(featureList, fragmentIndex) + + def _get_membrane_mask(self, fov: int, channelIndex: int, + filterSigma: float) -> np.ndarray: + warpTask = self.dataSet.load_analysis_task( + self.parameters['warp_task']) + + imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) + for z in range(len(self.dataSet.get_z_positions()))]) + + # generate mask based on edge detection + edgeMask = np.zeros(imageStack.shape) + for z in range(len(self.dataSet.get_z_positions())): + edgeMask[:,:,z] = canny( + white_tophat(imageStack[:,:,z], selem.disk(10)), + sigma=2, use_quantiles=True, low_threshold=0.5, + high_threshold=0.8) + edgeMask[:,:,z] = binary_closing(edgeMask[:,:,z],selem.disk(5)) + edgeMask[:,:,z] = remove_small_objects( + edgeMask[:,:,z].astype('bool'), min_size=100, connectivity=1) + edgeMask[:,:,z] = skeletonize(edgeMask[:,:,z]) + + # generate mask based on thresholding + tresholdingMask = np.zeros(imageStack.shape) + fineBlockSize = 61 + for z in range(len(self.dataSet.get_z_positions())): + tresholdingMask[:,:,z] = imageStack[:,:,z] > + threshold_local(imageStack[:,:,z], fineBlockSize, offset=0) + tresholdingMask[:,:,z] = remove_small_objects( + imageStack[:,:,z].astype('bool'), min_size=100, connectivity=1) + tresholdingMask[:,:,z] = binary_closing(imageStack[:,:,z], + selem.disk(5)) + tresholdingMask[:,:,z] = skeletonize(imageStack[:,:,z]) + + # combine masks + return edgeMask + thresholdingMask + + def _get_nuclei_mask(self, fov: int, channelIndex: int, + filterSigma: float) -> np.ndarray: + + warpTask = self.dataSet.load_analysis_task( + self.parameters['warp_task']) + + imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) + for z in range(len(self.dataSet.get_z_positions()))]) + + # generate nuclei mask based on thresholding + thresholdingMask = np.zeros(imageStack.shape) + coarseBlockSize = 241 + fineBlockSize = 61 + for z in range(len(self.dataSet.get_z_positions())): + coarseThresholdingMask = imageStack[:,:,z] > + threshold_local(imageStack[:,:,z], coarseBlockSize, offset=0) + fineThresholdingMask = imageStack[:,:,z] > + threshold_local(imageStack[:,:,z], fineBlockSize, offset=0) + thresholdingMask[:,:,z] = coarseThresholdingMask* + fineThresholdingMask + thresholdingMask[:,:,z] = binary_fill_holes(thresholdingMask[:,:,z]) + + # generate nuclei mask from hessian, fine + fineHessianMask = np.zeros(imageStack.shape) + for z in range(len(self.dataSet.get_z_positions())): + fineHessian = hessian(imageStack[:,:,z]) + fineHessianMask[:,:,z] = fineHessian == fineHessian.max() + fineHessianMask[:,:,z] = binary_closing( + fineHessianMask[:,:,z], selem.disk(5)) + fineHessianMask[:,:,z] = binary_fill_holes(fineHessianMask[:,:,z]) + + # generate dapi mask from hessian, coarse + coarseHessianMask = np.zeros(imageStack.shape) + for z in range(len(self.dataSet.get_z_positions())): + coarseHessian = hessian(imageStack[:,:,z] - + white_tophat(imageStack[:,:,z], selem.disk(20))) + coarseHessianMask[:,:,z] = coarseHessian == coarseHessian.max() + coarseHessianMask[:,:,z] = binary_closing(coarseHessianMask[:,:,z], + selem.disk(5)) + coarseHessianMask[:,:,z] = binary_fill_holes( + coarseHessianMask[:,:,z]) + + # combine masks + nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask + return binary_fill_holes(nucleiMask) + + def _generate_watershed_mask(self, fov: int, channelIndex: int, + filterSigma: float) -> np.ndarray: + + + def _read_and_filter_image_stack(self, fov: int, channelIndex: int, + filterSigma: float) -> np.ndarray: + filterSize = int(2*np.ceil(2*filterSigma)+1) + warpTask = self.dataSet.load_analysis_task( + self.parameters['warp_task']) + return np.array([cv2.GaussianBlur( + warpTask.get_aligned_image(fov, channelIndex, z), + (filterSize, filterSize), filterSigma) + for z in range(len(self.dataSet.get_z_positions()))]) + + +#-------------------------------------------------------------------------------------------------------------------- +# Generate. As before, use a combination of masks +#-------------------------------------------------------------------------------------------------------------------- +sure_bg_dapi = sm.dilation(foreground,sm.selem.disk(15)) + +mask_wga_dil = sm.dilation(mask_wga,sm.selem.disk(10)) +sure_fg = sm.erosion(foreground*~mask_wga_dil,sm.selem.disk(5)) + +unknown_dapi = sure_bg_dapi*~sure_fg + +sure_bg_dapi = np.uint8(sure_bg_dapi)*255 +sure_fg = np.uint8(sure_fg)*255 +unknown_dapi = np.uint8(unknown_dapi)*255 + + +# Marker labelling +ret, markers = cv2.connectedComponents(sure_fg) + +# Add one to all labels so that sure background is not 0, but 1 +markers_dapi = markers+100 + +# Now, mark the region of unknown with zero +markers_dapi[unknown_dapi==255] = 0 + +# Apply watershed using cv2 +markers_ws_dapi = cv2.watershed(Idapi_inv,markers_dapi) + + + + + class CleanCellBoundaries(analysistask.ParallelAnalysisTask): ''' From 6cf563727458fb40465abadd5a85bb7e00f5a1f6 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Jan 2020 08:49:46 -0500 Subject: [PATCH 227/419] changed formatting to comply to pep8 --- merlin/analysis/segment.py | 50 ++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index d90401ea..661b0184 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -134,12 +134,12 @@ class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): found in https://opencv-python-tutroals.readthedocs.io/en/latest/ py_tutorials/py_imgproc/py_watershed/py_watershed.html. - The watershed segmentation is performed in each z-position independently and - combined into 3D objects in a later step + The watershed segmentation is performed in each z-position + independently and combined into 3D objects in a later step - Since each field of view is analyzed individually, the segmentation results - should be cleaned in order to merge cells that cross the field of - view boundary. + Since each field of view is analyzed individually, the segmentation + results should be cleaned in order to merge cells that cross the + field of view boundary. """ def __init__(self, dataSet, parameters=None, analysisName=None): @@ -213,9 +213,9 @@ def _get_membrane_mask(self, fov: int, channelIndex: int, edgeMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): edgeMask[:,:,z] = canny( - white_tophat(imageStack[:,:,z], selem.disk(10)), - sigma=2, use_quantiles=True, low_threshold=0.5, - high_threshold=0.8) + white_tophat(imageStack[:,:,z], selem.disk(10)), + sigma=2, use_quantiles=True, + low_threshold=0.5, high_threshold=0.8) edgeMask[:,:,z] = binary_closing(edgeMask[:,:,z],selem.disk(5)) edgeMask[:,:,z] = remove_small_objects( edgeMask[:,:,z].astype('bool'), min_size=100, connectivity=1) @@ -225,12 +225,13 @@ def _get_membrane_mask(self, fov: int, channelIndex: int, tresholdingMask = np.zeros(imageStack.shape) fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - tresholdingMask[:,:,z] = imageStack[:,:,z] > + tresholdingMask[:,:,z] = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], fineBlockSize, offset=0) tresholdingMask[:,:,z] = remove_small_objects( - imageStack[:,:,z].astype('bool'), min_size=100, connectivity=1) - tresholdingMask[:,:,z] = binary_closing(imageStack[:,:,z], - selem.disk(5)) + imageStack[:,:,z].astype('bool'), + min_size=100, connectivity=1) + tresholdingMask[:,:,z] = binary_closing(imageStack[:,:,z], + selem.disk(5)) tresholdingMask[:,:,z] = skeletonize(imageStack[:,:,z]) # combine masks @@ -250,12 +251,14 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int, coarseBlockSize = 241 fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = imageStack[:,:,z] > - threshold_local(imageStack[:,:,z], coarseBlockSize, offset=0) + coarseThresholdingMask = imageStack[:,:,z] > + threshold_local(imageStack[:,:,z], + coarseBlockSize, offset=0) fineThresholdingMask = imageStack[:,:,z] > - threshold_local(imageStack[:,:,z], fineBlockSize, offset=0) + threshold_local(imageStack[:,:,z], + fineBlockSize, offset=0) thresholdingMask[:,:,z] = coarseThresholdingMask* - fineThresholdingMask + fineThresholdingMask thresholdingMask[:,:,z] = binary_fill_holes(thresholdingMask[:,:,z]) # generate nuclei mask from hessian, fine @@ -263,20 +266,21 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions())): fineHessian = hessian(imageStack[:,:,z]) fineHessianMask[:,:,z] = fineHessian == fineHessian.max() - fineHessianMask[:,:,z] = binary_closing( - fineHessianMask[:,:,z], selem.disk(5)) + fineHessianMask[:,:,z] = binary_closing(fineHessianMask[:,:,z], + selem.disk(5)) fineHessianMask[:,:,z] = binary_fill_holes(fineHessianMask[:,:,z]) # generate dapi mask from hessian, coarse coarseHessianMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): - coarseHessian = hessian(imageStack[:,:,z] - - white_tophat(imageStack[:,:,z], selem.disk(20))) + coarseHessian = hessian(imageStack[:,:,z] - + white_tophat(imageStack[:,:,z], + selem.disk(20))) coarseHessianMask[:,:,z] = coarseHessian == coarseHessian.max() - coarseHessianMask[:,:,z] = binary_closing(coarseHessianMask[:,:,z], - selem.disk(5)) + coarseHessianMask[:,:,z] = binary_closing(coarseHessianMask[:,:,z], + selem.disk(5)) coarseHessianMask[:,:,z] = binary_fill_holes( - coarseHessianMask[:,:,z]) + coarseHessianMask[:,:,z]) # combine masks nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask From bdac53ca91f87eca9b58aeeabf4eef945b92f7e3 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Jan 2020 09:47:58 -0500 Subject: [PATCH 228/419] add borderMask to _get_nuclei_mask function --- merlin/analysis/segment.py | 44 ++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 661b0184..9651589c 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -210,6 +210,7 @@ def _get_membrane_mask(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions()))]) # generate mask based on edge detection + """ edgeMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): edgeMask[:,:,z] = canny( @@ -218,24 +219,28 @@ def _get_membrane_mask(self, fov: int, channelIndex: int, low_threshold=0.5, high_threshold=0.8) edgeMask[:,:,z] = binary_closing(edgeMask[:,:,z],selem.disk(5)) edgeMask[:,:,z] = remove_small_objects( - edgeMask[:,:,z].astype('bool'), min_size=100, connectivity=1) + edgeMask[:,:,z].astype('bool'), + min_size=100, connectivity=1) edgeMask[:,:,z] = skeletonize(edgeMask[:,:,z]) + """ # generate mask based on thresholding tresholdingMask = np.zeros(imageStack.shape) fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:,:,z] = imageStack[:,:,z] > - threshold_local(imageStack[:,:,z], fineBlockSize, offset=0) + threshold_local(imageStack[:,:,z], + fineBlockSize, offset=0) tresholdingMask[:,:,z] = remove_small_objects( - imageStack[:,:,z].astype('bool'), - min_size=100, connectivity=1) + imageStack[:,:,z].astype('bool'), + min_size=100, connectivity=1) tresholdingMask[:,:,z] = binary_closing(imageStack[:,:,z], - selem.disk(5)) + selem.disk(5)) tresholdingMask[:,:,z] = skeletonize(imageStack[:,:,z]) # combine masks - return edgeMask + thresholdingMask + # return edgeMask + thresholdingMask + return thresholdingMask def _get_nuclei_mask(self, fov: int, channelIndex: int, filterSigma: float) -> np.ndarray: @@ -253,21 +258,31 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions())): coarseThresholdingMask = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], - coarseBlockSize, offset=0) + coarseBlockSize, offset=0) fineThresholdingMask = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], - fineBlockSize, offset=0) + fineBlockSize, offset=0) thresholdingMask[:,:,z] = coarseThresholdingMask* fineThresholdingMask - thresholdingMask[:,:,z] = binary_fill_holes(thresholdingMask[:,:,z]) - + thresholdingMask[:,:,z] = binary_fill_holes( + thresholdingMask[:,:,z]) + + # generate border mask, necessary to avoid making a single + # connected component when using binary_fill_holes below + borderMask = np.zeros((2048,2048)) + borderMask[25:2023,25:2023] = 1 + + # TODO - use the image size variable for borderMask + + # generate nuclei mask from hessian, fine fineHessianMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): fineHessian = hessian(imageStack[:,:,z]) fineHessianMask[:,:,z] = fineHessian == fineHessian.max() fineHessianMask[:,:,z] = binary_closing(fineHessianMask[:,:,z], - selem.disk(5)) + selem.disk(5)) + fineHessianMask[:,:,z] = fineHessianMask[:,:,z]*borderMask fineHessianMask[:,:,z] = binary_fill_holes(fineHessianMask[:,:,z]) # generate dapi mask from hessian, coarse @@ -275,12 +290,13 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions())): coarseHessian = hessian(imageStack[:,:,z] - white_tophat(imageStack[:,:,z], - selem.disk(20))) + selem.disk(20))) coarseHessianMask[:,:,z] = coarseHessian == coarseHessian.max() coarseHessianMask[:,:,z] = binary_closing(coarseHessianMask[:,:,z], - selem.disk(5)) + selem.disk(5)) + coarseHessianMask[:,:,z] = coarseHessianMask[:,:,z]*borderMask coarseHessianMask[:,:,z] = binary_fill_holes( - coarseHessianMask[:,:,z]) + coarseHessianMask[:,:,z]) # combine masks nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask From e65f631129818e54b85a7606be98e071c8146adf Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Jan 2020 14:11:30 -0500 Subject: [PATCH 229/419] added method _generate_markers to WatershedSegmentNucleiCV2 --- merlin/analysis/segment.py | 102 +++++++++++++++++++++---------------- 1 file changed, 57 insertions(+), 45 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 9651589c..27c9456d 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -180,6 +180,16 @@ def _run_analysis(self, fragmentIndex): watershedIndex = self.dataSet.get_data_organization() \ .get_data_channel_index(self.parameters['watershed_channel_name']) + + membraneMask = self._get_membrane_mask(fragmentIndex, watershedIndex) + nucleiMask = self._get_nuclei_mask(fragmentIndex, watershedIndex) + watershedMarkers = self._generate_markers(nucleiMask,membraneMask) + + + + + + watershedImages = self._read_and_filter_image_stack(fragmentIndex, watershedIndex, 5) seeds = watershed.separate_merged_seeds( @@ -201,8 +211,7 @@ def _run_analysis(self, fragmentIndex): featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) - def _get_membrane_mask(self, fov: int, channelIndex: int, - filterSigma: float) -> np.ndarray: + def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) @@ -230,20 +239,20 @@ def _get_membrane_mask(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:,:,z] = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], - fineBlockSize, offset=0) + fineBlockSize, + offset=0) tresholdingMask[:,:,z] = remove_small_objects( - imageStack[:,:,z].astype('bool'), - min_size=100, connectivity=1) + imageStack[:,:,z].astype('bool'), + min_size=100, connectivity=1) tresholdingMask[:,:,z] = binary_closing(imageStack[:,:,z], - selem.disk(5)) + selem.disk(5)) tresholdingMask[:,:,z] = skeletonize(imageStack[:,:,z]) # combine masks # return edgeMask + thresholdingMask return thresholdingMask - def _get_nuclei_mask(self, fov: int, channelIndex: int, - filterSigma: float) -> np.ndarray: + def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) @@ -258,10 +267,12 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions())): coarseThresholdingMask = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], - coarseBlockSize, offset=0) + coarseBlockSize, + offset=0) fineThresholdingMask = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], - fineBlockSize, offset=0) + fineBlockSize, + offset=0) thresholdingMask[:,:,z] = coarseThresholdingMask* fineThresholdingMask thresholdingMask[:,:,z] = binary_fill_holes( @@ -281,7 +292,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int, fineHessian = hessian(imageStack[:,:,z]) fineHessianMask[:,:,z] = fineHessian == fineHessian.max() fineHessianMask[:,:,z] = binary_closing(fineHessianMask[:,:,z], - selem.disk(5)) + selem.disk(5)) fineHessianMask[:,:,z] = fineHessianMask[:,:,z]*borderMask fineHessianMask[:,:,z] = binary_fill_holes(fineHessianMask[:,:,z]) @@ -290,10 +301,10 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions())): coarseHessian = hessian(imageStack[:,:,z] - white_tophat(imageStack[:,:,z], - selem.disk(20))) + selem.disk(20))) coarseHessianMask[:,:,z] = coarseHessian == coarseHessian.max() coarseHessianMask[:,:,z] = binary_closing(coarseHessianMask[:,:,z], - selem.disk(5)) + selem.disk(5)) coarseHessianMask[:,:,z] = coarseHessianMask[:,:,z]*borderMask coarseHessianMask[:,:,z] = binary_fill_holes( coarseHessianMask[:,:,z]) @@ -302,6 +313,39 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int, nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask return binary_fill_holes(nucleiMask) + def _generate_markers(self, nucleiMask: np.ndarray, + membraneMask: np.ndarray) -> np.ndarray: + + watershedMarker = np.zeros(nucleiMask.shape) + + for z in range(len(self.dataSet.get_z_positions())): + + # generate areas of sure bg and fg, as well as the area of + # unknown classification + background = sm.dilation(nucleiMask[:,:,z],sm.selem.disk(15)) + membraneDilated = sm.dilation(membraneMask[:,:,z].astype('bool'), + sm.selem.disk(10)) + foreground = sm.erosion(nucleiMask[:,:,z]*~membraneDilated, + sm.selem.disk(5)) + unknown = background*~foreground + + background = np.uint8(background)*255 + foreground = np.uint8(foreground)*255 + unknown = np.uint8(unknown)*255 + + # Marker labelling + ret, markers = cv2.connectedComponents(foreground) + + # Add one to all labels so that sure background is not 0, but 1 + markers = markers+100 + + # Now, mark the region of unknown with zero + markers[unknown==255] = 0 + + watershedMarker[:,:,z] = markers + + return watershedMarker + def _generate_watershed_mask(self, fov: int, channelIndex: int, filterSigma: float) -> np.ndarray: @@ -317,38 +361,6 @@ def _read_and_filter_image_stack(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions()))]) -#-------------------------------------------------------------------------------------------------------------------- -# Generate. As before, use a combination of masks -#-------------------------------------------------------------------------------------------------------------------- -sure_bg_dapi = sm.dilation(foreground,sm.selem.disk(15)) - -mask_wga_dil = sm.dilation(mask_wga,sm.selem.disk(10)) -sure_fg = sm.erosion(foreground*~mask_wga_dil,sm.selem.disk(5)) - -unknown_dapi = sure_bg_dapi*~sure_fg - -sure_bg_dapi = np.uint8(sure_bg_dapi)*255 -sure_fg = np.uint8(sure_fg)*255 -unknown_dapi = np.uint8(unknown_dapi)*255 - - -# Marker labelling -ret, markers = cv2.connectedComponents(sure_fg) - -# Add one to all labels so that sure background is not 0, but 1 -markers_dapi = markers+100 - -# Now, mark the region of unknown with zero -markers_dapi[unknown_dapi==255] = 0 - -# Apply watershed using cv2 -markers_ws_dapi = cv2.watershed(Idapi_inv,markers_dapi) - - - - - - class CleanCellBoundaries(analysistask.ParallelAnalysisTask): ''' A task to construct a network graph where each cell is a node, and overlaps From a6b51b89751c90f2c3d2432a73b81badd2e38918 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Jan 2020 14:26:39 -0500 Subject: [PATCH 230/419] added method _apply_watershed --- merlin/analysis/segment.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 27c9456d..41f32d41 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -183,9 +183,9 @@ def _run_analysis(self, fragmentIndex): membraneMask = self._get_membrane_mask(fragmentIndex, watershedIndex) nucleiMask = self._get_nuclei_mask(fragmentIndex, watershedIndex) - watershedMarkers = self._generate_markers(nucleiMask,membraneMask) - - + watershedMarkers = self._get_watershed_markers(nucleiMask,membraneMask) + watershedOutput = self._apply_watershed(fragmentIndex, watershedIndex, + watershedMarkers) @@ -313,7 +313,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask return binary_fill_holes(nucleiMask) - def _generate_markers(self, nucleiMask: np.ndarray, + def _get_watershed_markers(self, nucleiMask: np.ndarray, membraneMask: np.ndarray) -> np.ndarray: watershedMarker = np.zeros(nucleiMask.shape) @@ -346,8 +346,8 @@ def _generate_markers(self, nucleiMask: np.ndarray, return watershedMarker - def _generate_watershed_mask(self, fov: int, channelIndex: int, - filterSigma: float) -> np.ndarray: + def _apply_watershed(self, fov: int, channelIndex: int, + watershedMarkers: np.ndarray) -> np.ndarray: def _read_and_filter_image_stack(self, fov: int, channelIndex: int, From 257b90217ef39d412f650cac8df8af40e1aa4afa Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Jan 2020 16:33:41 -0500 Subject: [PATCH 231/419] added method _convert_grayscale_to_rgb --- merlin/analysis/segment.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 41f32d41..8a80fac1 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -346,8 +346,33 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, return watershedMarker + def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: + # create 3D images of 8bit + # https://stackoverflow.com/questions/25485886/how-to-convert-a + # -16-bit-to-an-8-bit-image-in-opencv + + # invert image + uint16Image = 2**16 - uint16Image + + # convert to uint8 + ratio = np.amax(uint16Image) / 256 ; + uint8Image = (uint16Image / ratio).astype('uint8') + + rgbImage = np.zeros((2048,2048,3)) + rgbImage[:,:,0] = uint8Image + rgbImage[:,:,1] = uint8Image + rgbImage[:,:,2] = uint8Image + rgbImage = rgbImage.astype('uint8') + + return rgbImage + def _apply_watershed(self, fov: int, channelIndex: int, watershedMarkers: np.ndarray) -> np.ndarray: + warpTask = self.dataSet.load_analysis_task( + self.parameters['warp_task']) + + imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) + for z in range(len(self.dataSet.get_z_positions()))]) def _read_and_filter_image_stack(self, fov: int, channelIndex: int, From c247209989bf190915e4129bac7a11730576d6a6 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Jan 2020 16:45:05 -0500 Subject: [PATCH 232/419] modify _apply_watershed method --- merlin/analysis/segment.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 8a80fac1..51688cf5 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -374,6 +374,14 @@ def _apply_watershed(self, fov: int, channelIndex: int, imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) for z in range(len(self.dataSet.get_z_positions()))]) + watershedOutput = np.zeros(watershedMarkers.shape) + for z in range(len(self.dataSet.get_z_positions())): + rgbImage = convert_grayscale_to_rgb(dapiStack[:,:,z]) + watershedOutput[:,:,z] = cv2.watershed(rgbImage, + watershedMarkers[:,:,z]. + astype('int32')) + return watershedOutput + def _read_and_filter_image_stack(self, fov: int, channelIndex: int, filterSigma: float) -> np.ndarray: From a228f566ef0eee6362923a2c9106eb44ee7954f1 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Jan 2020 17:55:02 -0500 Subject: [PATCH 233/419] adding _combine_watershed_z_positions method, starting by pseudocode --- merlin/analysis/segment.py | 61 ++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 51688cf5..6e7a3c2b 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -181,15 +181,20 @@ def _run_analysis(self, fragmentIndex): watershedIndex = self.dataSet.get_data_organization() \ .get_data_channel_index(self.parameters['watershed_channel_name']) + # Prepare masks for cv2 watershed membraneMask = self._get_membrane_mask(fragmentIndex, watershedIndex) nucleiMask = self._get_nuclei_mask(fragmentIndex, watershedIndex) watershedMarkers = self._get_watershed_markers(nucleiMask,membraneMask) + + # perform watershed in individual z positions watershedOutput = self._apply_watershed(fragmentIndex, watershedIndex, watershedMarkers) + # combine all z positions in watershed + watershedCombinedOutput = self._combine_watershed_z_positions( + watershedOutput) - - + """ watershedImages = self._read_and_filter_image_stack(fragmentIndex, watershedIndex, 5) seeds = watershed.separate_merged_seeds( @@ -201,6 +206,7 @@ def _run_analysis(self, fragmentIndex): watershedOutput = segmentation.watershed( normalizedWatershed, measure.label(seeds), mask=watershedMask, connectivity=np.ones((3, 3, 3)), watershed_line=True) + """ zPos = np.array(self.dataSet.get_data_organization().get_z_positions()) featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( @@ -239,7 +245,7 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:,:,z] = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], - fineBlockSize, + fineBlockSize, offset=0) tresholdingMask[:,:,z] = remove_small_objects( imageStack[:,:,z].astype('bool'), @@ -267,11 +273,11 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: for z in range(len(self.dataSet.get_z_positions())): coarseThresholdingMask = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], - coarseBlockSize, + coarseBlockSize, offset=0) fineThresholdingMask = imageStack[:,:,z] > threshold_local(imageStack[:,:,z], - fineBlockSize, + fineBlockSize, offset=0) thresholdingMask[:,:,z] = coarseThresholdingMask* fineThresholdingMask @@ -310,17 +316,17 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseHessianMask[:,:,z]) # combine masks - nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask + nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask return binary_fill_holes(nucleiMask) - def _get_watershed_markers(self, nucleiMask: np.ndarray, + def _get_watershed_markers(self, nucleiMask: np.ndarray, membraneMask: np.ndarray) -> np.ndarray: watershedMarker = np.zeros(nucleiMask.shape) for z in range(len(self.dataSet.get_z_positions())): - # generate areas of sure bg and fg, as well as the area of + # generate areas of sure bg and fg, as well as the area of # unknown classification background = sm.dilation(nucleiMask[:,:,z],sm.selem.disk(15)) membraneDilated = sm.dilation(membraneMask[:,:,z].astype('bool'), @@ -347,15 +353,16 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, return watershedMarker def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: - # create 3D images of 8bit - # https://stackoverflow.com/questions/25485886/how-to-convert-a - # -16-bit-to-an-8-bit-image-in-opencv + # cv2 only works in 3D images of 8bit. Make a 3D grayscale by + # using the same grayscale image in each of the rgb channels + # code below based on https://stackoverflow.com/questions/ + # 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv # invert image uint16Image = 2**16 - uint16Image # convert to uint8 - ratio = np.amax(uint16Image) / 256 ; + ratio = np.amax(uint16Image) / 256 uint8Image = (uint16Image / ratio).astype('uint8') rgbImage = np.zeros((2048,2048,3)) @@ -382,7 +389,34 @@ def _apply_watershed(self, fov: int, channelIndex: int, astype('int32')) return watershedOutput + def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) + -> np.ndarray: + """ + PSEUDECODE + + initialize empty array the same size as the watershedOutput array + + start loop from the section farthest from the coverslip (N) + + for each nuclei + get nuclei indexes in sections N - 1 and N + 1 (if applies) + + if there is nuclei in the projection of N into N-1 and N+1 + get the index of the overlaping nuclei in N+1 + + find the most frequent non-zero index in N+1, + + else if projection N -> N+1 has overlaping nuclei + get the indexes of nuclei in N+1 + find the index in N+1, different from zero, that is most frequent + + + relabel_segmentation_stack + clean_segmentation_stack + + """ + """ def _read_and_filter_image_stack(self, fov: int, channelIndex: int, filterSigma: float) -> np.ndarray: filterSize = int(2*np.ceil(2*filterSigma)+1) @@ -392,7 +426,8 @@ def _read_and_filter_image_stack(self, fov: int, channelIndex: int, warpTask.get_aligned_image(fov, channelIndex, z), (filterSize, filterSize), filterSigma) for z in range(len(self.dataSet.get_z_positions()))]) - + """ + class CleanCellBoundaries(analysistask.ParallelAnalysisTask): ''' From eb266848249859773f7286393f3eed604e8b5573 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 24 Jan 2020 17:34:20 -0500 Subject: [PATCH 234/419] expanding _combine_watershed_z_positions and adding _get_overlapping_nuclei method --- merlin/analysis/segment.py | 91 +++++++++++++++++++++++--------------- 1 file changed, 56 insertions(+), 35 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 6e7a3c2b..685576ac 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -383,51 +383,72 @@ def _apply_watershed(self, fov: int, channelIndex: int, watershedOutput = np.zeros(watershedMarkers.shape) for z in range(len(self.dataSet.get_z_positions())): - rgbImage = convert_grayscale_to_rgb(dapiStack[:,:,z]) + rgbImage = _convert_grayscale_to_rgb(dapiStack[:,:,z]) watershedOutput[:,:,z] = cv2.watershed(rgbImage, watershedMarkers[:,:,z]. astype('int32')) return watershedOutput - def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) - -> np.ndarray: - """ - PSEUDECODE + def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, + watershedZ1: np.ndarray, n0: int): + z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) + z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes>100] + + if z1NucleiIndexes.shape[0] > 0: + + # calculate overlap fraction + n0Area = np.count_nonzero(watershedZ0 == n0) + n1Area = np.zeros(len(z1NucleiIndexes)) + overlapArea = np.zeros(len(z1NucleiIndexes)) + + for ii in range(len(z1NucleiIndexes)): + n1 = z1NucleiIndexes[ii] + n1Area[ii] = np.count_nonzero(watershedZ1 == n1) + overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) + *(watershedZ1 == n1)) + + n0OverlapFraction = np.asarray(overlapArea/n0Area) + n1OverlapFraction = np.asarray(overlapArea/n1Area) + index = list(range(len(n0OverlapFraction))) + + # select the nuclei that has the highest fraction in n0 and n1 + r1, r2, indexSorted = zip(*sorted(zip(n0OverlapFraction, + n1OverlapFraction, + index), + reverse=True)) + + if n0OverlapFraction[indexSorted[0]] > 0.2 and + n1OverlapFraction[indexSorted[0]] > 0.5: + return m1NucleiIndexes[indexSorted[0]], + n0OverlapFraction[indexSorted[0]], + n1OverlapFraction[indexSorted[0]] + else: + return False, False, False + else: + return False, False, False - initialize empty array the same size as the watershedOutput array - start loop from the section farthest from the coverslip (N) + def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) + -> np.ndarray: - for each nuclei - get nuclei indexes in sections N - 1 and N + 1 (if applies) + # Initialize empty array with size as watershedOutput array + watershedCombinedZ = np.zeros(watershedOutput.shape) - if there is nuclei in the projection of N into N-1 and N+1 - get the index of the overlaping nuclei in N+1 - - find the most frequent non-zero index in N+1, - - - else if projection N -> N+1 has overlaping nuclei - get the indexes of nuclei in N+1 - find the index in N+1, different from zero, that is most frequent - - - relabel_segmentation_stack - clean_segmentation_stack - - """ - """ - def _read_and_filter_image_stack(self, fov: int, channelIndex: int, - filterSigma: float) -> np.ndarray: - filterSize = int(2*np.ceil(2*filterSigma)+1) - warpTask = self.dataSet.load_analysis_task( - self.parameters['warp_task']) - return np.array([cv2.GaussianBlur( - warpTask.get_aligned_image(fov, channelIndex, z), - (filterSize, filterSize), filterSigma) - for z in range(len(self.dataSet.get_z_positions()))]) - """ + # copy the mask of the section farthest to the coverslip + watershedCombinedZ[:,:,-1] = watershedOutput[:,:,-1] + # starting far from coverslip + for z in range(len(self.dataSet.get_z_positions())-1,0,-1): + zNucleiIndex = np.unique(watershedOutput[:,:,z])[ + np.unique(watershedOutput[:,:,z])>100] + + for n0 in zNucleiIndex: # for each nuclei N(Z) in Z + n1,f0,f1 = _get_overlapping_nuclei(watershedCombinedZ[:,:,z], + watershedOutput[:,:,z-1],n0) + if n1: + watershedCombinedZ[:,:,z-1][watershedOutput[:,:,z-1] == n1] + = n0 + return watershedCombinedZ class CleanCellBoundaries(analysistask.ParallelAnalysisTask): ''' From 363de1128a75aa4c90443a33f4a73e285db468d2 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 24 Jan 2020 17:45:42 -0500 Subject: [PATCH 235/419] pep8 compliance --- merlin/analysis/segment.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 685576ac..924d219d 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -234,7 +234,7 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: low_threshold=0.5, high_threshold=0.8) edgeMask[:,:,z] = binary_closing(edgeMask[:,:,z],selem.disk(5)) edgeMask[:,:,z] = remove_small_objects( - edgeMask[:,:,z].astype('bool'), + edgeMask[:,:,z].astype('bool'), min_size=100, connectivity=1) edgeMask[:,:,z] = skeletonize(edgeMask[:,:,z]) """ @@ -287,7 +287,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: # generate border mask, necessary to avoid making a single # connected component when using binary_fill_holes below borderMask = np.zeros((2048,2048)) - borderMask[25:2023,25:2023] = 1 + borderMask[25:2023,25:2023] = 1 # TODO - use the image size variable for borderMask @@ -394,7 +394,7 @@ def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes>100] - if z1NucleiIndexes.shape[0] > 0: + if z1NucleiIndexes.shape[0] > 0: # calculate overlap fraction n0Area = np.count_nonzero(watershedZ0 == n0) @@ -417,36 +417,38 @@ def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, index), reverse=True)) - if n0OverlapFraction[indexSorted[0]] > 0.2 and + if n0OverlapFraction[indexSorted[0]] > 0.2 and n1OverlapFraction[indexSorted[0]] > 0.5: - return m1NucleiIndexes[indexSorted[0]], - n0OverlapFraction[indexSorted[0]], + return m1NucleiIndexes[indexSorted[0]], + n0OverlapFraction[indexSorted[0]], n1OverlapFraction[indexSorted[0]] else: - return False, False, False + return False, False, False else: return False, False, False def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) -> np.ndarray: - + # TO DO: this implementation is very rough, needs to be improved. + # good just for testing purposes + # Initialize empty array with size as watershedOutput array watershedCombinedZ = np.zeros(watershedOutput.shape) - # copy the mask of the section farthest to the coverslip + # copy the mask of the section farthest to the coverslip watershedCombinedZ[:,:,-1] = watershedOutput[:,:,-1] # starting far from coverslip - for z in range(len(self.dataSet.get_z_positions())-1,0,-1): + for z in range(len(self.dataSet.get_z_positions())-1,0,-1): zNucleiIndex = np.unique(watershedOutput[:,:,z])[ np.unique(watershedOutput[:,:,z])>100] for n0 in zNucleiIndex: # for each nuclei N(Z) in Z n1,f0,f1 = _get_overlapping_nuclei(watershedCombinedZ[:,:,z], - watershedOutput[:,:,z-1],n0) + watershedOutput[:,:,z-1],n0) if n1: - watershedCombinedZ[:,:,z-1][watershedOutput[:,:,z-1] == n1] + watershedCombinedZ[:,:,z-1][watershedOutput[:,:,z-1] == n1] = n0 return watershedCombinedZ From 09f1f1e0683e13be2af2f5e65e1330c0f00d3e05 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 24 Jan 2020 17:55:30 -0500 Subject: [PATCH 236/419] pep8 compliance --- merlin/analysis/segment.py | 58 +++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 924d219d..10167674 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -9,7 +9,7 @@ from shapely import geometry from typing import List, Dict from scipy.spatial import cKDTree -from scipy.ndimage.morphology import binary_fill_holes +from scipy.ndimage.morphology import binary_fill_holes from merlin.core import dataset from merlin.core import analysistask @@ -123,18 +123,19 @@ def _read_and_filter_image_stack(self, fov: int, channelIndex: int, (filterSize, filterSize), filterSigma) for z in range(len(self.dataSet.get_z_positions()))]) + class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): """ An analysis task that determines the boundaries of features in the - image data in each field of view using a watershed algorithm + image data in each field of view using a watershed algorithm implemented in CV2. - A tutorial explaining the general scheme of the method can be + A tutorial explaining the general scheme of the method can be found in https://opencv-python-tutroals.readthedocs.io/en/latest/ - py_tutorials/py_imgproc/py_watershed/py_watershed.html. + py_tutorials/py_imgproc/py_watershed/py_watershed.html. - The watershed segmentation is performed in each z-position + The watershed segmentation is performed in each z-position independently and combined into 3D objects in a later step Since each field of view is analyzed individually, the segmentation @@ -199,7 +200,8 @@ def _run_analysis(self, fragmentIndex): watershedIndex, 5) seeds = watershed.separate_merged_seeds( watershed.extract_seeds(seedImages)) - normalizedWatershed, watershedMask = watershed.prepare_watershed_images( + normalizedWatershed, watershedMask = + watershed.prepare_watershed_images( watershedImages) seeds[np.invert(watershedMask)] = 0 @@ -271,12 +273,12 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseBlockSize = 241 fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = imageStack[:,:,z] > - threshold_local(imageStack[:,:,z], + coarseThresholdingMask = imageStack[:,:,z] + >threshold_local(imageStack[:,:,z], coarseBlockSize, offset=0) - fineThresholdingMask = imageStack[:,:,z] > - threshold_local(imageStack[:,:,z], + fineThresholdingMask = imageStack[:,:,z] + > threshold_local(imageStack[:,:,z], fineBlockSize, offset=0) thresholdingMask[:,:,z] = coarseThresholdingMask* @@ -291,7 +293,6 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: # TODO - use the image size variable for borderMask - # generate nuclei mask from hessian, fine fineHessianMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): @@ -301,7 +302,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: selem.disk(5)) fineHessianMask[:,:,z] = fineHessianMask[:,:,z]*borderMask fineHessianMask[:,:,z] = binary_fill_holes(fineHessianMask[:,:,z]) - + # generate dapi mask from hessian, coarse coarseHessianMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): @@ -314,18 +315,17 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseHessianMask[:,:,z] = coarseHessianMask[:,:,z]*borderMask coarseHessianMask[:,:,z] = binary_fill_holes( coarseHessianMask[:,:,z]) - + # combine masks nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask return binary_fill_holes(nucleiMask) def _get_watershed_markers(self, nucleiMask: np.ndarray, membraneMask: np.ndarray) -> np.ndarray: - watershedMarker = np.zeros(nucleiMask.shape) for z in range(len(self.dataSet.get_z_positions())): - + # generate areas of sure bg and fg, as well as the area of # unknown classification background = sm.dilation(nucleiMask[:,:,z],sm.selem.disk(15)) @@ -334,11 +334,11 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, foreground = sm.erosion(nucleiMask[:,:,z]*~membraneDilated, sm.selem.disk(5)) unknown = background*~foreground - + background = np.uint8(background)*255 foreground = np.uint8(foreground)*255 unknown = np.uint8(unknown)*255 - + # Marker labelling ret, markers = cv2.connectedComponents(foreground) @@ -357,20 +357,20 @@ def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: # using the same grayscale image in each of the rgb channels # code below based on https://stackoverflow.com/questions/ # 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv - + # invert image uint16Image = 2**16 - uint16Image - + # convert to uint8 ratio = np.amax(uint16Image) / 256 uint8Image = (uint16Image / ratio).astype('uint8') - + rgbImage = np.zeros((2048,2048,3)) rgbImage[:,:,0] = uint8Image rgbImage[:,:,1] = uint8Image rgbImage[:,:,2] = uint8Image rgbImage = rgbImage.astype('uint8') - + return rgbImage def _apply_watershed(self, fov: int, channelIndex: int, @@ -393,30 +393,30 @@ def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, watershedZ1: np.ndarray, n0: int): z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes>100] - + if z1NucleiIndexes.shape[0] > 0: - + # calculate overlap fraction n0Area = np.count_nonzero(watershedZ0 == n0) n1Area = np.zeros(len(z1NucleiIndexes)) overlapArea = np.zeros(len(z1NucleiIndexes)) - + for ii in range(len(z1NucleiIndexes)): n1 = z1NucleiIndexes[ii] n1Area[ii] = np.count_nonzero(watershedZ1 == n1) overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) *(watershedZ1 == n1)) - + n0OverlapFraction = np.asarray(overlapArea/n0Area) n1OverlapFraction = np.asarray(overlapArea/n1Area) index = list(range(len(n0OverlapFraction))) - + # select the nuclei that has the highest fraction in n0 and n1 r1, r2, indexSorted = zip(*sorted(zip(n0OverlapFraction, n1OverlapFraction, index), reverse=True)) - + if n0OverlapFraction[indexSorted[0]] > 0.2 and n1OverlapFraction[indexSorted[0]] > 0.5: return m1NucleiIndexes[indexSorted[0]], @@ -430,7 +430,7 @@ def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) -> np.ndarray: - # TO DO: this implementation is very rough, needs to be improved. + # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes # Initialize empty array with size as watershedOutput array @@ -443,7 +443,7 @@ def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) for z in range(len(self.dataSet.get_z_positions())-1,0,-1): zNucleiIndex = np.unique(watershedOutput[:,:,z])[ np.unique(watershedOutput[:,:,z])>100] - + for n0 in zNucleiIndex: # for each nuclei N(Z) in Z n1,f0,f1 = _get_overlapping_nuclei(watershedCombinedZ[:,:,z], watershedOutput[:,:,z-1],n0) From 27cb38e57169511bf853dd61a7ae2272553d3506 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 24 Jan 2020 18:08:59 -0500 Subject: [PATCH 237/419] pep8 compliance --- merlin/analysis/segment.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 10167674..dc706e2a 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -137,7 +137,7 @@ class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): The watershed segmentation is performed in each z-position independently and combined into 3D objects in a later step - + Since each field of view is analyzed individually, the segmentation results should be cleaned in order to merge cells that cross the field of view boundary. @@ -181,12 +181,13 @@ def _run_analysis(self, fragmentIndex): watershedIndex = self.dataSet.get_data_organization() \ .get_data_channel_index(self.parameters['watershed_channel_name']) - + # Prepare masks for cv2 watershed membraneMask = self._get_membrane_mask(fragmentIndex, watershedIndex) nucleiMask = self._get_nuclei_mask(fragmentIndex, watershedIndex) - watershedMarkers = self._get_watershed_markers(nucleiMask,membraneMask) - + watershedMarkers = self._get_watershed_markers(nucleiMask, + membraneMask) + # perform watershed in individual z positions watershedOutput = self._apply_watershed(fragmentIndex, watershedIndex, watershedMarkers) From 1d8082f6bdeef9a1de9d3962a35de36638cd1eac Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 10:58:37 -0500 Subject: [PATCH 238/419] pep8 compliance --- merlin/analysis/segment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index dc706e2a..0b330080 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -186,7 +186,7 @@ def _run_analysis(self, fragmentIndex): membraneMask = self._get_membrane_mask(fragmentIndex, watershedIndex) nucleiMask = self._get_nuclei_mask(fragmentIndex, watershedIndex) watershedMarkers = self._get_watershed_markers(nucleiMask, - membraneMask) + membraneMask) # perform watershed in individual z positions watershedOutput = self._apply_watershed(fragmentIndex, watershedIndex, From 20c86227d01242cce5305f20654344ff06aba451 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:04:04 -0500 Subject: [PATCH 239/419] pep8 compliance --- merlin/analysis/segment.py | 98 +++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 0b330080..736f8934 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -226,36 +226,36 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) for z in range(len(self.dataSet.get_z_positions()))]) - + # generate mask based on edge detection """ edgeMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): - edgeMask[:,:,z] = canny( - white_tophat(imageStack[:,:,z], selem.disk(10)), + edgeMask[:, :, z] = canny( + white_tophat(imageStack[:, :, z], selem.disk(10)), sigma=2, use_quantiles=True, low_threshold=0.5, high_threshold=0.8) - edgeMask[:,:,z] = binary_closing(edgeMask[:,:,z],selem.disk(5)) - edgeMask[:,:,z] = remove_small_objects( - edgeMask[:,:,z].astype('bool'), + edgeMask[:, :, z] = binary_closing(edgeMask[:, :, z],selem.disk(5)) + edgeMask[:, :, z] = remove_small_objects( + edgeMask[:, :, z].astype('bool'), min_size=100, connectivity=1) - edgeMask[:,:,z] = skeletonize(edgeMask[:,:,z]) + edgeMask[:, :, z] = skeletonize(edgeMask[:, :, z]) """ # generate mask based on thresholding tresholdingMask = np.zeros(imageStack.shape) fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - tresholdingMask[:,:,z] = imageStack[:,:,z] > - threshold_local(imageStack[:,:,z], + tresholdingMask[:, :, z] = imageStack[:, :, z] > + threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) - tresholdingMask[:,:,z] = remove_small_objects( - imageStack[:,:,z].astype('bool'), + tresholdingMask[:, :, z] = remove_small_objects( + imageStack[:, :, z].astype('bool'), min_size=100, connectivity=1) - tresholdingMask[:,:,z] = binary_closing(imageStack[:,:,z], + tresholdingMask[:, :, z] = binary_closing(imageStack[:, :, z], selem.disk(5)) - tresholdingMask[:,:,z] = skeletonize(imageStack[:,:,z]) + tresholdingMask[:, :, z] = skeletonize(imageStack[:, :, z]) # combine masks # return edgeMask + thresholdingMask @@ -274,18 +274,18 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseBlockSize = 241 fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = imageStack[:,:,z] - >threshold_local(imageStack[:,:,z], + coarseThresholdingMask = imageStack[:, :, z] + >threshold_local(imageStack[:, :, z], coarseBlockSize, offset=0) - fineThresholdingMask = imageStack[:,:,z] - > threshold_local(imageStack[:,:,z], + fineThresholdingMask = imageStack[:, :, z] + > threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) - thresholdingMask[:,:,z] = coarseThresholdingMask* + thresholdingMask[:, :, z] = coarseThresholdingMask* fineThresholdingMask - thresholdingMask[:,:,z] = binary_fill_holes( - thresholdingMask[:,:,z]) + thresholdingMask[:, :, z] = binary_fill_holes( + thresholdingMask[:, :, z]) # generate border mask, necessary to avoid making a single # connected component when using binary_fill_holes below @@ -297,25 +297,25 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: # generate nuclei mask from hessian, fine fineHessianMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): - fineHessian = hessian(imageStack[:,:,z]) - fineHessianMask[:,:,z] = fineHessian == fineHessian.max() - fineHessianMask[:,:,z] = binary_closing(fineHessianMask[:,:,z], + fineHessian = hessian(imageStack[:, :, z]) + fineHessianMask[:, :, z] = fineHessian == fineHessian.max() + fineHessianMask[:, :, z] = binary_closing(fineHessianMask[:, :, z], selem.disk(5)) - fineHessianMask[:,:,z] = fineHessianMask[:,:,z]*borderMask - fineHessianMask[:,:,z] = binary_fill_holes(fineHessianMask[:,:,z]) + fineHessianMask[:, :, z] = fineHessianMask[:, :, z]*borderMask + fineHessianMask[:, :, z] = binary_fill_holes(fineHessianMask[:, :, z]) # generate dapi mask from hessian, coarse coarseHessianMask = np.zeros(imageStack.shape) for z in range(len(self.dataSet.get_z_positions())): - coarseHessian = hessian(imageStack[:,:,z] - - white_tophat(imageStack[:,:,z], + coarseHessian = hessian(imageStack[:, :, z] - + white_tophat(imageStack[:, :, z], selem.disk(20))) - coarseHessianMask[:,:,z] = coarseHessian == coarseHessian.max() - coarseHessianMask[:,:,z] = binary_closing(coarseHessianMask[:,:,z], + coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() + coarseHessianMask[:, :, z] = binary_closing(coarseHessianMask[:, :, z], selem.disk(5)) - coarseHessianMask[:,:,z] = coarseHessianMask[:,:,z]*borderMask - coarseHessianMask[:,:,z] = binary_fill_holes( - coarseHessianMask[:,:,z]) + coarseHessianMask[:, :, z] = coarseHessianMask[:, :, z]*borderMask + coarseHessianMask[:, :, z] = binary_fill_holes( + coarseHessianMask[:, :, z]) # combine masks nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask @@ -329,10 +329,10 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, # generate areas of sure bg and fg, as well as the area of # unknown classification - background = sm.dilation(nucleiMask[:,:,z],sm.selem.disk(15)) - membraneDilated = sm.dilation(membraneMask[:,:,z].astype('bool'), + background = sm.dilation(nucleiMask[:, :, z],sm.selem.disk(15)) + membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), sm.selem.disk(10)) - foreground = sm.erosion(nucleiMask[:,:,z]*~membraneDilated, + foreground = sm.erosion(nucleiMask[:, :, z]*~membraneDilated, sm.selem.disk(5)) unknown = background*~foreground @@ -349,7 +349,7 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, # Now, mark the region of unknown with zero markers[unknown==255] = 0 - watershedMarker[:,:,z] = markers + watershedMarker[:, :, z] = markers return watershedMarker @@ -367,9 +367,9 @@ def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: uint8Image = (uint16Image / ratio).astype('uint8') rgbImage = np.zeros((2048,2048,3)) - rgbImage[:,:,0] = uint8Image - rgbImage[:,:,1] = uint8Image - rgbImage[:,:,2] = uint8Image + rgbImage[:, :, 0] = uint8Image + rgbImage[:, :, 1] = uint8Image + rgbImage[:, :, 2] = uint8Image rgbImage = rgbImage.astype('uint8') return rgbImage @@ -384,9 +384,9 @@ def _apply_watershed(self, fov: int, channelIndex: int, watershedOutput = np.zeros(watershedMarkers.shape) for z in range(len(self.dataSet.get_z_positions())): - rgbImage = _convert_grayscale_to_rgb(dapiStack[:,:,z]) - watershedOutput[:,:,z] = cv2.watershed(rgbImage, - watershedMarkers[:,:,z]. + rgbImage = _convert_grayscale_to_rgb(dapiStack[:, :, z]) + watershedOutput[:, :, z] = cv2.watershed(rgbImage, + watershedMarkers[:, :, z]. astype('int32')) return watershedOutput @@ -438,18 +438,18 @@ def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) watershedCombinedZ = np.zeros(watershedOutput.shape) # copy the mask of the section farthest to the coverslip - watershedCombinedZ[:,:,-1] = watershedOutput[:,:,-1] + watershedCombinedZ[:, :, -1] = watershedOutput[:, :, -1] # starting far from coverslip - for z in range(len(self.dataSet.get_z_positions())-1,0,-1): - zNucleiIndex = np.unique(watershedOutput[:,:,z])[ - np.unique(watershedOutput[:,:,z])>100] + for z in range(len(self.dataSet.get_z_positions())-1, 0, -1): + zNucleiIndex = np.unique(watershedOutput[:, :, z])[ + np.unique(watershedOutput[:, :, z])>100] for n0 in zNucleiIndex: # for each nuclei N(Z) in Z - n1,f0,f1 = _get_overlapping_nuclei(watershedCombinedZ[:,:,z], - watershedOutput[:,:,z-1],n0) + n1,f0,f1 = _get_overlapping_nuclei(watershedCombinedZ[:, :, z], + watershedOutput[:, :, z-1],n0) if n1: - watershedCombinedZ[:,:,z-1][watershedOutput[:,:,z-1] == n1] + watershedCombinedZ[:, :, z-1][watershedOutput[:, :, z-1] == n1] = n0 return watershedCombinedZ From d045c61c4aeebe121660f4b9bcbf95bd819afe19 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:09:06 -0500 Subject: [PATCH 240/419] pep8 compliance --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 736f8934..b9ce29ab 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -225,8 +225,8 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: self.parameters['warp_task']) imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet.get_z_positions()))]) - + for z in range(len(self.dataSet. + get_z_positions()))]) # generate mask based on edge detection """ edgeMask = np.zeros(imageStack.shape) From e17bf95dc60ce13f2d4ac8e5be1ff02d0771c241 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:11:58 -0500 Subject: [PATCH 241/419] pep8 compliance --- merlin/analysis/segment.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index b9ce29ab..8e1b810e 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -225,7 +225,7 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: self.parameters['warp_task']) imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet. + for z in range(len(self.dataSet. get_z_positions()))]) # generate mask based on edge detection """ @@ -247,14 +247,12 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:, :, z] = imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], - fineBlockSize, - offset=0) + threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) tresholdingMask[:, :, z] = remove_small_objects( - imageStack[:, :, z].astype('bool'), - min_size=100, connectivity=1) + imageStack[:, :, z].astype('bool'), min_size=100, + connectivity=1) tresholdingMask[:, :, z] = binary_closing(imageStack[:, :, z], - selem.disk(5)) + selem.disk(5)) tresholdingMask[:, :, z] = skeletonize(imageStack[:, :, z]) # combine masks From 0a8d94c7753afe29ca72553f419aaad24c5d692f Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:12:48 -0500 Subject: [PATCH 242/419] pep8 compliance --- merlin/analysis/segment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 8e1b810e..32d4317a 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -225,7 +225,7 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: self.parameters['warp_task']) imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet. + for z in range(len(self.dataSet. get_z_positions()))]) # generate mask based on edge detection """ From 977dbba675182f00b318388737a77b9bbc174a90 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:13:39 -0500 Subject: [PATCH 243/419] pep8 compliance --- merlin/analysis/segment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 32d4317a..52911b4e 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -225,7 +225,7 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: self.parameters['warp_task']) imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet. + for z in range(len(self.dataSet. get_z_positions()))]) # generate mask based on edge detection """ From b32ce41e6d0aea1b6b8cbbce3f10c7c075b80032 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:15:00 -0500 Subject: [PATCH 244/419] pep8 compliance --- merlin/analysis/segment.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 52911b4e..34de8891 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -226,7 +226,7 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) for z in range(len(self.dataSet. - get_z_positions()))]) + get_z_positions()))]) # generate mask based on edge detection """ edgeMask = np.zeros(imageStack.shape) @@ -247,7 +247,9 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:, :, z] = imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) + threshold_local(imageStack[:, :, z], + fineBlockSize, + offset=0) tresholdingMask[:, :, z] = remove_small_objects( imageStack[:, :, z].astype('bool'), min_size=100, connectivity=1) From 02ba35e8e895e91bbb4fe7e8221b82bafa399dca Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:15:52 -0500 Subject: [PATCH 245/419] pep8 compliance --- merlin/analysis/segment.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 34de8891..ac620bef 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -247,9 +247,9 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:, :, z] = imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], - fineBlockSize, - offset=0) + threshold_local(imageStack[:, :, z], + fineBlockSize, + offset=0) tresholdingMask[:, :, z] = remove_small_objects( imageStack[:, :, z].astype('bool'), min_size=100, connectivity=1) From d45769ffd76b3fd06e0e17441b7d2bf6bf84a340 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:19:12 -0500 Subject: [PATCH 246/419] pep8 compliance --- merlin/analysis/segment.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index ac620bef..d9a3751d 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -247,14 +247,14 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:, :, z] = imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], - fineBlockSize, - offset=0) + threshold_local(imageStack[:, :, z], + fineBlockSize, + offset=0) tresholdingMask[:, :, z] = remove_small_objects( - imageStack[:, :, z].astype('bool'), min_size=100, + imageStack[:, :, z].astype('bool'), min_size=100, connectivity=1) tresholdingMask[:, :, z] = binary_closing(imageStack[:, :, z], - selem.disk(5)) + selem.disk(5)) tresholdingMask[:, :, z] = skeletonize(imageStack[:, :, z]) # combine masks @@ -267,7 +267,8 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: self.parameters['warp_task']) imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet.get_z_positions()))]) + for z in range(len(self.dataSet. + get_z_positions()))]) # generate nuclei mask based on thresholding thresholdingMask = np.zeros(imageStack.shape) From 4b5fa6118d4c0ed2edfebb8c04c6380fc75b1d2a Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:27:50 -0500 Subject: [PATCH 247/419] pep8 compliance --- merlin/analysis/segment.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index d9a3751d..0e6fe0e5 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -247,14 +247,15 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:, :, z] = imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], - fineBlockSize, - offset=0) + threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) + tresholdingMask[:, :, z] = remove_small_objects( imageStack[:, :, z].astype('bool'), min_size=100, connectivity=1) + tresholdingMask[:, :, z] = binary_closing(imageStack[:, :, z], - selem.disk(5)) + selem.disk(5)) + tresholdingMask[:, :, z] = skeletonize(imageStack[:, :, z]) # combine masks @@ -290,8 +291,8 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: # generate border mask, necessary to avoid making a single # connected component when using binary_fill_holes below - borderMask = np.zeros((2048,2048)) - borderMask[25:2023,25:2023] = 1 + borderMask = np.zeros((2048, 2048)) + borderMask[25:2023, 25:2023] = 1 # TODO - use the image size variable for borderMask @@ -330,7 +331,7 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, # generate areas of sure bg and fg, as well as the area of # unknown classification - background = sm.dilation(nucleiMask[:, :, z],sm.selem.disk(15)) + background = sm.dilation(nucleiMask[:, :, z], sm.selem.disk(15)) membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), sm.selem.disk(10)) foreground = sm.erosion(nucleiMask[:, :, z]*~membraneDilated, @@ -367,7 +368,7 @@ def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: ratio = np.amax(uint16Image) / 256 uint8Image = (uint16Image / ratio).astype('uint8') - rgbImage = np.zeros((2048,2048,3)) + rgbImage = np.zeros((2048, 2048, 3)) rgbImage[:, :, 0] = uint8Image rgbImage[:, :, 1] = uint8Image rgbImage[:, :, 2] = uint8Image From c3052e829f54a6e4148801dcefdf9e81c828848a Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:30:30 -0500 Subject: [PATCH 248/419] pep8 compliance --- merlin/analysis/segment.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 0e6fe0e5..0281d446 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -248,14 +248,14 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:, :, z] = imageStack[:, :, z] > threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) - + tresholdingMask[:, :, z] = remove_small_objects( imageStack[:, :, z].astype('bool'), min_size=100, connectivity=1) - + tresholdingMask[:, :, z] = binary_closing(imageStack[:, :, z], - selem.disk(5)) - + selem.disk(5)) + tresholdingMask[:, :, z] = skeletonize(imageStack[:, :, z]) # combine masks @@ -276,16 +276,15 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseBlockSize = 241 fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = imageStack[:, :, z] - >threshold_local(imageStack[:, :, z], - coarseBlockSize, - offset=0) - fineThresholdingMask = imageStack[:, :, z] - > threshold_local(imageStack[:, :, z], - fineBlockSize, - offset=0) + coarseThresholdingMask = imageStack[:, :, z] > + threshold_local(imageStack[:, :, z], coarseBlockSize, offset=0) + + fineThresholdingMask = imageStack[:, :, z] > + threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) + thresholdingMask[:, :, z] = coarseThresholdingMask* fineThresholdingMask + thresholdingMask[:, :, z] = binary_fill_holes( thresholdingMask[:, :, z]) From 21cf45a38d3e9f64bec669365c0a585bbaccf15c Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:40:24 -0500 Subject: [PATCH 249/419] pep8 compliance --- merlin/analysis/segment.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 0281d446..8a2cba45 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -248,14 +248,11 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: for z in range(len(self.dataSet.get_z_positions())): tresholdingMask[:, :, z] = imageStack[:, :, z] > threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) - tresholdingMask[:, :, z] = remove_small_objects( imageStack[:, :, z].astype('bool'), min_size=100, connectivity=1) - tresholdingMask[:, :, z] = binary_closing(imageStack[:, :, z], selem.disk(5)) - tresholdingMask[:, :, z] = skeletonize(imageStack[:, :, z]) # combine masks @@ -276,15 +273,12 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseBlockSize = 241 fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = imageStack[:, :, z] > + coarseThresholdingMask = imageStack[:, :, z] > threshold_local(imageStack[:, :, z], coarseBlockSize, offset=0) - - fineThresholdingMask = imageStack[:, :, z] > + fineThresholdingMask = imageStack[:, :, z] > threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) - - thresholdingMask[:, :, z] = coarseThresholdingMask* - fineThresholdingMask - + thresholdingMask[:, :, z] = coarseThresholdingMask * + fineThresholdingMask thresholdingMask[:, :, z] = binary_fill_holes( thresholdingMask[:, :, z]) @@ -301,9 +295,10 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: fineHessian = hessian(imageStack[:, :, z]) fineHessianMask[:, :, z] = fineHessian == fineHessian.max() fineHessianMask[:, :, z] = binary_closing(fineHessianMask[:, :, z], - selem.disk(5)) + selem.disk(5)) fineHessianMask[:, :, z] = fineHessianMask[:, :, z]*borderMask - fineHessianMask[:, :, z] = binary_fill_holes(fineHessianMask[:, :, z]) + fineHessianMask[:, :, z] = binary_fill_holes( + fineHessianMask[:, :, z]) # generate dapi mask from hessian, coarse coarseHessianMask = np.zeros(imageStack.shape) From 5d465b43870238d41e9d2d1644423d76da9b0645 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:44:45 -0500 Subject: [PATCH 250/419] pep8 compliance --- merlin/analysis/segment.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 8a2cba45..26ef2d02 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -277,7 +277,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: threshold_local(imageStack[:, :, z], coarseBlockSize, offset=0) fineThresholdingMask = imageStack[:, :, z] > threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) - thresholdingMask[:, :, z] = coarseThresholdingMask * + thresholdingMask[:, :, z] = coarseThresholdingMask * fineThresholdingMask thresholdingMask[:, :, z] = binary_fill_holes( thresholdingMask[:, :, z]) @@ -307,8 +307,8 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: white_tophat(imageStack[:, :, z], selem.disk(20))) coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() - coarseHessianMask[:, :, z] = binary_closing(coarseHessianMask[:, :, z], - selem.disk(5)) + coarseHessianMask[:, :, z] = binary_closing( + coarseHessianMask[:, :, z], selem.disk(5)) coarseHessianMask[:, :, z] = coarseHessianMask[:, :, z]*borderMask coarseHessianMask[:, :, z] = binary_fill_holes( coarseHessianMask[:, :, z]) @@ -326,10 +326,10 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, # generate areas of sure bg and fg, as well as the area of # unknown classification background = sm.dilation(nucleiMask[:, :, z], sm.selem.disk(15)) - membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), - sm.selem.disk(10)) + membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), + sm.selem.disk(10)) foreground = sm.erosion(nucleiMask[:, :, z]*~membraneDilated, - sm.selem.disk(5)) + sm.selem.disk(5)) unknown = background*~foreground background = np.uint8(background)*255 From 28e875ad2a262792685f1bb86f8a2af6bdf4729b Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:48:35 -0500 Subject: [PATCH 251/419] pep8 compliance --- merlin/analysis/segment.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 26ef2d02..5b0886a7 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -318,7 +318,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: return binary_fill_holes(nucleiMask) def _get_watershed_markers(self, nucleiMask: np.ndarray, - membraneMask: np.ndarray) -> np.ndarray: + membraneMask: np.ndarray) -> np.ndarray: watershedMarker = np.zeros(nucleiMask.shape) for z in range(len(self.dataSet.get_z_positions())): @@ -327,14 +327,14 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, # unknown classification background = sm.dilation(nucleiMask[:, :, z], sm.selem.disk(15)) membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), - sm.selem.disk(10)) + sm.selem.disk(10)) foreground = sm.erosion(nucleiMask[:, :, z]*~membraneDilated, - sm.selem.disk(5)) + sm.selem.disk(5)) unknown = background*~foreground background = np.uint8(background)*255 foreground = np.uint8(foreground)*255 - unknown = np.uint8(unknown)*255 + unknown = np.uint8(unknown)*255 # Marker labelling ret, markers = cv2.connectedComponents(foreground) @@ -343,7 +343,7 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, markers = markers+100 # Now, mark the region of unknown with zero - markers[unknown==255] = 0 + markers[unknown == 255] = 0 watershedMarker[:, :, z] = markers @@ -371,12 +371,13 @@ def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: return rgbImage def _apply_watershed(self, fov: int, channelIndex: int, - watershedMarkers: np.ndarray) -> np.ndarray: - warpTask = self.dataSet.load_analysis_task( + watershedMarkers: np.ndarray) -> np.ndarray: + warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet.get_z_positions()))]) + for z in range(len(self.dataSet. + get_z_positions()))]) watershedOutput = np.zeros(watershedMarkers.shape) for z in range(len(self.dataSet.get_z_positions())): From 4104ef2cbd7a4592796382947225ea321fb3ee66 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:53:48 -0500 Subject: [PATCH 252/419] pep8 compliance --- merlin/analysis/segment.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 5b0886a7..3473f4c6 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -318,7 +318,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: return binary_fill_holes(nucleiMask) def _get_watershed_markers(self, nucleiMask: np.ndarray, - membraneMask: np.ndarray) -> np.ndarray: + membraneMask: np.ndarray) -> np.ndarray: watershedMarker = np.zeros(nucleiMask.shape) for z in range(len(self.dataSet.get_z_positions())): @@ -371,7 +371,7 @@ def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: return rgbImage def _apply_watershed(self, fov: int, channelIndex: int, - watershedMarkers: np.ndarray) -> np.ndarray: + watershedMarkers: np.ndarray) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) @@ -383,18 +383,18 @@ def _apply_watershed(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions())): rgbImage = _convert_grayscale_to_rgb(dapiStack[:, :, z]) watershedOutput[:, :, z] = cv2.watershed(rgbImage, - watershedMarkers[:, :, z]. - astype('int32')) + watershedMarkers[:, :, z]. + astype('int32')) return watershedOutput - def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, - watershedZ1: np.ndarray, n0: int): + def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, + watershedZ1: np.ndarray, n0: int): z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) - z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes>100] + z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes > 100] if z1NucleiIndexes.shape[0] > 0: - # calculate overlap fraction + # calculate overlap fraction n0Area = np.count_nonzero(watershedZ0 == n0) n1Area = np.zeros(len(z1NucleiIndexes)) overlapArea = np.zeros(len(z1NucleiIndexes)) @@ -402,8 +402,8 @@ def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, for ii in range(len(z1NucleiIndexes)): n1 = z1NucleiIndexes[ii] n1Area[ii] = np.count_nonzero(watershedZ1 == n1) - overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) - *(watershedZ1 == n1)) + overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * + (watershedZ1 == n1)) n0OverlapFraction = np.asarray(overlapArea/n0Area) n1OverlapFraction = np.asarray(overlapArea/n1Area) @@ -415,11 +415,11 @@ def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, index), reverse=True)) - if n0OverlapFraction[indexSorted[0]] > 0.2 and + if n0OverlapFraction[indexSorted[0]] > 0.2 and n1OverlapFraction[indexSorted[0]] > 0.5: return m1NucleiIndexes[indexSorted[0]], - n0OverlapFraction[indexSorted[0]], - n1OverlapFraction[indexSorted[0]] + n0OverlapFraction[indexSorted[0]], + n1OverlapFraction[indexSorted[0]] else: return False, False, False else: From 4c89a958aca467448b3e44d91b0da0c445dcb258 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:56:41 -0500 Subject: [PATCH 253/419] pep8 compliance --- merlin/analysis/segment.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 3473f4c6..3808b44f 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -318,7 +318,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: return binary_fill_holes(nucleiMask) def _get_watershed_markers(self, nucleiMask: np.ndarray, - membraneMask: np.ndarray) -> np.ndarray: + membraneMask: np.ndarray) -> np.ndarray: watershedMarker = np.zeros(nucleiMask.shape) for z in range(len(self.dataSet.get_z_positions())): @@ -371,7 +371,7 @@ def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: return rgbImage def _apply_watershed(self, fov: int, channelIndex: int, - watershedMarkers: np.ndarray) -> np.ndarray: + watershedMarkers: np.ndarray) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) @@ -387,8 +387,8 @@ def _apply_watershed(self, fov: int, channelIndex: int, astype('int32')) return watershedOutput - def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, - watershedZ1: np.ndarray, n0: int): + def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, + watershedZ1: np.ndarray, n0: int): z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes > 100] @@ -402,7 +402,7 @@ def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, for ii in range(len(z1NucleiIndexes)): n1 = z1NucleiIndexes[ii] n1Area[ii] = np.count_nonzero(watershedZ1 == n1) - overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * + overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * (watershedZ1 == n1)) n0OverlapFraction = np.asarray(overlapArea/n0Area) @@ -415,8 +415,8 @@ def _get_overlapping_nuclei(self,watershedZ0: np.ndarray, index), reverse=True)) - if n0OverlapFraction[indexSorted[0]] > 0.2 and - n1OverlapFraction[indexSorted[0]] > 0.5: + if n0OverlapFraction[indexSorted[0]] > 0.2 and + n1OverlapFraction[indexSorted[0]] > 0.5: return m1NucleiIndexes[indexSorted[0]], n0OverlapFraction[indexSorted[0]], n1OverlapFraction[indexSorted[0]] From 0002003cdbae27de6f19a47a4b582376ec06ca1a Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:59:20 -0500 Subject: [PATCH 254/419] pep8 compliance --- merlin/analysis/segment.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 3808b44f..72049a8c 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -418,8 +418,8 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, if n0OverlapFraction[indexSorted[0]] > 0.2 and n1OverlapFraction[indexSorted[0]] > 0.5: return m1NucleiIndexes[indexSorted[0]], - n0OverlapFraction[indexSorted[0]], - n1OverlapFraction[indexSorted[0]] + n0OverlapFraction[indexSorted[0]], + n1OverlapFraction[indexSorted[0]] else: return False, False, False else: @@ -427,7 +427,7 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) - -> np.ndarray: + -> np.ndarray: # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes From d346f273427f4ebf76b3115fa6e9883d7d052045 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 12:00:52 -0500 Subject: [PATCH 255/419] pep8 compliance --- merlin/analysis/segment.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 72049a8c..c17f0708 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -425,9 +425,8 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, else: return False, False, False - def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) - -> np.ndarray: + -> np.ndarray: # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes From 6ee9e19cee87f329fa3276e3e9fc65c45acbc659 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 12:02:24 -0500 Subject: [PATCH 256/419] pep8 compliance --- merlin/analysis/segment.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index c17f0708..b3911369 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -425,8 +425,9 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, else: return False, False, False - def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) - -> np.ndarray: + def _combine_watershed_z_positions(self, + watershedOutput: np.ndarray) + -> np.ndarray: # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes From fa5e4ba4ce12d41309039454a564524ba7acd652 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 13:26:40 -0500 Subject: [PATCH 257/419] fixing invalid syntax --- merlin/analysis/segment.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index b3911369..3eafec1b 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -246,8 +246,10 @@ def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: tresholdingMask = np.zeros(imageStack.shape) fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - tresholdingMask[:, :, z] = imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) + tresholdingMask[:, :, z] = (imageStack[:, :, z] > + threshold_local(imageStack[:, :, z], + fineBlockSize, + offset=0)) tresholdingMask[:, :, z] = remove_small_objects( imageStack[:, :, z].astype('bool'), min_size=100, connectivity=1) @@ -425,9 +427,8 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, else: return False, False, False - def _combine_watershed_z_positions(self, - watershedOutput: np.ndarray) - -> np.ndarray: + def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) + -> np.ndarray: # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes @@ -440,11 +441,11 @@ def _combine_watershed_z_positions(self, # starting far from coverslip for z in range(len(self.dataSet.get_z_positions())-1, 0, -1): zNucleiIndex = np.unique(watershedOutput[:, :, z])[ - np.unique(watershedOutput[:, :, z])>100] + np.unique(watershedOutput[:, :, z]) > 100] for n0 in zNucleiIndex: # for each nuclei N(Z) in Z n1,f0,f1 = _get_overlapping_nuclei(watershedCombinedZ[:, :, z], - watershedOutput[:, :, z-1],n0) + watershedOutput[:, :, z-1], n0) if n1: watershedCombinedZ[:, :, z-1][watershedOutput[:, :, z-1] == n1] = n0 From 279c1c7b568b23bd50723db6cb3b15be65263b8d Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 13:38:23 -0500 Subject: [PATCH 258/419] fixing invalid syntax --- merlin/analysis/segment.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 3eafec1b..ed9a2464 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -275,10 +275,14 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseBlockSize = 241 fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], coarseBlockSize, offset=0) - fineThresholdingMask = imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) + coarseThresholdingMask = (imageStack[:, :, z] > + threshold_local(imageStack[:, :, z], + coarseBlockSize, + offset=0)) + fineThresholdingMask = (imageStack[:, :, z] > + threshold_local(imageStack[:, :, z], + fineBlockSize, + offset=0)) thresholdingMask[:, :, z] = coarseThresholdingMask * fineThresholdingMask thresholdingMask[:, :, z] = binary_fill_holes( @@ -407,8 +411,8 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * (watershedZ1 == n1)) - n0OverlapFraction = np.asarray(overlapArea/n0Area) - n1OverlapFraction = np.asarray(overlapArea/n1Area) + n0OverlapFraction = np.asarray(overlapArea / n0Area) + n1OverlapFraction = np.asarray(overlapArea / n1Area) index = list(range(len(n0OverlapFraction))) # select the nuclei that has the highest fraction in n0 and n1 From e84188fa0d3b0a7a59e07ff1b2066cc1f9576165 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 13:48:19 -0500 Subject: [PATCH 259/419] fixing invalid syntax --- merlin/analysis/segment.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index ed9a2464..194eea48 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -283,8 +283,8 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: threshold_local(imageStack[:, :, z], fineBlockSize, offset=0)) - thresholdingMask[:, :, z] = coarseThresholdingMask * - fineThresholdingMask + thresholdingMask[:, :, z] = (coarseThresholdingMask * + fineThresholdingMask) thresholdingMask[:, :, z] = binary_fill_holes( thresholdingMask[:, :, z]) @@ -302,7 +302,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: fineHessianMask[:, :, z] = fineHessian == fineHessian.max() fineHessianMask[:, :, z] = binary_closing(fineHessianMask[:, :, z], selem.disk(5)) - fineHessianMask[:, :, z] = fineHessianMask[:, :, z]*borderMask + fineHessianMask[:, :, z] = fineHessianMask[:, :, z] * borderMask fineHessianMask[:, :, z] = binary_fill_holes( fineHessianMask[:, :, z]) @@ -315,7 +315,8 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() coarseHessianMask[:, :, z] = binary_closing( coarseHessianMask[:, :, z], selem.disk(5)) - coarseHessianMask[:, :, z] = coarseHessianMask[:, :, z]*borderMask + coarseHessianMask[:, :, z] = (coarseHessianMask[:, :, z] * + borderMask) coarseHessianMask[:, :, z] = binary_fill_holes( coarseHessianMask[:, :, z]) @@ -334,19 +335,19 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, background = sm.dilation(nucleiMask[:, :, z], sm.selem.disk(15)) membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), sm.selem.disk(10)) - foreground = sm.erosion(nucleiMask[:, :, z]*~membraneDilated, + foreground = sm.erosion(nucleiMask[:, :, z] *~ membraneDilated, sm.selem.disk(5)) - unknown = background*~foreground + unknown = background *~ foreground - background = np.uint8(background)*255 - foreground = np.uint8(foreground)*255 - unknown = np.uint8(unknown)*255 + background = np.uint8(background) * 255 + foreground = np.uint8(foreground) * 255 + unknown = np.uint8(unknown) * 255 # Marker labelling ret, markers = cv2.connectedComponents(foreground) # Add one to all labels so that sure background is not 0, but 1 - markers = markers+100 + markers = markers + 100 # Now, mark the region of unknown with zero markers[unknown == 255] = 0 From a1c322cdec00ef8be19d5c28bcbea0033ba28098 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 13:52:09 -0500 Subject: [PATCH 260/419] fixing invalid syntax --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 194eea48..bc497061 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -422,8 +422,8 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, index), reverse=True)) - if n0OverlapFraction[indexSorted[0]] > 0.2 and - n1OverlapFraction[indexSorted[0]] > 0.5: + if (n0OverlapFraction[indexSorted[0]] > 0.2 and + n1OverlapFraction[indexSorted[0]] > 0.5): return m1NucleiIndexes[indexSorted[0]], n0OverlapFraction[indexSorted[0]], n1OverlapFraction[indexSorted[0]] From 0961624501653c58b511eff94f62980666062b57 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 13:56:13 -0500 Subject: [PATCH 261/419] fixing invalid syntax --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index bc497061..c3c32c90 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -432,8 +432,8 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, else: return False, False, False - def _combine_watershed_z_positions(self, watershedOutput: np.ndarray) - -> np.ndarray: + def _combine_watershed_z_positions(self, + watershedOutput: np.ndarray) -> np.ndarray: # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes From 274439615f97b275a083e91cc81fc2392092b6ab Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 14:00:18 -0500 Subject: [PATCH 262/419] fixing invalid syntax --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index c3c32c90..fdc242eb 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -452,8 +452,8 @@ def _combine_watershed_z_positions(self, n1,f0,f1 = _get_overlapping_nuclei(watershedCombinedZ[:, :, z], watershedOutput[:, :, z-1], n0) if n1: - watershedCombinedZ[:, :, z-1][watershedOutput[:, :, z-1] == n1] - = n0 + watershedCombinedZ[:, :, z-1][(watershedOutput[:, :, z-1] == + n1)] = n0 return watershedCombinedZ class CleanCellBoundaries(analysistask.ParallelAnalysisTask): From 40b228a553cf5bc0fd5089ba0e5eb00f405410cb Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 14:10:20 -0500 Subject: [PATCH 263/419] pep8 compliance --- merlin/analysis/segment.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index fdc242eb..4b7feae2 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -281,7 +281,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: offset=0)) fineThresholdingMask = (imageStack[:, :, z] > threshold_local(imageStack[:, :, z], - fineBlockSize, + fineBlockSize, offset=0)) thresholdingMask[:, :, z] = (coarseThresholdingMask * fineThresholdingMask) @@ -315,7 +315,7 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() coarseHessianMask[:, :, z] = binary_closing( coarseHessianMask[:, :, z], selem.disk(5)) - coarseHessianMask[:, :, z] = (coarseHessianMask[:, :, z] * + coarseHessianMask[:, :, z] = (coarseHessianMask[:, :, z] * borderMask) coarseHessianMask[:, :, z] = binary_fill_holes( coarseHessianMask[:, :, z]) @@ -335,9 +335,9 @@ def _get_watershed_markers(self, nucleiMask: np.ndarray, background = sm.dilation(nucleiMask[:, :, z], sm.selem.disk(15)) membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), sm.selem.disk(10)) - foreground = sm.erosion(nucleiMask[:, :, z] *~ membraneDilated, + foreground = sm.erosion(nucleiMask[:, :, z] * ~ membraneDilated, sm.selem.disk(5)) - unknown = background *~ foreground + unknown = background * ~ foreground background = np.uint8(background) * 255 foreground = np.uint8(foreground) * 255 @@ -433,7 +433,8 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, return False, False, False def _combine_watershed_z_positions(self, - watershedOutput: np.ndarray) -> np.ndarray: + watershedOutput: np.ndarray) -> np.ndarray: + # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes @@ -448,11 +449,12 @@ def _combine_watershed_z_positions(self, zNucleiIndex = np.unique(watershedOutput[:, :, z])[ np.unique(watershedOutput[:, :, z]) > 100] - for n0 in zNucleiIndex: # for each nuclei N(Z) in Z - n1,f0,f1 = _get_overlapping_nuclei(watershedCombinedZ[:, :, z], - watershedOutput[:, :, z-1], n0) + for n0 in zNucleiIndex: + n1, f0, f1 = _get_overlapping_nuclei(watershedCombinedZ[:, :, z], + watershedOutput[:, :, z-1], + n0) if n1: - watershedCombinedZ[:, :, z-1][(watershedOutput[:, :, z-1] == + watershedCombinedZ[:, :, z-1][(watershedOutput[:, :, z-1] == n1)] = n0 return watershedCombinedZ From c1156d60d4478367229e9c1af3bb9fc751613f89 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 14:12:19 -0500 Subject: [PATCH 264/419] pep8 compliance --- merlin/analysis/segment.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 4b7feae2..90c9aaa4 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -433,7 +433,8 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, return False, False, False def _combine_watershed_z_positions(self, - watershedOutput: np.ndarray) -> np.ndarray: + watershedOutput: + np.ndarray) ->np.ndarray: # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes From 9cd10007123e7f1fd4635cbe634cb4c72e17be3f Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 14:14:32 -0500 Subject: [PATCH 265/419] pep8 compliance --- merlin/analysis/segment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 90c9aaa4..150834b6 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -434,7 +434,7 @@ def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, def _combine_watershed_z_positions(self, watershedOutput: - np.ndarray) ->np.ndarray: + np.ndarray) -> np.ndarray: # TO DO: this implementation is very rough, needs to be improved. # good just for testing purposes From 5b963de6f7d866151569715a603c96a640f8d6f2 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 14:49:52 -0500 Subject: [PATCH 266/419] added _read_and_filter_image_stack to WatershedSegmentNucleiCV2 --- merlin/analysis/segment.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 150834b6..0a8cd45c 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -220,6 +220,16 @@ def _run_analysis(self, fragmentIndex): featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) + def _read_and_filter_image_stack(self, fov: int, channelIndex: int, + filterSigma: float) -> np.ndarray: + filterSize = int(2*np.ceil(2*filterSigma)+1) + warpTask = self.dataSet.load_analysis_task( + self.parameters['warp_task']) + return np.array([cv2.GaussianBlur( + warpTask.get_aligned_image(fov, channelIndex, z), + (filterSize, filterSize), filterSigma) + for z in range(len(self.dataSet.get_z_positions()))]) + def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) From 0f35412e7572ad67e4b628b88cefc1d4e75e57b8 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 16:38:22 -0500 Subject: [PATCH 267/419] reorganized Watershed...CV2 to remove repeated method calls --- merlin/analysis/segment.py | 156 +++++++++++++------------------------ 1 file changed, 53 insertions(+), 103 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 0a8cd45c..fa4e47a1 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -146,10 +146,10 @@ class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): def __init__(self, dataSet, parameters=None, analysisName=None): super().__init__(dataSet, parameters, analysisName) - if 'seed_channel_name' not in self.parameters: - self.parameters['seed_channel_name'] = 'WGA' - if 'watershed_channel_name' not in self.parameters: - self.parameters['watershed_channel_name'] = 'DAPI' + if 'membrane_channel_name' not in self.parameters: + self.parameters['membrane_channel_name'] = 'ConA' + if 'nuclei_channel_name' not in self.parameters: + self.parameters['nucleichannel_name'] = 'DAPI' def fragment_count(self): return len(self.dataSet.get_fovs()) @@ -174,123 +174,75 @@ def _run_analysis(self, fragmentIndex): globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) - seedIndex = self.dataSet.get_data_organization().get_data_channel_index( - self.parameters['seed_channel_name']) - seedImages = self._read_and_filter_image_stack(fragmentIndex, - seedIndex, 5) - - watershedIndex = self.dataSet.get_data_organization() \ - .get_data_channel_index(self.parameters['watershed_channel_name']) + # read membrane (seed) and nuclei (watershed) indexes + membraneIndex = self.dataSet.get_data_organization(). + get_data_channel_index(self.parameters['membrane_channel_name']) + nucleiIndex = self.dataSet.get_data_organization(). + get_data_channel_index(self.parameters['nuclei_channel_name']) + + # read membrane (seed) and nuclei (watershed) images + membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) + nucleiImages = self._read_image_stack(fragmentIndex, nucleiIndex) # Prepare masks for cv2 watershed - membraneMask = self._get_membrane_mask(fragmentIndex, watershedIndex) - nucleiMask = self._get_nuclei_mask(fragmentIndex, watershedIndex) - watershedMarkers = self._get_watershed_markers(nucleiMask, - membraneMask) + watershedMarkers = self._get_watershed_markers(nucleiImages, + membraneImages) # perform watershed in individual z positions - watershedOutput = self._apply_watershed(fragmentIndex, watershedIndex, + watershedOutput = self._apply_watershed(nucleiImages, watershedMarkers) # combine all z positions in watershed watershedCombinedOutput = self._combine_watershed_z_positions( watershedOutput) - """ - watershedImages = self._read_and_filter_image_stack(fragmentIndex, - watershedIndex, 5) - seeds = watershed.separate_merged_seeds( - watershed.extract_seeds(seedImages)) - normalizedWatershed, watershedMask = - watershed.prepare_watershed_images( - watershedImages) - - seeds[np.invert(watershedMask)] = 0 - watershedOutput = segmentation.watershed( - normalizedWatershed, measure.label(seeds), mask=watershedMask, - connectivity=np.ones((3, 3, 3)), watershed_line=True) - """ - zPos = np.array(self.dataSet.get_data_organization().get_z_positions()) featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( - (watershedOutput == i), fragmentIndex, + (watershedCombinedOutput == i), fragmentIndex, globalTask.fov_to_global_transform(fragmentIndex), zPos) for i in np.unique(watershedOutput) if i != 0] featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) - def _read_and_filter_image_stack(self, fov: int, channelIndex: int, - filterSigma: float) -> np.ndarray: - filterSize = int(2*np.ceil(2*filterSigma)+1) + def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) - return np.array([cv2.GaussianBlur( - warpTask.get_aligned_image(fov, channelIndex, z), - (filterSize, filterSize), filterSigma) - for z in range(len(self.dataSet.get_z_positions()))]) - - def _get_membrane_mask(self, fov: int, channelIndex: int) -> np.ndarray: - warpTask = self.dataSet.load_analysis_task( - self.parameters['warp_task']) - - imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet. - get_z_positions()))]) - # generate mask based on edge detection - """ - edgeMask = np.zeros(imageStack.shape) - for z in range(len(self.dataSet.get_z_positions())): - edgeMask[:, :, z] = canny( - white_tophat(imageStack[:, :, z], selem.disk(10)), - sigma=2, use_quantiles=True, - low_threshold=0.5, high_threshold=0.8) - edgeMask[:, :, z] = binary_closing(edgeMask[:, :, z],selem.disk(5)) - edgeMask[:, :, z] = remove_small_objects( - edgeMask[:, :, z].astype('bool'), - min_size=100, connectivity=1) - edgeMask[:, :, z] = skeletonize(edgeMask[:, :, z]) - """ + return np.array([warpTask.get_aligned_image(fov, channelIndex, z) + for z in range(len(self.dataSet.get_z_positions()))]) + def _get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: # generate mask based on thresholding - tresholdingMask = np.zeros(imageStack.shape) + mask = np.zeros(membraneImages.shape) fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - tresholdingMask[:, :, z] = (imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], - fineBlockSize, - offset=0)) - tresholdingMask[:, :, z] = remove_small_objects( - imageStack[:, :, z].astype('bool'), min_size=100, - connectivity=1) - tresholdingMask[:, :, z] = binary_closing(imageStack[:, :, z], + mask[:, :, z] = (membraneImages[:, :, z] > + threshold_local(membraneImages[:, :, z], + fineBlockSize, + offset=0)) + mask[:, :, z] = remove_small_objects(membraneImages[:, :, z]. + astype('bool'), + min_size=100, + connectivity=1) + mask[:, :, z] = binary_closing(membraneImages[:, :, z], selem.disk(5)) - tresholdingMask[:, :, z] = skeletonize(imageStack[:, :, z]) + mask[:, :, z] = skeletonize(membraneImages[:, :, z]) # combine masks - # return edgeMask + thresholdingMask - return thresholdingMask - - def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: - - warpTask = self.dataSet.load_analysis_task( - self.parameters['warp_task']) - - imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet. - get_z_positions()))]) + return mask + def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: # generate nuclei mask based on thresholding - thresholdingMask = np.zeros(imageStack.shape) + thresholdingMask = np.zeros(nucleiImages.shape) coarseBlockSize = 241 fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = (imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], + coarseThresholdingMask = (nucleiImages[:, :, z] > + threshold_local(nucleiImages[:, :, z], coarseBlockSize, offset=0)) - fineThresholdingMask = (imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], + fineThresholdingMask = (nucleiImages[:, :, z] > + threshold_local(nucleiImages[:, :, z], fineBlockSize, offset=0)) thresholdingMask[:, :, z] = (coarseThresholdingMask * @@ -306,21 +258,21 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: # TODO - use the image size variable for borderMask # generate nuclei mask from hessian, fine - fineHessianMask = np.zeros(imageStack.shape) + fineHessianMask = np.zeros(nucleiImages.shape) for z in range(len(self.dataSet.get_z_positions())): - fineHessian = hessian(imageStack[:, :, z]) + fineHessian = hessian(nucleiImages[:, :, z]) fineHessianMask[:, :, z] = fineHessian == fineHessian.max() fineHessianMask[:, :, z] = binary_closing(fineHessianMask[:, :, z], selem.disk(5)) fineHessianMask[:, :, z] = fineHessianMask[:, :, z] * borderMask fineHessianMask[:, :, z] = binary_fill_holes( - fineHessianMask[:, :, z]) + fineHessianMask[:, :, z]) # generate dapi mask from hessian, coarse - coarseHessianMask = np.zeros(imageStack.shape) + coarseHessianMask = np.zeros(nucleiImages.shape) for z in range(len(self.dataSet.get_z_positions())): - coarseHessian = hessian(imageStack[:, :, z] - - white_tophat(imageStack[:, :, z], + coarseHessian = hessian(nucleiImages[:, :, z] - + white_tophat(nucleiImages[:, :, z], selem.disk(20))) coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() coarseHessianMask[:, :, z] = binary_closing( @@ -334,8 +286,12 @@ def _get_nuclei_mask(self, fov: int, channelIndex: int) -> np.ndarray: nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask return binary_fill_holes(nucleiMask) - def _get_watershed_markers(self, nucleiMask: np.ndarray, - membraneMask: np.ndarray) -> np.ndarray: + def _get_watershed_markers(self, nucleiImages: np.ndarray, + membraneImages: np.ndarray) -> np.ndarray: + + nucleiMask = self._get_nuclei_mask(nucleiImages) + membraneMask = self._get_membrane_mask(membraneImages) + watershedMarker = np.zeros(nucleiMask.shape) for z in range(len(self.dataSet.get_z_positions())): @@ -387,18 +343,12 @@ def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: return rgbImage - def _apply_watershed(self, fov: int, channelIndex: int, + def _apply_watershed(self, nucleiImages: np.ndarray, watershedMarkers: np.ndarray) -> np.ndarray: - warpTask = self.dataSet.load_analysis_task( - self.parameters['warp_task']) - - imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet. - get_z_positions()))]) watershedOutput = np.zeros(watershedMarkers.shape) for z in range(len(self.dataSet.get_z_positions())): - rgbImage = _convert_grayscale_to_rgb(dapiStack[:, :, z]) + rgbImage = _convert_grayscale_to_rgb(nucleiImages[:, :, z]) watershedOutput[:, :, z] = cv2.watershed(rgbImage, watershedMarkers[:, :, z]. astype('int32')) From 332bbe2200b994e97692acf7f1d3eadf4df4112d Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 16:44:01 -0500 Subject: [PATCH 268/419] cleaned watershed --- merlin/analysis/segment.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index fa4e47a1..cf59a553 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -352,6 +352,8 @@ def _apply_watershed(self, nucleiImages: np.ndarray, watershedOutput[:, :, z] = cv2.watershed(rgbImage, watershedMarkers[:, :, z]. astype('int32')) + watershedOutput[:, :, z][watershedOutput[:, :, z] <= 100] = 0 + return watershedOutput def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, From f971acb36cf2d4a02233e8d05e3775f1b6c635ce Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 16:47:37 -0500 Subject: [PATCH 269/419] pep8 compliance --- merlin/analysis/segment.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index cf59a553..c2eef180 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -176,9 +176,11 @@ def _run_analysis(self, fragmentIndex): # read membrane (seed) and nuclei (watershed) indexes membraneIndex = self.dataSet.get_data_organization(). - get_data_channel_index(self.parameters['membrane_channel_name']) + get_data_channel_index( + self.parameters['m brane_channel_name']) nucleiIndex = self.dataSet.get_data_organization(). - get_data_channel_index(self.parameters['nuclei_channel_name']) + get_data_channel_index( + self.parameters['nuclei_channel_name']) # read membrane (seed) and nuclei (watershed) images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) From 9d4bfc1874e88777a8d6177001a9969de36dfa56 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 16:49:19 -0500 Subject: [PATCH 270/419] pep8 compliance --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index c2eef180..ff545a84 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -176,8 +176,8 @@ def _run_analysis(self, fragmentIndex): # read membrane (seed) and nuclei (watershed) indexes membraneIndex = self.dataSet.get_data_organization(). - get_data_channel_index( - self.parameters['m brane_channel_name']) + get_data_channel_index( + self.parameters['membrane_channel_name']) nucleiIndex = self.dataSet.get_data_organization(). get_data_channel_index( self.parameters['nuclei_channel_name']) From 66c4d267cea4316ca6c7470d5673e9111fad80aa Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 16:54:08 -0500 Subject: [PATCH 271/419] pep8 compliance --- merlin/analysis/segment.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index ff545a84..5505b8f8 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -175,12 +175,12 @@ def _run_analysis(self, fragmentIndex): self.parameters['global_align_task']) # read membrane (seed) and nuclei (watershed) indexes - membraneIndex = self.dataSet.get_data_organization(). - get_data_channel_index( - self.parameters['membrane_channel_name']) + membraneIndex = self.dataSet \ + .get_data_organization() \ + .get_data_channel_index( + self.parameters['membrane_channel_name']) nucleiIndex = self.dataSet.get_data_organization(). - get_data_channel_index( - self.parameters['nuclei_channel_name']) + get_data_channel_index(self.parameters['nuclei_channel_name']) # read membrane (seed) and nuclei (watershed) images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) From d3e3e6009f4ad41f158bbdfc4760a1117e3e8aa0 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 16:55:47 -0500 Subject: [PATCH 272/419] pep8 compliance --- merlin/analysis/segment.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 5505b8f8..6d74df95 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -175,13 +175,11 @@ def _run_analysis(self, fragmentIndex): self.parameters['global_align_task']) # read membrane (seed) and nuclei (watershed) indexes - membraneIndex = self.dataSet \ - .get_data_organization() \ - .get_data_channel_index( - self.parameters['membrane_channel_name']) + membraneIndex = self.dataSet.get_data_organization(). + get_data_channel_index(self.parameters['membrane_channel_name']) nucleiIndex = self.dataSet.get_data_organization(). get_data_channel_index(self.parameters['nuclei_channel_name']) - + # read membrane (seed) and nuclei (watershed) images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) nucleiImages = self._read_image_stack(fragmentIndex, nucleiIndex) From c9c6973cb3f2e15999ad6a29b415f78638bcb6b0 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 16:58:17 -0500 Subject: [PATCH 273/419] pep8 compliance --- merlin/analysis/segment.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 6d74df95..40bbf3ed 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -180,7 +180,7 @@ def _run_analysis(self, fragmentIndex): nucleiIndex = self.dataSet.get_data_organization(). get_data_channel_index(self.parameters['nuclei_channel_name']) - # read membrane (seed) and nuclei (watershed) images + # read membrane (seed) and nuclei (watershed) images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) nucleiImages = self._read_image_stack(fragmentIndex, nucleiIndex) @@ -221,11 +221,11 @@ def _get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: fineBlockSize, offset=0)) mask[:, :, z] = remove_small_objects(membraneImages[:, :, z]. - astype('bool'), - min_size=100, - connectivity=1) + astype('bool'), + min_size=100, + connectivity=1) mask[:, :, z] = binary_closing(membraneImages[:, :, z], - selem.disk(5)) + selem.disk(5)) mask[:, :, z] = skeletonize(membraneImages[:, :, z]) # combine masks @@ -288,10 +288,10 @@ def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: def _get_watershed_markers(self, nucleiImages: np.ndarray, membraneImages: np.ndarray) -> np.ndarray: - + nucleiMask = self._get_nuclei_mask(nucleiImages) membraneMask = self._get_membrane_mask(membraneImages) - + watershedMarker = np.zeros(nucleiMask.shape) for z in range(len(self.dataSet.get_z_positions())): @@ -352,8 +352,8 @@ def _apply_watershed(self, nucleiImages: np.ndarray, watershedOutput[:, :, z] = cv2.watershed(rgbImage, watershedMarkers[:, :, z]. astype('int32')) - watershedOutput[:, :, z][watershedOutput[:, :, z] <= 100] = 0 - + watershedOutput[:, :, z][watershedOutput[:, :, z] <= 100] = 0 + return watershedOutput def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, From 36a5982f51b41e3094f66de568f88a2c8349fe81 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 17:05:54 -0500 Subject: [PATCH 274/419] fixing invalid syntax --- merlin/analysis/segment.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 40bbf3ed..3aa46d72 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -175,10 +175,14 @@ def _run_analysis(self, fragmentIndex): self.parameters['global_align_task']) # read membrane (seed) and nuclei (watershed) indexes - membraneIndex = self.dataSet.get_data_organization(). - get_data_channel_index(self.parameters['membrane_channel_name']) - nucleiIndex = self.dataSet.get_data_organization(). - get_data_channel_index(self.parameters['nuclei_channel_name']) + membraneIndex = self.dataSet \ + .get_data_organization() \ + .get_data_channel_index( + self.parameters['membrane_channel_name']) + nucleiIndex = self.dataSet \ + .get_data_organization() \ + .get_data_channel_index( + self.parameters['nuclei_channel_name']) # read membrane (seed) and nuclei (watershed) images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) From c44a71a8c45fe50b987aa2983c8005728132eb2f Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 17:53:10 -0500 Subject: [PATCH 275/419] correct skimage function names --- merlin/analysis/segment.py | 59 +++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 3aa46d72..4a7f5b36 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -221,16 +221,16 @@ def _get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): mask[:, :, z] = (membraneImages[:, :, z] > - threshold_local(membraneImages[:, :, z], + filters.threshold_local(membraneImages[:, :, z], fineBlockSize, offset=0)) - mask[:, :, z] = remove_small_objects(membraneImages[:, :, z]. - astype('bool'), - min_size=100, - connectivity=1) - mask[:, :, z] = binary_closing(membraneImages[:, :, z], - selem.disk(5)) - mask[:, :, z] = skeletonize(membraneImages[:, :, z]) + mask[:, :, z] = morphology.remove_small_objects( + membraneImages[:, :, z].astype('bool'), + min_size=100, + connectivity=1) + mask[:, :, z] = morphology.binary_closing(membraneImages[:, :, z], + morphology.selem.disk(5)) + mask[:, :, z] = morphology.skeletonize(membraneImages[:, :, z]) # combine masks return mask @@ -242,13 +242,15 @@ def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): coarseThresholdingMask = (nucleiImages[:, :, z] > - threshold_local(nucleiImages[:, :, z], - coarseBlockSize, - offset=0)) + filters.threshold_local( + nucleiImages[:, :, z], + coarseBlockSize, + offset=0)) fineThresholdingMask = (nucleiImages[:, :, z] > - threshold_local(nucleiImages[:, :, z], - fineBlockSize, - offset=0)) + filters.threshold_local( + nucleiImages[:, :, z], + fineBlockSize, + offset=0)) thresholdingMask[:, :, z] = (coarseThresholdingMask * fineThresholdingMask) thresholdingMask[:, :, z] = binary_fill_holes( @@ -266,8 +268,9 @@ def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: for z in range(len(self.dataSet.get_z_positions())): fineHessian = hessian(nucleiImages[:, :, z]) fineHessianMask[:, :, z] = fineHessian == fineHessian.max() - fineHessianMask[:, :, z] = binary_closing(fineHessianMask[:, :, z], - selem.disk(5)) + fineHessianMask[:, :, z] = morphology.binary_closing( + fineHessianMask[:, :, z], + morphology.selem.disk(5)) fineHessianMask[:, :, z] = fineHessianMask[:, :, z] * borderMask fineHessianMask[:, :, z] = binary_fill_holes( fineHessianMask[:, :, z]) @@ -275,12 +278,13 @@ def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: # generate dapi mask from hessian, coarse coarseHessianMask = np.zeros(nucleiImages.shape) for z in range(len(self.dataSet.get_z_positions())): - coarseHessian = hessian(nucleiImages[:, :, z] - - white_tophat(nucleiImages[:, :, z], - selem.disk(20))) + coarseHessian = filters.hessian(nucleiImages[:, :, z] - + morphology.white_tophat( + nucleiImages[:, :, z], + morphology.selem.disk(20))) coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() - coarseHessianMask[:, :, z] = binary_closing( - coarseHessianMask[:, :, z], selem.disk(5)) + coarseHessianMask[:, :, z] = morphology.binary_closing( + coarseHessianMask[:, :, z], morphology.selem.disk(5)) coarseHessianMask[:, :, z] = (coarseHessianMask[:, :, z] * borderMask) coarseHessianMask[:, :, z] = binary_fill_holes( @@ -302,11 +306,14 @@ def _get_watershed_markers(self, nucleiImages: np.ndarray, # generate areas of sure bg and fg, as well as the area of # unknown classification - background = sm.dilation(nucleiMask[:, :, z], sm.selem.disk(15)) - membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), - sm.selem.disk(10)) - foreground = sm.erosion(nucleiMask[:, :, z] * ~ membraneDilated, - sm.selem.disk(5)) + background = morphology.dilation(nucleiMask[:, :, z], + morphology.selem.disk(15)) + membraneDilated = morphology.dilation( + membraneMask[:, :, z].astype('bool'), + morphology.selem.disk(10)) + foreground = morphology.erosion(nucleiMask[:, :, z] * ~ + membraneDilated, + morphology.selem.disk(5)) unknown = background * ~ foreground background = np.uint8(background) * 255 From f5166b543bceb737c50801db3bab880cd480bba3 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 17:55:00 -0500 Subject: [PATCH 276/419] pep8 compliance --- merlin/analysis/segment.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 4a7f5b36..80e732b4 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -222,14 +222,14 @@ def _get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: for z in range(len(self.dataSet.get_z_positions())): mask[:, :, z] = (membraneImages[:, :, z] > filters.threshold_local(membraneImages[:, :, z], - fineBlockSize, - offset=0)) + fineBlockSize, + offset=0)) mask[:, :, z] = morphology.remove_small_objects( membraneImages[:, :, z].astype('bool'), min_size=100, connectivity=1) mask[:, :, z] = morphology.binary_closing(membraneImages[:, :, z], - morphology.selem.disk(5)) + morphology.selem.disk(5)) mask[:, :, z] = morphology.skeletonize(membraneImages[:, :, z]) # combine masks @@ -279,7 +279,7 @@ def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: coarseHessianMask = np.zeros(nucleiImages.shape) for z in range(len(self.dataSet.get_z_positions())): coarseHessian = filters.hessian(nucleiImages[:, :, z] - - morphology.white_tophat( + morphology.white_tophat( nucleiImages[:, :, z], morphology.selem.disk(20))) coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() From 6e07a30a51b5236233733328ad1d78e36ead2143 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 18:06:02 -0500 Subject: [PATCH 277/419] correct skimage function names --- merlin/analysis/segment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 80e732b4..5ca586c1 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -266,7 +266,7 @@ def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: # generate nuclei mask from hessian, fine fineHessianMask = np.zeros(nucleiImages.shape) for z in range(len(self.dataSet.get_z_positions())): - fineHessian = hessian(nucleiImages[:, :, z]) + fineHessian = filters.hessian(nucleiImages[:, :, z]) fineHessianMask[:, :, z] = fineHessian == fineHessian.max() fineHessianMask[:, :, z] = morphology.binary_closing( fineHessianMask[:, :, z], From 8b0ed8a45e9cc938068662d98bf74cd49bde90e9 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sun, 26 Jan 2020 09:04:09 -0500 Subject: [PATCH 278/419] changed Image dimension order to fit MERlin's --- merlin/analysis/segment.py | 89 ++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 43 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 5ca586c1..b88ab8e4 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -149,7 +149,7 @@ def __init__(self, dataSet, parameters=None, analysisName=None): if 'membrane_channel_name' not in self.parameters: self.parameters['membrane_channel_name'] = 'ConA' if 'nuclei_channel_name' not in self.parameters: - self.parameters['nucleichannel_name'] = 'DAPI' + self.parameters['nuclei_channel_name'] = 'DAPI' def fragment_count(self): return len(self.dataSet.get_fovs()) @@ -188,6 +188,9 @@ def _run_analysis(self, fragmentIndex): membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) nucleiImages = self._read_image_stack(fragmentIndex, nucleiIndex) + print('membraneImages = ' + str(membraneImages.shape)) + print('nucleiImages = ' + str(nucleiImages.shape)) + # Prepare masks for cv2 watershed watershedMarkers = self._get_watershed_markers(nucleiImages, membraneImages) @@ -220,17 +223,17 @@ def _get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: mask = np.zeros(membraneImages.shape) fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - mask[:, :, z] = (membraneImages[:, :, z] > - filters.threshold_local(membraneImages[:, :, z], + mask[z, :, :] = (membraneImages[z, :, :] > + filters.threshold_local(membraneImages[z, :, :], fineBlockSize, offset=0)) - mask[:, :, z] = morphology.remove_small_objects( - membraneImages[:, :, z].astype('bool'), + mask[z, :, :] = morphology.remove_small_objects( + membraneImages[z, :, :].astype('bool'), min_size=100, connectivity=1) - mask[:, :, z] = morphology.binary_closing(membraneImages[:, :, z], + mask[z, :, :] = morphology.binary_closing(membraneImages[z, :, :], morphology.selem.disk(5)) - mask[:, :, z] = morphology.skeletonize(membraneImages[:, :, z]) + mask[z, :, :] = morphology.skeletonize(membraneImages[z, :, :]) # combine masks return mask @@ -241,20 +244,20 @@ def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: coarseBlockSize = 241 fineBlockSize = 61 for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = (nucleiImages[:, :, z] > + coarseThresholdingMask = (nucleiImages[z, :, :] > filters.threshold_local( - nucleiImages[:, :, z], + nucleiImages[z, :, :], coarseBlockSize, offset=0)) - fineThresholdingMask = (nucleiImages[:, :, z] > + fineThresholdingMask = (nucleiImages[z, :, :] > filters.threshold_local( - nucleiImages[:, :, z], + nucleiImages[z, :, :], fineBlockSize, offset=0)) - thresholdingMask[:, :, z] = (coarseThresholdingMask * + thresholdingMask[z, :, :] = (coarseThresholdingMask * fineThresholdingMask) - thresholdingMask[:, :, z] = binary_fill_holes( - thresholdingMask[:, :, z]) + thresholdingMask[z, :, :] = binary_fill_holes( + thresholdingMask[z, :, :]) # generate border mask, necessary to avoid making a single # connected component when using binary_fill_holes below @@ -266,29 +269,29 @@ def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: # generate nuclei mask from hessian, fine fineHessianMask = np.zeros(nucleiImages.shape) for z in range(len(self.dataSet.get_z_positions())): - fineHessian = filters.hessian(nucleiImages[:, :, z]) - fineHessianMask[:, :, z] = fineHessian == fineHessian.max() - fineHessianMask[:, :, z] = morphology.binary_closing( - fineHessianMask[:, :, z], + fineHessian = filters.hessian(nucleiImages[z, :, :]) + fineHessianMask[z, :, :] = fineHessian == fineHessian.max() + fineHessianMask[z, :, :] = morphology.binary_closing( + fineHessianMask[z, :, :], morphology.selem.disk(5)) - fineHessianMask[:, :, z] = fineHessianMask[:, :, z] * borderMask - fineHessianMask[:, :, z] = binary_fill_holes( - fineHessianMask[:, :, z]) + fineHessianMask[z, :, :] = fineHessianMask[z, :, :] * borderMask + fineHessianMask[z, :, :] = binary_fill_holes( + fineHessianMask[z, :, :]) # generate dapi mask from hessian, coarse coarseHessianMask = np.zeros(nucleiImages.shape) for z in range(len(self.dataSet.get_z_positions())): - coarseHessian = filters.hessian(nucleiImages[:, :, z] - + coarseHessian = filters.hessian(nucleiImages[z, :, :] - morphology.white_tophat( - nucleiImages[:, :, z], + nucleiImages[z, :, :], morphology.selem.disk(20))) - coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() - coarseHessianMask[:, :, z] = morphology.binary_closing( - coarseHessianMask[:, :, z], morphology.selem.disk(5)) - coarseHessianMask[:, :, z] = (coarseHessianMask[:, :, z] * + coarseHessianMask[z, :, :] = coarseHessian == coarseHessian.max() + coarseHessianMask[z, :, :] = morphology.binary_closing( + coarseHessianMask[z, :, :], morphology.selem.disk(5)) + coarseHessianMask[z, :, :] = (coarseHessianMask[z, :, :] * borderMask) - coarseHessianMask[:, :, z] = binary_fill_holes( - coarseHessianMask[:, :, z]) + coarseHessianMask[z, :, :] = binary_fill_holes( + coarseHessianMask[z, :, :]) # combine masks nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask @@ -306,12 +309,12 @@ def _get_watershed_markers(self, nucleiImages: np.ndarray, # generate areas of sure bg and fg, as well as the area of # unknown classification - background = morphology.dilation(nucleiMask[:, :, z], + background = morphology.dilation(nucleiMask[z, :, :], morphology.selem.disk(15)) membraneDilated = morphology.dilation( - membraneMask[:, :, z].astype('bool'), + membraneMask[z, :, :].astype('bool'), morphology.selem.disk(10)) - foreground = morphology.erosion(nucleiMask[:, :, z] * ~ + foreground = morphology.erosion(nucleiMask[z, :, :] * ~ membraneDilated, morphology.selem.disk(5)) unknown = background * ~ foreground @@ -329,7 +332,7 @@ def _get_watershed_markers(self, nucleiImages: np.ndarray, # Now, mark the region of unknown with zero markers[unknown == 255] = 0 - watershedMarker[:, :, z] = markers + watershedMarker[z, :, :] = markers return watershedMarker @@ -359,11 +362,11 @@ def _apply_watershed(self, nucleiImages: np.ndarray, watershedOutput = np.zeros(watershedMarkers.shape) for z in range(len(self.dataSet.get_z_positions())): - rgbImage = _convert_grayscale_to_rgb(nucleiImages[:, :, z]) - watershedOutput[:, :, z] = cv2.watershed(rgbImage, - watershedMarkers[:, :, z]. + rgbImage = _convert_grayscale_to_rgb(nucleiImages[z, :, :]) + watershedOutput[z, :, :] = cv2.watershed(rgbImage, + watershedMarkers[z, :, :]. astype('int32')) - watershedOutput[:, :, z][watershedOutput[:, :, z] <= 100] = 0 + watershedOutput[z, :, :][watershedOutput[z, :, :] <= 100] = 0 return watershedOutput @@ -416,19 +419,19 @@ def _combine_watershed_z_positions(self, watershedCombinedZ = np.zeros(watershedOutput.shape) # copy the mask of the section farthest to the coverslip - watershedCombinedZ[:, :, -1] = watershedOutput[:, :, -1] + watershedCombinedZ[-1, :, :] = watershedOutput[-1, :, :] # starting far from coverslip for z in range(len(self.dataSet.get_z_positions())-1, 0, -1): - zNucleiIndex = np.unique(watershedOutput[:, :, z])[ - np.unique(watershedOutput[:, :, z]) > 100] + zNucleiIndex = np.unique(watershedOutput[z, :, :])[ + np.unique(watershedOutput[z, :, :]) > 100] for n0 in zNucleiIndex: - n1, f0, f1 = _get_overlapping_nuclei(watershedCombinedZ[:, :, z], - watershedOutput[:, :, z-1], + n1, f0, f1 = _get_overlapping_nuclei(watershedCombinedZ[z, :, :], + watershedOutput[z-1, :, :], n0) if n1: - watershedCombinedZ[:, :, z-1][(watershedOutput[:, :, z-1] == + watershedCombinedZ[z-1, :, :][(watershedOutput[z-1, :, :] == n1)] = n0 return watershedCombinedZ From fda36a2f1014795c718042c506f5bea6d2fa847c Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sun, 26 Jan 2020 09:32:42 -0500 Subject: [PATCH 279/419] change variable name --- merlin/analysis/segment.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index b88ab8e4..27e4f64d 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -228,12 +228,12 @@ def _get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: fineBlockSize, offset=0)) mask[z, :, :] = morphology.remove_small_objects( - membraneImages[z, :, :].astype('bool'), + mask[z, :, :].astype('bool'), min_size=100, connectivity=1) - mask[z, :, :] = morphology.binary_closing(membraneImages[z, :, :], + mask[z, :, :] = morphology.binary_closing(mask[z, :, :], morphology.selem.disk(5)) - mask[z, :, :] = morphology.skeletonize(membraneImages[z, :, :]) + mask[z, :, :] = morphology.skeletonize(mask[z, :, :]) # combine masks return mask From 0fd6943c2232c6c63d8c35d25c4cd18ae0c20023 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sun, 26 Jan 2020 09:51:46 -0500 Subject: [PATCH 280/419] added missing self. in method call --- merlin/analysis/segment.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 27e4f64d..aa183d92 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -362,7 +362,7 @@ def _apply_watershed(self, nucleiImages: np.ndarray, watershedOutput = np.zeros(watershedMarkers.shape) for z in range(len(self.dataSet.get_z_positions())): - rgbImage = _convert_grayscale_to_rgb(nucleiImages[z, :, :]) + rgbImage = self._convert_grayscale_to_rgb(nucleiImages[z, :, :]) watershedOutput[z, :, :] = cv2.watershed(rgbImage, watershedMarkers[z, :, :]. astype('int32')) @@ -427,9 +427,10 @@ def _combine_watershed_z_positions(self, np.unique(watershedOutput[z, :, :]) > 100] for n0 in zNucleiIndex: - n1, f0, f1 = _get_overlapping_nuclei(watershedCombinedZ[z, :, :], - watershedOutput[z-1, :, :], - n0) + n1, f0, f1 = self._get_overlapping_nuclei( + watershedCombinedZ[z, :, :], + watershedOutput[z-1, :, :], + n0) if n1: watershedCombinedZ[z-1, :, :][(watershedOutput[z-1, :, :] == n1)] = n0 From 83e247650bcd7a85a860e0a4b5358080677d29d0 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sun, 26 Jan 2020 13:50:42 -0500 Subject: [PATCH 281/419] adding print statements for debugging --- merlin/analysis/segment.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index aa183d92..993a3bba 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -17,6 +17,7 @@ from merlin.util import watershed import pandas import networkx as nx +import time class FeatureSavingAnalysisTask(analysistask.ParallelAnalysisTask): @@ -171,9 +172,14 @@ def get_cell_boundaries(self) -> List[spatialfeature.SpatialFeature]: return featureDB.read_features() def _run_analysis(self, fragmentIndex): + startTime = time.time() + globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) + + print('reading indexes for fov ' + str(fragmentIndex) ) + # read membrane (seed) and nuclei (watershed) indexes membraneIndex = self.dataSet \ .get_data_organization() \ @@ -184,25 +190,42 @@ def _run_analysis(self, fragmentIndex): .get_data_channel_index( self.parameters['nuclei_channel_name']) + endTime = time.time() + print("Indexes read, ET {:.2f} min"\ + .format((endTime - startTime) / 60)) + # read membrane (seed) and nuclei (watershed) images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) nucleiImages = self._read_image_stack(fragmentIndex, nucleiIndex) - print('membraneImages = ' + str(membraneImages.shape)) - print('nucleiImages = ' + str(nucleiImages.shape)) + endTime = time.time() + print("Images read, ET {:.2f} min" \ + .format((endTime - startTime) / 60)) # Prepare masks for cv2 watershed watershedMarkers = self._get_watershed_markers(nucleiImages, membraneImages) + endTime = time.time() + print("Markers calculated, ET {:.2f} min" \ + .format((endTime - startTime) / 60)) + # perform watershed in individual z positions watershedOutput = self._apply_watershed(nucleiImages, watershedMarkers) + endTime = time.time() + print("watershed calculated, ET {:.2f} min" \ + .format((endTime - startTime) / 60)) + # combine all z positions in watershed watershedCombinedOutput = self._combine_watershed_z_positions( watershedOutput) + endTime = time.time() + print("watershed z positions combined, ET {:.2f} min" \ + .format((endTime - startTime) / 60)) + zPos = np.array(self.dataSet.get_data_organization().get_z_positions()) featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( (watershedCombinedOutput == i), fragmentIndex, @@ -212,6 +235,10 @@ def _run_analysis(self, fragmentIndex): featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) + endTime = time.time() + print("features written, ET {:.2f} min" \ + .format((endTime - startTime) / 60)) + def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) From 437fa2f0ab21ff5e396d7dcdb4cca155396daf60 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 15:29:03 -0500 Subject: [PATCH 282/419] moving utility functions from segment.py to watershed.py --- merlin/analysis/segment.py | 2 + merlin/util/watershed.py | 233 +++++++++++++++++++++++++++++++++++++ 2 files changed, 235 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 993a3bba..7425eed6 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -226,6 +226,8 @@ def _run_analysis(self, fragmentIndex): print("watershed z positions combined, ET {:.2f} min" \ .format((endTime - startTime) / 60)) + # get features from mask. This is the slowestart (6 min for the + # previous part, 15+ for the rest, for a 7 frame Image. zPos = np.array(self.dataSet.get_data_organization().get_z_positions()) featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( (watershedCombinedOutput == i), fragmentIndex, diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 809dfc5b..3b5563f3 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -1,6 +1,7 @@ import numpy as np import cv2 from scipy import ndimage +from scipy.ndimage.morphology import binary_fill_holes from skimage import morphology from skimage import filters from skimage import measure @@ -139,3 +140,235 @@ def prepare_watershed_images(watershedImageStack: np.ndarray normalizedWatershed[np.invert(watershedMask)] = 1 return normalizedWatershed, watershedMask + +def get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: + """Calculate binary mask with 1's in membrane pixels and 0 otherwise. + The images expected are some type of membrane label (WGA, ConA, + Lamin, Cadherins) + + Args: + membraneImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) + """ + mask = np.zeros(membraneImages.shape) + fineBlockSize = 61 + for z in range(len(self.dataSet.get_z_positions())): + mask[z, :, :] = (membraneImages[z, :, :] > + filters.threshold_local(membraneImages[z, :, :], + fineBlockSize, + offset=0)) + mask[z, :, :] = morphology.remove_small_objects( + mask[z, :, :].astype('bool'), + min_size=100, + connectivity=1) + mask[z, :, :] = morphology.binary_closing(mask[z, :, :], + morphology.selem.disk(5)) + mask[z, :, :] = morphology.skeletonize(mask[z, :, :]) + + # combine masks + return mask + +def get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: + + # TO DO: Add description + + # generate nuclei mask based on thresholding + thresholdingMask = np.zeros(nucleiImages.shape) + coarseBlockSize = 241 + fineBlockSize = 61 + for z in range(len(self.dataSet.get_z_positions())): + coarseThresholdingMask = (nucleiImages[z, :, :] > + filters.threshold_local( + nucleiImages[z, :, :], + coarseBlockSize, + offset=0)) + fineThresholdingMask = (nucleiImages[z, :, :] > + filters.threshold_local( + nucleiImages[z, :, :], + fineBlockSize, + offset=0)) + thresholdingMask[z, :, :] = (coarseThresholdingMask * + fineThresholdingMask) + thresholdingMask[z, :, :] = binary_fill_holes( + thresholdingMask[z, :, :]) + + # generate border mask, necessary to avoid making a single + # connected component when using binary_fill_holes below + borderMask = np.zeros((2048, 2048)) + borderMask[25:2023, 25:2023] = 1 + + # TODO - use the image size variable for borderMask + + # generate nuclei mask from hessian, fine + fineHessianMask = np.zeros(nucleiImages.shape) + for z in range(len(self.dataSet.get_z_positions())): + fineHessian = filters.hessian(nucleiImages[z, :, :]) + fineHessianMask[z, :, :] = fineHessian == fineHessian.max() + fineHessianMask[z, :, :] = morphology.binary_closing( + fineHessianMask[z, :, :], + morphology.selem.disk(5)) + fineHessianMask[z, :, :] = fineHessianMask[z, :, :] * borderMask + fineHessianMask[z, :, :] = binary_fill_holes( + fineHessianMask[z, :, :]) + + # generate dapi mask from hessian, coarse + coarseHessianMask = np.zeros(nucleiImages.shape) + for z in range(len(self.dataSet.get_z_positions())): + coarseHessian = filters.hessian(nucleiImages[z, :, :] - + morphology.white_tophat( + nucleiImages[z, :, :], + morphology.selem.disk(20))) + coarseHessianMask[z, :, :] = coarseHessian == coarseHessian.max() + coarseHessianMask[z, :, :] = morphology.binary_closing( + coarseHessianMask[z, :, :], morphology.selem.disk(5)) + coarseHessianMask[z, :, :] = (coarseHessianMask[z, :, :] * + borderMask) + coarseHessianMask[z, :, :] = binary_fill_holes( + coarseHessianMask[z, :, :]) + + # combine masks + nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask + return binary_fill_holes(nucleiMask) + +def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, + membraneImages: np.ndarray) -> np.ndarray: + # TO DO: Add description + + nucleiMask = self.get_nuclei_mask(nucleiImages) + membraneMask = self.get_membrane_mask(membraneImages) + + watershedMarker = np.zeros(nucleiMask.shape) + + for z in range(len(self.dataSet.get_z_positions())): + + # generate areas of sure bg and fg, as well as the area of + # unknown classification + background = morphology.dilation(nucleiMask[z, :, :], + morphology.selem.disk(15)) + membraneDilated = morphology.dilation( + membraneMask[z, :, :].astype('bool'), + morphology.selem.disk(10)) + foreground = morphology.erosion(nucleiMask[z, :, :] * ~ + membraneDilated, + morphology.selem.disk(5)) + unknown = background * ~ foreground + + background = np.uint8(background) * 255 + foreground = np.uint8(foreground) * 255 + unknown = np.uint8(unknown) * 255 + + # Marker labelling + ret, markers = cv2.connectedComponents(foreground) + + # Add one to all labels so that sure background is not 0, but 1 + markers = markers + 100 + + # Now, mark the region of unknown with zero + markers[unknown == 255] = 0 + + watershedMarker[z, :, :] = markers + + return watershedMarker + +def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: + # cv2 only works in 3D images of 8bit. Make a 3D grayscale by + # using the same grayscale image in each of the rgb channels + # code below based on https://stackoverflow.com/questions/ + # 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv + + # invert image + uint16Image = 2**16 - uint16Image + + # convert to uint8 + ratio = np.amax(uint16Image) / 256 + uint8Image = (uint16Image / ratio).astype('uint8') + + rgbImage = np.zeros((2048, 2048, 3)) + rgbImage[:, :, 0] = uint8Image + rgbImage[:, :, 1] = uint8Image + rgbImage[:, :, 2] = uint8Image + rgbImage = rgbImage.astype('uint8') + + return rgbImage + +def apply_watershed(self, nucleiImages: np.ndarray, + watershedMarkers: np.ndarray) -> np.ndarray: + + watershedOutput = np.zeros(watershedMarkers.shape) + for z in range(len(self.dataSet.get_z_positions())): + rgbImage = self.convert_grayscale_to_rgb(nucleiImages[z, :, :]) + watershedOutput[z, :, :] = cv2.watershed(rgbImage, + watershedMarkers[z, :, :]. + astype('int32')) + watershedOutput[z, :, :][watershedOutput[z, :, :] <= 100] = 0 + + return watershedOutput + +def get_overlapping_nuclei(self, watershedZ0: np.ndarray, + watershedZ1: np.ndarray, n0: int): + z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) + z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes > 100] + + if z1NucleiIndexes.shape[0] > 0: + + # calculate overlap fraction + n0Area = np.count_nonzero(watershedZ0 == n0) + n1Area = np.zeros(len(z1NucleiIndexes)) + overlapArea = np.zeros(len(z1NucleiIndexes)) + + for ii in range(len(z1NucleiIndexes)): + n1 = z1NucleiIndexes[ii] + n1Area[ii] = np.count_nonzero(watershedZ1 == n1) + overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * + (watershedZ1 == n1)) + + n0OverlapFraction = np.asarray(overlapArea / n0Area) + n1OverlapFraction = np.asarray(overlapArea / n1Area) + index = list(range(len(n0OverlapFraction))) + + # select the nuclei that has the highest fraction in n0 and n1 + r1, r2, indexSorted = zip(*sorted(zip(n0OverlapFraction, + n1OverlapFraction, + index), + reverse=True)) + + if (n0OverlapFraction[indexSorted[0]] > 0.2 and + n1OverlapFraction[indexSorted[0]] > 0.5): + return m1NucleiIndexes[indexSorted[0]], + n0OverlapFraction[indexSorted[0]], + n1OverlapFraction[indexSorted[0]] + else: + return False, False, False + else: + return False, False, False + +def combine_2d_segmentation_masks_into_3d(self, + watershedOutput: + np.ndarray) -> np.ndarray: + # TO DO: Add description + + # TO DO: this implementation is very rough, needs to be improved. + # good just for testing purposes + + # Initialize empty array with size as watershedOutput array + watershedCombinedZ = np.zeros(watershedOutput.shape) + + # copy the mask of the section farthest to the coverslip + watershedCombinedZ[-1, :, :] = watershedOutput[-1, :, :] + + # starting far from coverslip + for z in range(len(self.dataSet.get_z_positions())-1, 0, -1): + zNucleiIndex = np.unique(watershedOutput[z, :, :])[ + np.unique(watershedOutput[z, :, :]) > 100] + + for n0 in zNucleiIndex: + n1, f0, f1 = self.get_overlapping_nuclei( + watershedCombinedZ[z, :, :], + watershedOutput[z-1, :, :], + n0) + if n1: + watershedCombinedZ[z-1, :, :][(watershedOutput[z-1, :, :] == + n1)] = n0 + return watershedCombinedZ From c663c25348204598590f7c09d43d63edf8473065 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 15:30:14 -0500 Subject: [PATCH 283/419] removing utility functions from segment.py --- merlin/analysis/segment.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 7425eed6..8bcaec6f 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -9,7 +9,6 @@ from shapely import geometry from typing import List, Dict from scipy.spatial import cKDTree -from scipy.ndimage.morphology import binary_fill_holes from merlin.core import dataset from merlin.core import analysistask @@ -174,11 +173,13 @@ def get_cell_boundaries(self) -> List[spatialfeature.SpatialFeature]: def _run_analysis(self, fragmentIndex): startTime = time.time() + print('Entered the _run_analysis method, FOV ' + str(fragmentIndex) ) + globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) - print('reading indexes for fov ' + str(fragmentIndex) ) + print(' globalTask loaded') # read membrane (seed) and nuclei (watershed) indexes membraneIndex = self.dataSet \ @@ -191,7 +192,7 @@ def _run_analysis(self, fragmentIndex): self.parameters['nuclei_channel_name']) endTime = time.time() - print("Indexes read, ET {:.2f} min"\ + print(" image indexes read, ET {:.2f} min"\ .format((endTime - startTime) / 60)) # read membrane (seed) and nuclei (watershed) images @@ -199,31 +200,31 @@ def _run_analysis(self, fragmentIndex): nucleiImages = self._read_image_stack(fragmentIndex, nucleiIndex) endTime = time.time() - print("Images read, ET {:.2f} min" \ + print(" images read, ET {:.2f} min" \ .format((endTime - startTime) / 60)) # Prepare masks for cv2 watershed - watershedMarkers = self._get_watershed_markers(nucleiImages, - membraneImages) + watershedMarkers = watershed.get_cv2_watershed_markers(nucleiImages, + membraneImages) endTime = time.time() - print("Markers calculated, ET {:.2f} min" \ + print(" markers calculated, ET {:.2f} min" \ .format((endTime - startTime) / 60)) # perform watershed in individual z positions - watershedOutput = self._apply_watershed(nucleiImages, - watershedMarkers) + watershedOutput = watershed.apply_cv2_watershed(nucleiImages, + watershedMarkers) endTime = time.time() - print("watershed calculated, ET {:.2f} min" \ + print(" watershed calculated, ET {:.2f} min" \ .format((endTime - startTime) / 60)) # combine all z positions in watershed - watershedCombinedOutput = self._combine_watershed_z_positions( - watershedOutput) + watershedCombinedOutput = watershed \ + .combine_2d_segmentation_masks_into_3d(watershedOutput) endTime = time.time() - print("watershed z positions combined, ET {:.2f} min" \ + print(" watershed z positions combined, ET {:.2f} min" \ .format((endTime - startTime) / 60)) # get features from mask. This is the slowestart (6 min for the @@ -238,7 +239,7 @@ def _run_analysis(self, fragmentIndex): featureDB.write_features(featureList, fragmentIndex) endTime = time.time() - print("features written, ET {:.2f} min" \ + print(" features written, ET {:.2f} min" \ .format((endTime - startTime) / 60)) def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: @@ -246,7 +247,7 @@ def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: self.parameters['warp_task']) return np.array([warpTask.get_aligned_image(fov, channelIndex, z) for z in range(len(self.dataSet.get_z_positions()))]) - +""" def _get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: # generate mask based on thresholding mask = np.zeros(membraneImages.shape) @@ -464,6 +465,7 @@ def _combine_watershed_z_positions(self, watershedCombinedZ[z-1, :, :][(watershedOutput[z-1, :, :] == n1)] = n0 return watershedCombinedZ +""" class CleanCellBoundaries(analysistask.ParallelAnalysisTask): ''' From b22bb6f77e4063f167d17efe51b8c5ca435b19e7 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 16:39:26 -0500 Subject: [PATCH 284/419] add comments to the header of multiple methods. --- merlin/util/watershed.py | 83 ++++++++++++++++++++++++++++++++++------ 1 file changed, 71 insertions(+), 12 deletions(-) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 3b5563f3..d9a820ff 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -171,8 +171,15 @@ def get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: return mask def get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: - - # TO DO: Add description + """Calculate binary mask with 1's in membrane pixels and 0 otherwise. + The images expected are some type of Nuclei label (e.g. DAPI) + + Args: + membraneImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) + """ # generate nuclei mask based on thresholding thresholdingMask = np.zeros(nucleiImages.shape) @@ -234,7 +241,18 @@ def get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, membraneImages: np.ndarray) -> np.ndarray: - # TO DO: Add description + """Combine membrane and nuclei markers into a single multilabel mask + for CV2 watershed + + Args: + nucleiImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + membraneImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) of + cv2-compatible watershed markers + """ nucleiMask = self.get_nuclei_mask(nucleiImages) membraneMask = self.get_membrane_mask(membraneImages) @@ -273,10 +291,16 @@ def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, return watershedMarker def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: - # cv2 only works in 3D images of 8bit. Make a 3D grayscale by - # using the same grayscale image in each of the rgb channels - # code below based on https://stackoverflow.com/questions/ - # 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv + """Convert a 16 bit 2D grayscale image into a 3D 8-bit RGB image. + cv2 only works in 8-bit. Based on https://stackoverflow.com/questions/ + 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv3D + + Args: + uint16Image: a 2 dimensional numpy array containing the 16-bit + image + Returns: + ndarray containing a 3 dimensional 8-bit image stack + """ # invert image uint16Image = 2**16 - uint16Image @@ -293,8 +317,20 @@ def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: return rgbImage -def apply_watershed(self, nucleiImages: np.ndarray, +def apply_cv2_watershed(self, nucleiImages: np.ndarray, watershedMarkers: np.ndarray) -> np.ndarray: + """Perform watershed using cv2 + + Args: + nucleiImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + watershedMarkers: a 3 dimensional numpy array containing the cv2 + markers arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) of + segmented cells. masks in different z positions are + independent + """ watershedOutput = np.zeros(watershedMarkers.shape) for z in range(len(self.dataSet.get_z_positions())): @@ -308,6 +344,22 @@ def apply_watershed(self, nucleiImages: np.ndarray, def get_overlapping_nuclei(self, watershedZ0: np.ndarray, watershedZ1: np.ndarray, n0: int): + """Perform watershed using cv2 + + Args: + watershedZ0: a 2 dimensional numpy array containing a + segmentation mask + watershedZ1: a 2 dimensional numpy array containing a + segmentation mask adjacent to watershedZ1 + n0: an integer with the index of the cell/nuclei to be compared + between the provided watershed segmentation masks + Returns: + a tuple (n1, f0, f1) containing the label of the cell in Z1 + overlapping n0 (n1), the fraction of n0 overlaping n1 (f0) and + the fraction of n1 overlapping n0 (f1) + + """ + z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes > 100] @@ -336,7 +388,7 @@ def get_overlapping_nuclei(self, watershedZ0: np.ndarray, if (n0OverlapFraction[indexSorted[0]] > 0.2 and n1OverlapFraction[indexSorted[0]] > 0.5): - return m1NucleiIndexes[indexSorted[0]], + return z1NucleiIndexes[indexSorted[0]], n0OverlapFraction[indexSorted[0]], n1OverlapFraction[indexSorted[0]] else: @@ -347,10 +399,17 @@ def get_overlapping_nuclei(self, watershedZ0: np.ndarray, def combine_2d_segmentation_masks_into_3d(self, watershedOutput: np.ndarray) -> np.ndarray: - # TO DO: Add description + """Take a 3 dimensional watershed masks and relabel them so that + nuclei in adjacent sections have the same label if the area their + overlap surpases certain threshold - # TO DO: this implementation is very rough, needs to be improved. - # good just for testing purposes + Args: + watershedOutput: a 3 dimensional numpy array containing the + segmentation masks arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) of + relabeled segmented cells + """ # Initialize empty array with size as watershedOutput array watershedCombinedZ = np.zeros(watershedOutput.shape) From dad7592aaaa19fc1695144b8ae8a2d2d70328cb5 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 16:44:13 -0500 Subject: [PATCH 285/419] remove commented methods --- merlin/analysis/segment.py | 220 +------------------------------------ 1 file changed, 1 insertion(+), 219 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 8bcaec6f..7534bb9e 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -247,225 +247,7 @@ def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: self.parameters['warp_task']) return np.array([warpTask.get_aligned_image(fov, channelIndex, z) for z in range(len(self.dataSet.get_z_positions()))]) -""" - def _get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: - # generate mask based on thresholding - mask = np.zeros(membraneImages.shape) - fineBlockSize = 61 - for z in range(len(self.dataSet.get_z_positions())): - mask[z, :, :] = (membraneImages[z, :, :] > - filters.threshold_local(membraneImages[z, :, :], - fineBlockSize, - offset=0)) - mask[z, :, :] = morphology.remove_small_objects( - mask[z, :, :].astype('bool'), - min_size=100, - connectivity=1) - mask[z, :, :] = morphology.binary_closing(mask[z, :, :], - morphology.selem.disk(5)) - mask[z, :, :] = morphology.skeletonize(mask[z, :, :]) - - # combine masks - return mask - - def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: - # generate nuclei mask based on thresholding - thresholdingMask = np.zeros(nucleiImages.shape) - coarseBlockSize = 241 - fineBlockSize = 61 - for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = (nucleiImages[z, :, :] > - filters.threshold_local( - nucleiImages[z, :, :], - coarseBlockSize, - offset=0)) - fineThresholdingMask = (nucleiImages[z, :, :] > - filters.threshold_local( - nucleiImages[z, :, :], - fineBlockSize, - offset=0)) - thresholdingMask[z, :, :] = (coarseThresholdingMask * - fineThresholdingMask) - thresholdingMask[z, :, :] = binary_fill_holes( - thresholdingMask[z, :, :]) - - # generate border mask, necessary to avoid making a single - # connected component when using binary_fill_holes below - borderMask = np.zeros((2048, 2048)) - borderMask[25:2023, 25:2023] = 1 - - # TODO - use the image size variable for borderMask - - # generate nuclei mask from hessian, fine - fineHessianMask = np.zeros(nucleiImages.shape) - for z in range(len(self.dataSet.get_z_positions())): - fineHessian = filters.hessian(nucleiImages[z, :, :]) - fineHessianMask[z, :, :] = fineHessian == fineHessian.max() - fineHessianMask[z, :, :] = morphology.binary_closing( - fineHessianMask[z, :, :], - morphology.selem.disk(5)) - fineHessianMask[z, :, :] = fineHessianMask[z, :, :] * borderMask - fineHessianMask[z, :, :] = binary_fill_holes( - fineHessianMask[z, :, :]) - - # generate dapi mask from hessian, coarse - coarseHessianMask = np.zeros(nucleiImages.shape) - for z in range(len(self.dataSet.get_z_positions())): - coarseHessian = filters.hessian(nucleiImages[z, :, :] - - morphology.white_tophat( - nucleiImages[z, :, :], - morphology.selem.disk(20))) - coarseHessianMask[z, :, :] = coarseHessian == coarseHessian.max() - coarseHessianMask[z, :, :] = morphology.binary_closing( - coarseHessianMask[z, :, :], morphology.selem.disk(5)) - coarseHessianMask[z, :, :] = (coarseHessianMask[z, :, :] * - borderMask) - coarseHessianMask[z, :, :] = binary_fill_holes( - coarseHessianMask[z, :, :]) - - # combine masks - nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask - return binary_fill_holes(nucleiMask) - - def _get_watershed_markers(self, nucleiImages: np.ndarray, - membraneImages: np.ndarray) -> np.ndarray: - - nucleiMask = self._get_nuclei_mask(nucleiImages) - membraneMask = self._get_membrane_mask(membraneImages) - - watershedMarker = np.zeros(nucleiMask.shape) - - for z in range(len(self.dataSet.get_z_positions())): - - # generate areas of sure bg and fg, as well as the area of - # unknown classification - background = morphology.dilation(nucleiMask[z, :, :], - morphology.selem.disk(15)) - membraneDilated = morphology.dilation( - membraneMask[z, :, :].astype('bool'), - morphology.selem.disk(10)) - foreground = morphology.erosion(nucleiMask[z, :, :] * ~ - membraneDilated, - morphology.selem.disk(5)) - unknown = background * ~ foreground - - background = np.uint8(background) * 255 - foreground = np.uint8(foreground) * 255 - unknown = np.uint8(unknown) * 255 - - # Marker labelling - ret, markers = cv2.connectedComponents(foreground) - - # Add one to all labels so that sure background is not 0, but 1 - markers = markers + 100 - - # Now, mark the region of unknown with zero - markers[unknown == 255] = 0 - - watershedMarker[z, :, :] = markers - - return watershedMarker - - def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: - # cv2 only works in 3D images of 8bit. Make a 3D grayscale by - # using the same grayscale image in each of the rgb channels - # code below based on https://stackoverflow.com/questions/ - # 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv - - # invert image - uint16Image = 2**16 - uint16Image - - # convert to uint8 - ratio = np.amax(uint16Image) / 256 - uint8Image = (uint16Image / ratio).astype('uint8') - - rgbImage = np.zeros((2048, 2048, 3)) - rgbImage[:, :, 0] = uint8Image - rgbImage[:, :, 1] = uint8Image - rgbImage[:, :, 2] = uint8Image - rgbImage = rgbImage.astype('uint8') - - return rgbImage - - def _apply_watershed(self, nucleiImages: np.ndarray, - watershedMarkers: np.ndarray) -> np.ndarray: - - watershedOutput = np.zeros(watershedMarkers.shape) - for z in range(len(self.dataSet.get_z_positions())): - rgbImage = self._convert_grayscale_to_rgb(nucleiImages[z, :, :]) - watershedOutput[z, :, :] = cv2.watershed(rgbImage, - watershedMarkers[z, :, :]. - astype('int32')) - watershedOutput[z, :, :][watershedOutput[z, :, :] <= 100] = 0 - - return watershedOutput - - def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, - watershedZ1: np.ndarray, n0: int): - z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) - z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes > 100] - - if z1NucleiIndexes.shape[0] > 0: - - # calculate overlap fraction - n0Area = np.count_nonzero(watershedZ0 == n0) - n1Area = np.zeros(len(z1NucleiIndexes)) - overlapArea = np.zeros(len(z1NucleiIndexes)) - - for ii in range(len(z1NucleiIndexes)): - n1 = z1NucleiIndexes[ii] - n1Area[ii] = np.count_nonzero(watershedZ1 == n1) - overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * - (watershedZ1 == n1)) - - n0OverlapFraction = np.asarray(overlapArea / n0Area) - n1OverlapFraction = np.asarray(overlapArea / n1Area) - index = list(range(len(n0OverlapFraction))) - - # select the nuclei that has the highest fraction in n0 and n1 - r1, r2, indexSorted = zip(*sorted(zip(n0OverlapFraction, - n1OverlapFraction, - index), - reverse=True)) - - if (n0OverlapFraction[indexSorted[0]] > 0.2 and - n1OverlapFraction[indexSorted[0]] > 0.5): - return m1NucleiIndexes[indexSorted[0]], - n0OverlapFraction[indexSorted[0]], - n1OverlapFraction[indexSorted[0]] - else: - return False, False, False - else: - return False, False, False - - def _combine_watershed_z_positions(self, - watershedOutput: - np.ndarray) -> np.ndarray: - - # TO DO: this implementation is very rough, needs to be improved. - # good just for testing purposes - - # Initialize empty array with size as watershedOutput array - watershedCombinedZ = np.zeros(watershedOutput.shape) - - # copy the mask of the section farthest to the coverslip - watershedCombinedZ[-1, :, :] = watershedOutput[-1, :, :] - - # starting far from coverslip - for z in range(len(self.dataSet.get_z_positions())-1, 0, -1): - zNucleiIndex = np.unique(watershedOutput[z, :, :])[ - np.unique(watershedOutput[z, :, :]) > 100] - - for n0 in zNucleiIndex: - n1, f0, f1 = self._get_overlapping_nuclei( - watershedCombinedZ[z, :, :], - watershedOutput[z-1, :, :], - n0) - if n1: - watershedCombinedZ[z-1, :, :][(watershedOutput[z-1, :, :] == - n1)] = n0 - return watershedCombinedZ -""" + class CleanCellBoundaries(analysistask.ParallelAnalysisTask): ''' From 2cf338c55e88e50783f4c3dc0d81d30b75d74a5a Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 16:59:49 -0500 Subject: [PATCH 286/419] pep8 compliance --- merlin/analysis/segment.py | 23 +++++++++++------------ merlin/util/watershed.py | 28 ++++++++++++++-------------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 7534bb9e..fb87ceac 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -16,7 +16,7 @@ from merlin.util import watershed import pandas import networkx as nx -import time +import time class FeatureSavingAnalysisTask(analysistask.ParallelAnalysisTask): @@ -173,12 +173,11 @@ def get_cell_boundaries(self) -> List[spatialfeature.SpatialFeature]: def _run_analysis(self, fragmentIndex): startTime = time.time() - print('Entered the _run_analysis method, FOV ' + str(fragmentIndex) ) + print('Entered the _run_analysis method, FOV ' + str(fragmentIndex)) globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) - print(' globalTask loaded') # read membrane (seed) and nuclei (watershed) indexes @@ -192,8 +191,8 @@ def _run_analysis(self, fragmentIndex): self.parameters['nuclei_channel_name']) endTime = time.time() - print(" image indexes read, ET {:.2f} min"\ - .format((endTime - startTime) / 60)) + print(" image indexes read, ET {:.2f} min" \ + .format((endTime - startTime) / 60)) # read membrane (seed) and nuclei (watershed) images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) @@ -201,7 +200,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" images read, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + .format((endTime - startTime) / 60)) # Prepare masks for cv2 watershed watershedMarkers = watershed.get_cv2_watershed_markers(nucleiImages, @@ -209,7 +208,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" markers calculated, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + .format((endTime - startTime) / 60)) # perform watershed in individual z positions watershedOutput = watershed.apply_cv2_watershed(nucleiImages, @@ -217,7 +216,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" watershed calculated, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + .format((endTime - startTime) / 60)) # combine all z positions in watershed watershedCombinedOutput = watershed \ @@ -225,10 +224,10 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" watershed z positions combined, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + .format((endTime - startTime) / 60)) - # get features from mask. This is the slowestart (6 min for the - # previous part, 15+ for the rest, for a 7 frame Image. + # get features from mask. This is the slowestart (6 min for the + # previous part, 15+ for the rest, for a 7 frame Image. zPos = np.array(self.dataSet.get_data_organization().get_z_positions()) featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( (watershedCombinedOutput == i), fragmentIndex, @@ -240,7 +239,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" features written, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + .format((endTime - startTime) / 60)) def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index d9a820ff..d6d4be07 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -143,13 +143,13 @@ def prepare_watershed_images(watershedImageStack: np.ndarray def get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: """Calculate binary mask with 1's in membrane pixels and 0 otherwise. - The images expected are some type of membrane label (WGA, ConA, + The images expected are some type of membrane label (WGA, ConA, Lamin, Cadherins) Args: membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). - Returns: + Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) """ mask = np.zeros(membraneImages.shape) @@ -177,7 +177,7 @@ def get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: Args: membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). - Returns: + Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) """ @@ -291,15 +291,15 @@ def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, return watershedMarker def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: - """Convert a 16 bit 2D grayscale image into a 3D 8-bit RGB image. + """Convert a 16 bit 2D grayscale image into a 3D 8-bit RGB image. cv2 only works in 8-bit. Based on https://stackoverflow.com/questions/ - 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv3D + 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv3D Args: - uint16Image: a 2 dimensional numpy array containing the 16-bit + uint16Image: a 2 dimensional numpy array containing the 16-bit image Returns: - ndarray containing a 3 dimensional 8-bit image stack + ndarray containing a 3 dimensional 8-bit image stack """ # invert image @@ -347,15 +347,15 @@ def get_overlapping_nuclei(self, watershedZ0: np.ndarray, """Perform watershed using cv2 Args: - watershedZ0: a 2 dimensional numpy array containing a + watershedZ0: a 2 dimensional numpy array containing a segmentation mask watershedZ1: a 2 dimensional numpy array containing a segmentation mask adjacent to watershedZ1 n0: an integer with the index of the cell/nuclei to be compared between the provided watershed segmentation masks Returns: - a tuple (n1, f0, f1) containing the label of the cell in Z1 - overlapping n0 (n1), the fraction of n0 overlaping n1 (f0) and + a tuple (n1, f0, f1) containing the label of the cell in Z1 + overlapping n0 (n1), the fraction of n0 overlaping n1 (f0) and the fraction of n1 overlapping n0 (f1) """ @@ -399,12 +399,12 @@ def get_overlapping_nuclei(self, watershedZ0: np.ndarray, def combine_2d_segmentation_masks_into_3d(self, watershedOutput: np.ndarray) -> np.ndarray: - """Take a 3 dimensional watershed masks and relabel them so that - nuclei in adjacent sections have the same label if the area their - overlap surpases certain threshold + """Take a 3 dimensional watershed masks and relabel them so that + nuclei in adjacent sections have the same label if the area their + overlap surpases certain threshold Args: - watershedOutput: a 3 dimensional numpy array containing the + watershedOutput: a 3 dimensional numpy array containing the segmentation masks arranged as (z, x, y). Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) of From 18dec1a3d717180f91c1bc9db09a94b68e852516 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 17:04:07 -0500 Subject: [PATCH 287/419] pep8 compliance --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index fb87ceac..d7e7a7ba 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -191,8 +191,8 @@ def _run_analysis(self, fragmentIndex): self.parameters['nuclei_channel_name']) endTime = time.time() - print(" image indexes read, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + print(" image indexes read, ET {:.2f} min".format( + (endTime - startTime) / 60)) # read membrane (seed) and nuclei (watershed) images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) From 7a4d7920cd9b3b6ff57811f8682c746061e9dc8f Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 17:11:46 -0500 Subject: [PATCH 288/419] pep8 compliance --- merlin/analysis/segment.py | 20 ++++++++++---------- merlin/util/watershed.py | 18 ++++++++++++------ 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index d7e7a7ba..a70af875 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -199,32 +199,32 @@ def _run_analysis(self, fragmentIndex): nucleiImages = self._read_image_stack(fragmentIndex, nucleiIndex) endTime = time.time() - print(" images read, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + print(" images read, ET {:.2f} min".format( + (endTime - startTime) / 60)) # Prepare masks for cv2 watershed watershedMarkers = watershed.get_cv2_watershed_markers(nucleiImages, membraneImages) endTime = time.time() - print(" markers calculated, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + print(" markers calculated, ET {:.2f} min".format( + (endTime - startTime) / 60)) # perform watershed in individual z positions watershedOutput = watershed.apply_cv2_watershed(nucleiImages, watershedMarkers) endTime = time.time() - print(" watershed calculated, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + print(" watershed calculated, ET {:.2f} min".format( + (endTime - startTime) / 60)) # combine all z positions in watershed watershedCombinedOutput = watershed \ .combine_2d_segmentation_masks_into_3d(watershedOutput) endTime = time.time() - print(" watershed z positions combined, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + print(" watershed z positions combined, ET {:.2f} min".format( + (endTime - startTime) / 60)) # get features from mask. This is the slowestart (6 min for the # previous part, 15+ for the rest, for a 7 frame Image. @@ -238,8 +238,8 @@ def _run_analysis(self, fragmentIndex): featureDB.write_features(featureList, fragmentIndex) endTime = time.time() - print(" features written, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + print(" features written, ET {:.2f} min".format( + (endTime - startTime) / 60)) def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index d6d4be07..a79f25cd 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -141,6 +141,7 @@ def prepare_watershed_images(watershedImageStack: np.ndarray return normalizedWatershed, watershedMask + def get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: """Calculate binary mask with 1's in membrane pixels and 0 otherwise. The images expected are some type of membrane label (WGA, ConA, @@ -170,6 +171,7 @@ def get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: # combine masks return mask + def get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: """Calculate binary mask with 1's in membrane pixels and 0 otherwise. The images expected are some type of Nuclei label (e.g. DAPI) @@ -239,6 +241,7 @@ def get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask return binary_fill_holes(nucleiMask) + def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, membraneImages: np.ndarray) -> np.ndarray: """Combine membrane and nuclei markers into a single multilabel mask @@ -290,6 +293,7 @@ def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, return watershedMarker + def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: """Convert a 16 bit 2D grayscale image into a 3D 8-bit RGB image. cv2 only works in 8-bit. Based on https://stackoverflow.com/questions/ @@ -298,7 +302,7 @@ def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: Args: uint16Image: a 2 dimensional numpy array containing the 16-bit image - Returns: + Returns: ndarray containing a 3 dimensional 8-bit image stack """ @@ -317,8 +321,9 @@ def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: return rgbImage + def apply_cv2_watershed(self, nucleiImages: np.ndarray, - watershedMarkers: np.ndarray) -> np.ndarray: + watershedMarkers: np.ndarray) -> np.ndarray: """Perform watershed using cv2 Args: @@ -326,9 +331,9 @@ def apply_cv2_watershed(self, nucleiImages: np.ndarray, arranged as (z, x, y). watershedMarkers: a 3 dimensional numpy array containing the cv2 markers arranged as (z, x, y). - Returns: + Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) of - segmented cells. masks in different z positions are + segmented cells. masks in different z positions are independent """ @@ -342,6 +347,7 @@ def apply_cv2_watershed(self, nucleiImages: np.ndarray, return watershedOutput + def get_overlapping_nuclei(self, watershedZ0: np.ndarray, watershedZ1: np.ndarray, n0: int): """Perform watershed using cv2 @@ -357,7 +363,6 @@ def get_overlapping_nuclei(self, watershedZ0: np.ndarray, a tuple (n1, f0, f1) containing the label of the cell in Z1 overlapping n0 (n1), the fraction of n0 overlaping n1 (f0) and the fraction of n1 overlapping n0 (f1) - """ z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) @@ -396,6 +401,7 @@ def get_overlapping_nuclei(self, watershedZ0: np.ndarray, else: return False, False, False + def combine_2d_segmentation_masks_into_3d(self, watershedOutput: np.ndarray) -> np.ndarray: @@ -406,7 +412,7 @@ def combine_2d_segmentation_masks_into_3d(self, Args: watershedOutput: a 3 dimensional numpy array containing the segmentation masks arranged as (z, x, y). - Returns: + Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) of relabeled segmented cells """ From 0b4b31cbd065c319dc90962dc9e1ed56899b1786 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 17:12:35 -0500 Subject: [PATCH 289/419] pep8 compliance --- merlin/util/watershed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index a79f25cd..9e40d7bf 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -252,7 +252,7 @@ def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, arranged as (z, x, y). membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). - Returns: + Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) of cv2-compatible watershed markers """ From de616ab89fac68e3740bef4160684176bbafbb9f Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 12 Feb 2020 13:03:26 -0500 Subject: [PATCH 290/419] add george's modifications to dataportal --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c65e9715..dfc4de44 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,4 +28,4 @@ tables boto3 xmltodict google-cloud-storage -docutils<0.16,>=0.10 \ No newline at end of file +docutils<0.16,>=0.10 From a41a46a7f90c2af8ddebb879fb7540a4ce26bb65 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 15 Feb 2020 09:38:38 -0500 Subject: [PATCH 291/419] adding additional modifications to aws --- merlin/util/dataportal.py | 1 - 1 file changed, 1 deletion(-) diff --git a/merlin/util/dataportal.py b/merlin/util/dataportal.py index 3c8d2bb6..bba38f97 100755 --- a/merlin/util/dataportal.py +++ b/merlin/util/dataportal.py @@ -375,6 +375,5 @@ def read_file_bytes(self, startByte, endByte): endByte=endByte-1) return file - def close(self) -> None: pass From 5f6960fdd2fce3a6de3309355a86c92ed4d0e1a2 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Mon, 2 Mar 2020 14:03:44 -0500 Subject: [PATCH 292/419] added printing for debuging --- merlin/analysis/segment.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index a70af875..a5f81214 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -201,6 +201,8 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) + print(" membraneImages Type: " + type(membraneImages)) + print(" nucleiImages Type: " + type(nucleiImages)) # Prepare masks for cv2 watershed watershedMarkers = watershed.get_cv2_watershed_markers(nucleiImages, From 6503d746b289fa4cdcbac1177b7a6eab33cbafd2 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Mon, 2 Mar 2020 14:26:51 -0500 Subject: [PATCH 293/419] added printing for debuging --- merlin/analysis/segment.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index a5f81214..0e6b01ee 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -201,8 +201,16 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) - print(" membraneImages Type: " + type(membraneImages)) - print(" nucleiImages Type: " + type(nucleiImages)) + print(" membraneImages Type: " + str(type(membraneImages))) + print(" membraneImages Size: [" + + str(membraneImages.shape[0]) + + "," + str(membraneImages.shape[1]) + + "," + str(membraneImages.shape[2]) "]" ) + print(" nucleiImages Type: " + str(type(nucleiImages))) + print(" nucleiImages Size: [" + + str(nucleiImages.shape[0]) + + "," + str(nucleiImages.shape[1]) + + "," + str(nucleiImages.shape[2]) "]" ) # Prepare masks for cv2 watershed watershedMarkers = watershed.get_cv2_watershed_markers(nucleiImages, From 77c2ae870e8378dd4e881dc9ca8208995ecde2e0 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Mon, 2 Mar 2020 14:31:30 -0500 Subject: [PATCH 294/419] added printing for debuging --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 0e6b01ee..52a9f517 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -205,12 +205,12 @@ def _run_analysis(self, fragmentIndex): print(" membraneImages Size: [" + str(membraneImages.shape[0]) + "," + str(membraneImages.shape[1]) - + "," + str(membraneImages.shape[2]) "]" ) + + "," + str(membraneImages.shape[2]) + "]" ) print(" nucleiImages Type: " + str(type(nucleiImages))) print(" nucleiImages Size: [" + str(nucleiImages.shape[0]) + "," + str(nucleiImages.shape[1]) - + "," + str(nucleiImages.shape[2]) "]" ) + + "," + str(nucleiImages.shape[2]) + "]" ) # Prepare masks for cv2 watershed watershedMarkers = watershed.get_cv2_watershed_markers(nucleiImages, From c595a6a25614701bbc48a527c0d56f3ef5c743f6 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Mon, 2 Mar 2020 16:22:34 -0500 Subject: [PATCH 295/419] removing self calls --- merlin/util/watershed.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 9e40d7bf..0ccac583 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -142,7 +142,7 @@ def prepare_watershed_images(watershedImageStack: np.ndarray return normalizedWatershed, watershedMask -def get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: +def get_membrane_mask(membraneImages: np.ndarray) -> np.ndarray: """Calculate binary mask with 1's in membrane pixels and 0 otherwise. The images expected are some type of membrane label (WGA, ConA, Lamin, Cadherins) @@ -172,7 +172,7 @@ def get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: return mask -def get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: +def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: """Calculate binary mask with 1's in membrane pixels and 0 otherwise. The images expected are some type of Nuclei label (e.g. DAPI) @@ -242,7 +242,7 @@ def get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: return binary_fill_holes(nucleiMask) -def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, +def get_cv2_watershed_markers(nucleiImages: np.ndarray, membraneImages: np.ndarray) -> np.ndarray: """Combine membrane and nuclei markers into a single multilabel mask for CV2 watershed @@ -294,7 +294,7 @@ def get_cv2_watershed_markers(self, nucleiImages: np.ndarray, return watershedMarker -def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: +def convert_grayscale_to_rgb(uint16Image: np.ndarray) -> np.ndarray: """Convert a 16 bit 2D grayscale image into a 3D 8-bit RGB image. cv2 only works in 8-bit. Based on https://stackoverflow.com/questions/ 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv3D @@ -322,7 +322,7 @@ def convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: return rgbImage -def apply_cv2_watershed(self, nucleiImages: np.ndarray, +def apply_cv2_watershed(nucleiImages: np.ndarray, watershedMarkers: np.ndarray) -> np.ndarray: """Perform watershed using cv2 @@ -348,7 +348,7 @@ def apply_cv2_watershed(self, nucleiImages: np.ndarray, return watershedOutput -def get_overlapping_nuclei(self, watershedZ0: np.ndarray, +def get_overlapping_nuclei(watershedZ0: np.ndarray, watershedZ1: np.ndarray, n0: int): """Perform watershed using cv2 @@ -402,8 +402,7 @@ def get_overlapping_nuclei(self, watershedZ0: np.ndarray, return False, False, False -def combine_2d_segmentation_masks_into_3d(self, - watershedOutput: +def combine_2d_segmentation_masks_into_3d(watershedOutput: np.ndarray) -> np.ndarray: """Take a 3 dimensional watershed masks and relabel them so that nuclei in adjacent sections have the same label if the area their From ce0739a7b9136c2cd3dce91b37abfb6b06cb9168 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 3 Mar 2020 09:02:53 -0500 Subject: [PATCH 296/419] remove self calls --- merlin/util/watershed.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 0ccac583..60268345 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -11,7 +11,7 @@ from merlin.util import matlab """ -This module contains utility functions for preparing imagmes for +This module contains utility functions for preparing imagmes for watershed segmentation. """ @@ -155,7 +155,7 @@ def get_membrane_mask(membraneImages: np.ndarray) -> np.ndarray: """ mask = np.zeros(membraneImages.shape) fineBlockSize = 61 - for z in range(len(self.dataSet.get_z_positions())): + for z in range(membraneImages.shape[0]): mask[z, :, :] = (membraneImages[z, :, :] > filters.threshold_local(membraneImages[z, :, :], fineBlockSize, @@ -187,7 +187,7 @@ def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: thresholdingMask = np.zeros(nucleiImages.shape) coarseBlockSize = 241 fineBlockSize = 61 - for z in range(len(self.dataSet.get_z_positions())): + for z in range(nucleiImages.shape[0]): coarseThresholdingMask = (nucleiImages[z, :, :] > filters.threshold_local( nucleiImages[z, :, :], @@ -212,7 +212,7 @@ def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: # generate nuclei mask from hessian, fine fineHessianMask = np.zeros(nucleiImages.shape) - for z in range(len(self.dataSet.get_z_positions())): + for z in range(nucleiImages.shape[0]): fineHessian = filters.hessian(nucleiImages[z, :, :]) fineHessianMask[z, :, :] = fineHessian == fineHessian.max() fineHessianMask[z, :, :] = morphology.binary_closing( @@ -224,7 +224,7 @@ def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: # generate dapi mask from hessian, coarse coarseHessianMask = np.zeros(nucleiImages.shape) - for z in range(len(self.dataSet.get_z_positions())): + for z in range(nucleiImages.shape[0]): coarseHessian = filters.hessian(nucleiImages[z, :, :] - morphology.white_tophat( nucleiImages[z, :, :], @@ -257,12 +257,12 @@ def get_cv2_watershed_markers(nucleiImages: np.ndarray, cv2-compatible watershed markers """ - nucleiMask = self.get_nuclei_mask(nucleiImages) - membraneMask = self.get_membrane_mask(membraneImages) + nucleiMask = get_nuclei_mask(nucleiImages) + membraneMask = get_membrane_mask(membraneImages) watershedMarker = np.zeros(nucleiMask.shape) - for z in range(len(self.dataSet.get_z_positions())): + for z in range(nucleiImages.shape[0]): # generate areas of sure bg and fg, as well as the area of # unknown classification @@ -338,8 +338,8 @@ def apply_cv2_watershed(nucleiImages: np.ndarray, """ watershedOutput = np.zeros(watershedMarkers.shape) - for z in range(len(self.dataSet.get_z_positions())): - rgbImage = self.convert_grayscale_to_rgb(nucleiImages[z, :, :]) + for z in range(nucleiImages.shape[0]): + rgbImage = convert_grayscale_to_rgb(nucleiImages[z, :, :]) watershedOutput[z, :, :] = cv2.watershed(rgbImage, watershedMarkers[z, :, :]. astype('int32')) @@ -423,13 +423,12 @@ def combine_2d_segmentation_masks_into_3d(watershedOutput: watershedCombinedZ[-1, :, :] = watershedOutput[-1, :, :] # starting far from coverslip - for z in range(len(self.dataSet.get_z_positions())-1, 0, -1): + for z in range(watershedOutput.shape[0]-1, 0, -1): zNucleiIndex = np.unique(watershedOutput[z, :, :])[ np.unique(watershedOutput[z, :, :]) > 100] for n0 in zNucleiIndex: - n1, f0, f1 = self.get_overlapping_nuclei( - watershedCombinedZ[z, :, :], + n1, f0, f1 = get_overlapping_nuclei(watershedCombinedZ[z, :, :], watershedOutput[z-1, :, :], n0) if n1: From 11202c463bad276bac82368800be8443210c4214 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 3 Mar 2020 10:50:52 -0500 Subject: [PATCH 297/419] pep8 compliance --- merlin/analysis/segment.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 52a9f517..d982a9ab 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -202,15 +202,15 @@ def _run_analysis(self, fragmentIndex): print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) print(" membraneImages Type: " + str(type(membraneImages))) - print(" membraneImages Size: [" - + str(membraneImages.shape[0]) - + "," + str(membraneImages.shape[1]) - + "," + str(membraneImages.shape[2]) + "]" ) + print(" membraneImages Size: [" + + str(membraneImages.shape[0]) + + "," + str(membraneImages.shape[1]) + + "," + str(membraneImages.shape[2]) + "]") print(" nucleiImages Type: " + str(type(nucleiImages))) - print(" nucleiImages Size: [" - + str(nucleiImages.shape[0]) - + "," + str(nucleiImages.shape[1]) - + "," + str(nucleiImages.shape[2]) + "]" ) + print(" nucleiImages Size: [" + + str(nucleiImages.shape[0]) + + "," + str(nucleiImages.shape[1]) + + "," + str(nucleiImages.shape[2]) + "]") # Prepare masks for cv2 watershed watershedMarkers = watershed.get_cv2_watershed_markers(nucleiImages, From b1fe54cd4371f0a00a12826b12abb3232276a8e0 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 22 Apr 2020 21:44:41 -0400 Subject: [PATCH 298/419] change WatershedSegmentNucleiCV2 to WatershedSegmentCV2, modify accordingly to allow for cytoplasmic or nuclei segmentation --- merlin/analysis/segment.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index d982a9ab..3534b599 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -124,7 +124,7 @@ def _read_and_filter_image_stack(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions()))]) -class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): +class WatershedSegmentCV2(FeatureSavingAnalysisTask): """ An analysis task that determines the boundaries of features in the @@ -136,7 +136,12 @@ class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): py_tutorials/py_imgproc/py_watershed/py_watershed.html. The watershed segmentation is performed in each z-position - independently and combined into 3D objects in a later step + independently and combined into 3D objects in a later step + + The class can be used to segment either nuclear or cytoplasmic + compartments. If both the compartment and membrane channels are the + same, the membrane channel is calculated from the edge transform of + the provided channel. Since each field of view is analyzed individually, the segmentation results should be cleaned in order to merge cells that cross the @@ -145,11 +150,11 @@ class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): def __init__(self, dataSet, parameters=None, analysisName=None): super().__init__(dataSet, parameters, analysisName) - + if 'membrane_channel_name' not in self.parameters: - self.parameters['membrane_channel_name'] = 'ConA' - if 'nuclei_channel_name' not in self.parameters: - self.parameters['nuclei_channel_name'] = 'DAPI' + self.parameters['membrane_channel_name'] = 'DAPI' + if 'compartment_channel_name' not in self.parameters: + self.parameters['compartment_channel_name'] = 'DAPI' def fragment_count(self): return len(self.dataSet.get_fovs()) From 3085ce11bcfa6c04083b7c56ebc9aaed4e963fd1 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 22 Apr 2020 21:57:33 -0400 Subject: [PATCH 299/419] change nuclei to compartment --- merlin/analysis/segment.py | 43 ++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 3534b599..8dee98f1 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -138,9 +138,9 @@ class WatershedSegmentCV2(FeatureSavingAnalysisTask): The watershed segmentation is performed in each z-position independently and combined into 3D objects in a later step - The class can be used to segment either nuclear or cytoplasmic - compartments. If both the compartment and membrane channels are the - same, the membrane channel is calculated from the edge transform of + The class can be used to segment either nuclear or cytoplasmic + compartments. If both the compartment and membrane channels are the + same, the membrane channel is calculated from the edge transform of the provided channel. Since each field of view is analyzed individually, the segmentation @@ -185,23 +185,30 @@ def _run_analysis(self, fragmentIndex): print(' globalTask loaded') - # read membrane (seed) and nuclei (watershed) indexes + # read membrane and compartment indexes membraneIndex = self.dataSet \ .get_data_organization() \ .get_data_channel_index( self.parameters['membrane_channel_name']) - nucleiIndex = self.dataSet \ - .get_data_organization() \ - .get_data_channel_index( - self.parameters['nuclei_channel_name']) + compartmentIndex = self.dataSet \ + .get_data_organization() \ + .get_data_channel_index( + self.parameters['compartment_channel_name']) + + if self.parameters['membrane_channel_name'] == + self.parameters['compartment_channel_name']: + membraneFlag = 0 + else: + membraneFlag = 1 endTime = time.time() print(" image indexes read, ET {:.2f} min".format( (endTime - startTime) / 60)) - # read membrane (seed) and nuclei (watershed) images + # read membrane and compartment images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) - nucleiImages = self._read_image_stack(fragmentIndex, nucleiIndex) + compartmentImages = self._read_image_stack(fragmentIndex, + compartmentIndex) endTime = time.time() print(" images read, ET {:.2f} min".format( @@ -211,22 +218,22 @@ def _run_analysis(self, fragmentIndex): + str(membraneImages.shape[0]) + "," + str(membraneImages.shape[1]) + "," + str(membraneImages.shape[2]) + "]") - print(" nucleiImages Type: " + str(type(nucleiImages))) - print(" nucleiImages Size: [" - + str(nucleiImages.shape[0]) - + "," + str(nucleiImages.shape[1]) - + "," + str(nucleiImages.shape[2]) + "]") + print(" compartmentImages Type: " + str(type(compartmentImages))) + print(" compartmentImages Size: [" + + str(compartmentImages.shape[0]) + + "," + str(compartmentImages.shape[1]) + + "," + str(compartmentImages.shape[2]) + "]") # Prepare masks for cv2 watershed - watershedMarkers = watershed.get_cv2_watershed_markers(nucleiImages, - membraneImages) + watershedMarkers = watershed.get_cv2_watershed_markers( + compartmentImages, membraneImages) endTime = time.time() print(" markers calculated, ET {:.2f} min".format( (endTime - startTime) / 60)) # perform watershed in individual z positions - watershedOutput = watershed.apply_cv2_watershed(nucleiImages, + watershedOutput = watershed.apply_cv2_watershed(compartmentImages, watershedMarkers) endTime = time.time() From 825af8441fd63925d70238fb8024aab3792bdcab Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 22 Apr 2020 22:08:40 -0400 Subject: [PATCH 300/419] change nuclei to compartment --- merlin/analysis/segment.py | 4 ++- merlin/util/watershed.py | 56 ++++++++++++++++++++------------------ 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 8dee98f1..d80d8ec3 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -226,7 +226,9 @@ def _run_analysis(self, fragmentIndex): # Prepare masks for cv2 watershed watershedMarkers = watershed.get_cv2_watershed_markers( - compartmentImages, membraneImages) + compartmentImages, + membraneImages, + membraneFlag) endTime = time.time() print(" markers calculated, ET {:.2f} min".format( diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 60268345..16bf72bd 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -242,36 +242,39 @@ def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: return binary_fill_holes(nucleiMask) -def get_cv2_watershed_markers(nucleiImages: np.ndarray, - membraneImages: np.ndarray) -> np.ndarray: - """Combine membrane and nuclei markers into a single multilabel mask +def get_cv2_watershed_markers(compartmentImages: np.ndarray, + membraneImages: np.ndarray, + membraneFlag: int) -> np.ndarray: + """Combine membrane and compartment markers into a single multilabel mask for CV2 watershed Args: - nucleiImages: a 3 dimensional numpy array containing the images + compartmentImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). + membraneFlag: 0 if compartment and membrane images are the same, 1 + otherwise Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) of cv2-compatible watershed markers """ - nucleiMask = get_nuclei_mask(nucleiImages) + compartmentMask = get_nuclei_mask(compartmentImages) membraneMask = get_membrane_mask(membraneImages) - watershedMarker = np.zeros(nucleiMask.shape) + watershedMarker = np.zeros(compartmentMask.shape) - for z in range(nucleiImages.shape[0]): + for z in range(compartmentImages.shape[0]): # generate areas of sure bg and fg, as well as the area of # unknown classification - background = morphology.dilation(nucleiMask[z, :, :], + background = morphology.dilation(compartmentMask[z, :, :], morphology.selem.disk(15)) membraneDilated = morphology.dilation( membraneMask[z, :, :].astype('bool'), morphology.selem.disk(10)) - foreground = morphology.erosion(nucleiMask[z, :, :] * ~ + foreground = morphology.erosion(compartmentMask[z, :, :] * ~ membraneDilated, morphology.selem.disk(5)) unknown = background * ~ foreground @@ -322,12 +325,12 @@ def convert_grayscale_to_rgb(uint16Image: np.ndarray) -> np.ndarray: return rgbImage -def apply_cv2_watershed(nucleiImages: np.ndarray, +def apply_cv2_watershed(compartmentImages: np.ndarray, watershedMarkers: np.ndarray) -> np.ndarray: """Perform watershed using cv2 Args: - nucleiImages: a 3 dimensional numpy array containing the images + compartmentImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). watershedMarkers: a 3 dimensional numpy array containing the cv2 markers arranged as (z, x, y). @@ -339,7 +342,7 @@ def apply_cv2_watershed(nucleiImages: np.ndarray, watershedOutput = np.zeros(watershedMarkers.shape) for z in range(nucleiImages.shape[0]): - rgbImage = convert_grayscale_to_rgb(nucleiImages[z, :, :]) + rgbImage = convert_grayscale_to_rgb(compartmentImages[z, :, :]) watershedOutput[z, :, :] = cv2.watershed(rgbImage, watershedMarkers[z, :, :]. astype('int32')) @@ -348,7 +351,7 @@ def apply_cv2_watershed(nucleiImages: np.ndarray, return watershedOutput -def get_overlapping_nuclei(watershedZ0: np.ndarray, +def get_overlapping_objects(watershedZ0: np.ndarray, watershedZ1: np.ndarray, n0: int): """Perform watershed using cv2 @@ -357,26 +360,27 @@ def get_overlapping_nuclei(watershedZ0: np.ndarray, segmentation mask watershedZ1: a 2 dimensional numpy array containing a segmentation mask adjacent to watershedZ1 - n0: an integer with the index of the cell/nuclei to be compared - between the provided watershed segmentation masks + n0: an integer with the index of the object (cell/nuclei) + to be compared between the provided watershed + segmentation masks Returns: a tuple (n1, f0, f1) containing the label of the cell in Z1 overlapping n0 (n1), the fraction of n0 overlaping n1 (f0) and the fraction of n1 overlapping n0 (f1) """ - z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) - z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes > 100] + z1Indexes = np.unique(watershedZ1[watershedZ0 == n0]) + z1Indexes = z1Indexes[z1NucleiIndexes > 100] - if z1NucleiIndexes.shape[0] > 0: + if z1Indexes.shape[0] > 0: # calculate overlap fraction n0Area = np.count_nonzero(watershedZ0 == n0) - n1Area = np.zeros(len(z1NucleiIndexes)) - overlapArea = np.zeros(len(z1NucleiIndexes)) + n1Area = np.zeros(len(z1Indexes)) + overlapArea = np.zeros(len(z1Indexes)) - for ii in range(len(z1NucleiIndexes)): - n1 = z1NucleiIndexes[ii] + for ii in range(len(z1Indexes)): + n1 = z1Indexes[ii] n1Area[ii] = np.count_nonzero(watershedZ1 == n1) overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * (watershedZ1 == n1)) @@ -393,7 +397,7 @@ def get_overlapping_nuclei(watershedZ0: np.ndarray, if (n0OverlapFraction[indexSorted[0]] > 0.2 and n1OverlapFraction[indexSorted[0]] > 0.5): - return z1NucleiIndexes[indexSorted[0]], + return z1Indexes[indexSorted[0]], n0OverlapFraction[indexSorted[0]], n1OverlapFraction[indexSorted[0]] else: @@ -428,9 +432,9 @@ def combine_2d_segmentation_masks_into_3d(watershedOutput: np.unique(watershedOutput[z, :, :]) > 100] for n0 in zNucleiIndex: - n1, f0, f1 = get_overlapping_nuclei(watershedCombinedZ[z, :, :], - watershedOutput[z-1, :, :], - n0) + n1, f0, f1 = get_overlapping_objects(watershedCombinedZ[z, :, :], + watershedOutput[z-1, :, :], + n0) if n1: watershedCombinedZ[z-1, :, :][(watershedOutput[z-1, :, :] == n1)] = n0 From d4385ef9bf52c3290840c3edc314a2dabe242613 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 22 Apr 2020 22:10:53 -0400 Subject: [PATCH 301/419] added framework for MachineLearningSegment class --- merlin/analysis/segment.py | 147 +++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index d80d8ec3..3bf3e4c4 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -271,6 +271,153 @@ def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: return np.array([warpTask.get_aligned_image(fov, channelIndex, z) for z in range(len(self.dataSet.get_z_positions()))]) +class MachineLearningSegment(FeatureSavingAnalysisTask): + + """ + An analysis task that determines the boundaries of features in the + image data in each field of view using a watershed algorithm + implemented in CV2. + + A tutorial explaining the general scheme of the method can be + found in https://opencv-python-tutroals.readthedocs.io/en/latest/ + py_tutorials/py_imgproc/py_watershed/py_watershed.html. + + The watershed segmentation is performed in each z-position + independently and combined into 3D objects in a later step + + The class can be used to segment either nuclear or cytoplasmic + compartments. If both the compartment and membrane channels are the + same, the membrane channel is calculated from the edge transform of + the provided channel. + + Since each field of view is analyzed individually, the segmentation + results should be cleaned in order to merge cells that cross the + field of view boundary. + """ + + def __init__(self, dataSet, parameters=None, analysisName=None): + super().__init__(dataSet, parameters, analysisName) + + if 'membrane_channel_name' not in self.parameters: + self.parameters['membrane_channel_name'] = 'DAPI' + if 'compartment_channel_name' not in self.parameters: + self.parameters['compartment_channel_name'] = 'DAPI' + + def fragment_count(self): + return len(self.dataSet.get_fovs()) + + def get_estimated_memory(self): + # TODO - refine estimate + return 2048 + + def get_estimated_time(self): + # TODO - refine estimate + return 5 + + def get_dependencies(self): + return [self.parameters['warp_task'], + self.parameters['global_align_task']] + + def get_cell_boundaries(self) -> List[spatialfeature.SpatialFeature]: + featureDB = self.get_feature_database() + return featureDB.read_features() + + def _run_analysis(self, fragmentIndex): + startTime = time.time() + + print('Entered the _run_analysis method, FOV ' + str(fragmentIndex)) + + globalTask = self.dataSet.load_analysis_task( + self.parameters['global_align_task']) + + print(' globalTask loaded') + + # read membrane and compartment indexes + membraneIndex = self.dataSet \ + .get_data_organization() \ + .get_data_channel_index( + self.parameters['membrane_channel_name']) + compartmentIndex = self.dataSet \ + .get_data_organization() \ + .get_data_channel_index( + self.parameters['compartment_channel_name']) + + if self.parameters['membrane_channel_name'] == + self.parameters['compartment_channel_name']: + membraneFlag = 0 + else: + membraneFlag = 1 + + endTime = time.time() + print(" image indexes read, ET {:.2f} min".format( + (endTime - startTime) / 60)) + + # read membrane and compartment images + membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) + compartmentImages = self._read_image_stack(fragmentIndex, + compartmentIndex) + + endTime = time.time() + print(" images read, ET {:.2f} min".format( + (endTime - startTime) / 60)) + print(" membraneImages Type: " + str(type(membraneImages))) + print(" membraneImages Size: [" + + str(membraneImages.shape[0]) + + "," + str(membraneImages.shape[1]) + + "," + str(membraneImages.shape[2]) + "]") + print(" compartmentImages Type: " + str(type(compartmentImages))) + print(" compartmentImages Size: [" + + str(compartmentImages.shape[0]) + + "," + str(compartmentImages.shape[1]) + + "," + str(compartmentImages.shape[2]) + "]") + + # Prepare masks for cv2 watershed + watershedMarkers = watershed.get_cv2_watershed_markers( + compartmentImages, + membraneImages, + membraneFlag) + + endTime = time.time() + print(" markers calculated, ET {:.2f} min".format( + (endTime - startTime) / 60)) + + # perform watershed in individual z positions + watershedOutput = watershed.apply_cv2_watershed(compartmentImages, + watershedMarkers) + + endTime = time.time() + print(" watershed calculated, ET {:.2f} min".format( + (endTime - startTime) / 60)) + + # combine all z positions in watershed + watershedCombinedOutput = watershed \ + .combine_2d_segmentation_masks_into_3d(watershedOutput) + + endTime = time.time() + print(" watershed z positions combined, ET {:.2f} min".format( + (endTime - startTime) / 60)) + + # get features from mask. This is the slowestart (6 min for the + # previous part, 15+ for the rest, for a 7 frame Image. + zPos = np.array(self.dataSet.get_data_organization().get_z_positions()) + featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( + (watershedCombinedOutput == i), fragmentIndex, + globalTask.fov_to_global_transform(fragmentIndex), zPos) + for i in np.unique(watershedOutput) if i != 0] + + featureDB = self.get_feature_database() + featureDB.write_features(featureList, fragmentIndex) + + endTime = time.time() + print(" features written, ET {:.2f} min".format( + (endTime - startTime) / 60)) + + def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: + warpTask = self.dataSet.load_analysis_task( + self.parameters['warp_task']) + return np.array([warpTask.get_aligned_image(fov, channelIndex, z) + for z in range(len(self.dataSet.get_z_positions()))]) + class CleanCellBoundaries(analysistask.ParallelAnalysisTask): ''' From 760df79bc7530443068c8d573663d8d54402f472 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Apr 2020 12:02:57 -0400 Subject: [PATCH 302/419] adding mls utils function --- merlin/analysis/segment.py | 43 ++++++++++++++-------- merlin/util/machinelearningsegmentation.py | 11 ++++++ 2 files changed, 38 insertions(+), 16 deletions(-) create mode 100755 merlin/util/machinelearningsegmentation.py diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 3bf3e4c4..50338905 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -275,8 +275,15 @@ class MachineLearningSegment(FeatureSavingAnalysisTask): """ An analysis task that determines the boundaries of features in the - image data in each field of view using a watershed algorithm - implemented in CV2. + image data in each field of view using a the specified machine learning + method. The available methods are: + + unet: + + ilastik: + + cellpose: + A tutorial explaining the general scheme of the method can be found in https://opencv-python-tutroals.readthedocs.io/en/latest/ @@ -298,8 +305,8 @@ class MachineLearningSegment(FeatureSavingAnalysisTask): def __init__(self, dataSet, parameters=None, analysisName=None): super().__init__(dataSet, parameters, analysisName) - if 'membrane_channel_name' not in self.parameters: - self.parameters['membrane_channel_name'] = 'DAPI' + if 'method' not in self.parameters: + self.parameters['method'] = 'ilastik' if 'compartment_channel_name' not in self.parameters: self.parameters['compartment_channel_name'] = 'DAPI' @@ -326,6 +333,7 @@ def _run_analysis(self, fragmentIndex): startTime = time.time() print('Entered the _run_analysis method, FOV ' + str(fragmentIndex)) + print('Using ' + self.parameters['method'] + ' method.') globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) @@ -333,30 +341,32 @@ def _run_analysis(self, fragmentIndex): print(' globalTask loaded') # read membrane and compartment indexes - membraneIndex = self.dataSet \ - .get_data_organization() \ - .get_data_channel_index( - self.parameters['membrane_channel_name']) compartmentIndex = self.dataSet \ .get_data_organization() \ .get_data_channel_index( self.parameters['compartment_channel_name']) - if self.parameters['membrane_channel_name'] == - self.parameters['compartment_channel_name']: - membraneFlag = 0 - else: - membraneFlag = 1 - endTime = time.time() print(" image indexes read, ET {:.2f} min".format( (endTime - startTime) / 60)) - # read membrane and compartment images - membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) + # Read images and perform segmentation compartmentImages = self._read_image_stack(fragmentIndex, compartmentIndex) + endTime = time.time() + print(" images read, ET {:.2f} min".format( + (endTime - startTime) / 60)) + + segmentationOutput = machinelearningsegmentation. + apply_machine_learning_segmentation( + compartmentImages, + self.parameters['method']) + + endTime = time.time() + print(" Segmentation finished, ET {:.2f} min".format( + (endTime - startTime) / 60)) +""" endTime = time.time() print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) @@ -396,6 +406,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" watershed z positions combined, ET {:.2f} min".format( (endTime - startTime) / 60)) +""" # get features from mask. This is the slowestart (6 min for the # previous part, 15+ for the rest, for a 7 frame Image. diff --git a/merlin/util/machinelearningsegmentation.py b/merlin/util/machinelearningsegmentation.py new file mode 100755 index 00000000..f882a06e --- /dev/null +++ b/merlin/util/machinelearningsegmentation.py @@ -0,0 +1,11 @@ +import numpy as np +import cv2 +from scipy import ndimage +from scipy.ndimage.morphology import binary_fill_holes +from skimage import morphology +from skimage import filters +from skimage import measure +from pyclustering.cluster import kmedoids +from typing import Tuple + +from merlin.util import matlab \ No newline at end of file From ce01117af5ee93d01e239512611f778d93caa7ab Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Apr 2020 12:19:53 -0400 Subject: [PATCH 303/419] add function definitions to mls.py --- merlin/util/machinelearningsegmentation.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/merlin/util/machinelearningsegmentation.py b/merlin/util/machinelearningsegmentation.py index f882a06e..fbc05f0e 100755 --- a/merlin/util/machinelearningsegmentation.py +++ b/merlin/util/machinelearningsegmentation.py @@ -8,4 +8,19 @@ from pyclustering.cluster import kmedoids from typing import Tuple -from merlin.util import matlab \ No newline at end of file +from merlin.util import matlab + +""" +This module contains utility functions for preparing imagmes for performing +segmentation using machine learning approaches +""" + +def apply_machine_learning_segmentation(imageStackIn: np.ndarray, + method: str) -> np.ndarray: + +def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: + +def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: + +def segment_using_cellpose(imageStackIn: np.ndarray) -> np.ndarray: + From e8d1e2c23ab4391363d5845dbeafc97385771527 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Apr 2020 22:16:23 -0400 Subject: [PATCH 304/419] add body of apply_machine_learning_segmentation function --- merlin/analysis/segment.py | 16 +------------- merlin/util/machinelearningsegmentation.py | 25 +++++++++++++++++++--- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 50338905..29778cd5 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -284,22 +284,8 @@ class MachineLearningSegment(FeatureSavingAnalysisTask): cellpose: + TODO: ADD FLAT FIELD CORRECTION TASK - A tutorial explaining the general scheme of the method can be - found in https://opencv-python-tutroals.readthedocs.io/en/latest/ - py_tutorials/py_imgproc/py_watershed/py_watershed.html. - - The watershed segmentation is performed in each z-position - independently and combined into 3D objects in a later step - - The class can be used to segment either nuclear or cytoplasmic - compartments. If both the compartment and membrane channels are the - same, the membrane channel is calculated from the edge transform of - the provided channel. - - Since each field of view is analyzed individually, the segmentation - results should be cleaned in order to merge cells that cross the - field of view boundary. """ def __init__(self, dataSet, parameters=None, analysisName=None): diff --git a/merlin/util/machinelearningsegmentation.py b/merlin/util/machinelearningsegmentation.py index fbc05f0e..9a0076c4 100755 --- a/merlin/util/machinelearningsegmentation.py +++ b/merlin/util/machinelearningsegmentation.py @@ -13,14 +13,33 @@ """ This module contains utility functions for preparing imagmes for performing segmentation using machine learning approaches +MAYBE COMBINE WITH WATERSHED.PY INTO A SINGLE FILE, SEGMENTATION.PY """ +def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: + +def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: + +def segment_using_cellpose(imageStackIn: np.ndarray) -> np.ndarray: + def apply_machine_learning_segmentation(imageStackIn: np.ndarray, method: str) -> np.ndarray: + """Calculate binary mask with 1's in membrane pixels and 0 otherwise. + The images expected are some type of Nuclei label (e.g. DAPI) -def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: + Args: + membraneImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) + """ + if method == 'ilastik': + segmentOutput = segment_using_ilastik(imageStackIn) + elif method == 'cellpose': + segmentOutput = segment_using_cellpose(imageStackIn) + elif method == 'unet' + segmentOutput = segment_using_unet(imageStackIn) -def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: + return segmentOutput -def segment_using_cellpose(imageStackIn: np.ndarray) -> np.ndarray: From aa1d3d317e9e490ea436782215dfbccc2c801024 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Apr 2020 16:48:14 -0400 Subject: [PATCH 305/419] change nuclei to compartment in cv2 segmentation functions --- merlin/util/machinelearningsegmentation.py | 5 +- merlin/util/watershed.py | 55 +++++++++++----------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/merlin/util/machinelearningsegmentation.py b/merlin/util/machinelearningsegmentation.py index 9a0076c4..e11ab397 100755 --- a/merlin/util/machinelearningsegmentation.py +++ b/merlin/util/machinelearningsegmentation.py @@ -24,11 +24,10 @@ def segment_using_cellpose(imageStackIn: np.ndarray) -> np.ndarray: def apply_machine_learning_segmentation(imageStackIn: np.ndarray, method: str) -> np.ndarray: - """Calculate binary mask with 1's in membrane pixels and 0 otherwise. - The images expected are some type of Nuclei label (e.g. DAPI) + """Calculate Args: - membraneImages: a 3 dimensional numpy array containing the images + imageStackIn: a 3 dimensional numpy array containing the images arranged as (z, x, y). Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 16bf72bd..6e57d1e5 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -172,30 +172,31 @@ def get_membrane_mask(membraneImages: np.ndarray) -> np.ndarray: return mask -def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: - """Calculate binary mask with 1's in membrane pixels and 0 otherwise. - The images expected are some type of Nuclei label (e.g. DAPI) +def get_compartment_mask(compartmentImages: np.ndarray) -> np.ndarray: + """Calculate binary mask with 1's in compartment (nuclei or cytoplasm) + pixels and 0 otherwise. The images expected are some type of compartment + label (e.g. Nuclei: DAPI, Cytoplasm: PolyT, CD45, etc) Args: - membraneImages: a 3 dimensional numpy array containing the images + compartmentImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) """ - # generate nuclei mask based on thresholding - thresholdingMask = np.zeros(nucleiImages.shape) + # generate compartment mask based on thresholding + thresholdingMask = np.zeros(compartmentImages.shape) coarseBlockSize = 241 fineBlockSize = 61 - for z in range(nucleiImages.shape[0]): - coarseThresholdingMask = (nucleiImages[z, :, :] > + for z in range(compartmentImages.shape[0]): + coarseThresholdingMask = (compartmentImages[z, :, :] > filters.threshold_local( - nucleiImages[z, :, :], + compartmentImages[z, :, :], coarseBlockSize, offset=0)) - fineThresholdingMask = (nucleiImages[z, :, :] > + fineThresholdingMask = (compartmentImages[z, :, :] > filters.threshold_local( - nucleiImages[z, :, :], + compartmentImages[z, :, :], fineBlockSize, offset=0)) thresholdingMask[z, :, :] = (coarseThresholdingMask * @@ -205,15 +206,15 @@ def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: # generate border mask, necessary to avoid making a single # connected component when using binary_fill_holes below - borderMask = np.zeros((2048, 2048)) - borderMask[25:2023, 25:2023] = 1 + borderMask = np.zeros((compartmentImages.shape[1], + compartmentImages.shape[2])) + borderMask[25:(compartmentImages.shape[1]-25), + 25:(compartmentImages.shape[2]-25)] = 1 - # TODO - use the image size variable for borderMask - - # generate nuclei mask from hessian, fine - fineHessianMask = np.zeros(nucleiImages.shape) - for z in range(nucleiImages.shape[0]): - fineHessian = filters.hessian(nucleiImages[z, :, :]) + # generate compartment mask from hessian, fine + fineHessianMask = np.zeros(compartmentImages.shape) + for z in range(compartmentImages.shape[0]): + fineHessian = filters.hessian(compartmentImages[z, :, :]) fineHessianMask[z, :, :] = fineHessian == fineHessian.max() fineHessianMask[z, :, :] = morphology.binary_closing( fineHessianMask[z, :, :], @@ -222,12 +223,12 @@ def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: fineHessianMask[z, :, :] = binary_fill_holes( fineHessianMask[z, :, :]) - # generate dapi mask from hessian, coarse - coarseHessianMask = np.zeros(nucleiImages.shape) - for z in range(nucleiImages.shape[0]): - coarseHessian = filters.hessian(nucleiImages[z, :, :] - + # generate compartment mask from hessian, coarse + coarseHessianMask = np.zeros(comapartImages.shape) + for z in range(compartmentImages.shape[0]): + coarseHessian = filters.hessian(compartmentImages[z, :, :] - morphology.white_tophat( - nucleiImages[z, :, :], + compartmentImages[z, :, :], morphology.selem.disk(20))) coarseHessianMask[z, :, :] = coarseHessian == coarseHessian.max() coarseHessianMask[z, :, :] = morphology.binary_closing( @@ -238,8 +239,8 @@ def get_nuclei_mask(nucleiImages: np.ndarray) -> np.ndarray: coarseHessianMask[z, :, :]) # combine masks - nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask - return binary_fill_holes(nucleiMask) + compartmentMask = thresholdingMask + fineHessianMask + coarseHessianMask + return binary_fill_holes(compartmentMask) def get_cv2_watershed_markers(compartmentImages: np.ndarray, @@ -260,7 +261,7 @@ def get_cv2_watershed_markers(compartmentImages: np.ndarray, cv2-compatible watershed markers """ - compartmentMask = get_nuclei_mask(compartmentImages) + compartmentMask = get_compartment_mask(compartmentImages) membraneMask = get_membrane_mask(membraneImages) watershedMarker = np.zeros(compartmentMask.shape) From 427e74663c6c529245596645d95f3ec8d1ea3d2b Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Apr 2020 17:34:33 -0400 Subject: [PATCH 306/419] adding edge memebrane calculation to CV2 segmenetation --- merlin/analysis/segment.py | 8 +-- merlin/util/machinelearningsegmentation.py | 3 +- merlin/util/watershed.py | 78 ++++++++++++++++------ 3 files changed, 59 insertions(+), 30 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 29778cd5..13a7e495 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -195,12 +195,6 @@ def _run_analysis(self, fragmentIndex): .get_data_channel_index( self.parameters['compartment_channel_name']) - if self.parameters['membrane_channel_name'] == - self.parameters['compartment_channel_name']: - membraneFlag = 0 - else: - membraneFlag = 1 - endTime = time.time() print(" image indexes read, ET {:.2f} min".format( (endTime - startTime) / 60)) @@ -228,7 +222,7 @@ def _run_analysis(self, fragmentIndex): watershedMarkers = watershed.get_cv2_watershed_markers( compartmentImages, membraneImages, - membraneFlag) + self.parameters['membrane_channel_name']) endTime = time.time() print(" markers calculated, ET {:.2f} min".format( diff --git a/merlin/util/machinelearningsegmentation.py b/merlin/util/machinelearningsegmentation.py index e11ab397..085fa4df 100755 --- a/merlin/util/machinelearningsegmentation.py +++ b/merlin/util/machinelearningsegmentation.py @@ -24,8 +24,7 @@ def segment_using_cellpose(imageStackIn: np.ndarray) -> np.ndarray: def apply_machine_learning_segmentation(imageStackIn: np.ndarray, method: str) -> np.ndarray: - """Calculate - + """Select segmentation algorithm to use Args: imageStackIn: a 3 dimensional numpy array containing the images arranged as (z, x, y). diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 6e57d1e5..bdfd2d1b 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -5,6 +5,7 @@ from skimage import morphology from skimage import filters from skimage import measure +from skimage import feature from pyclustering.cluster import kmedoids from typing import Tuple @@ -142,33 +143,68 @@ def prepare_watershed_images(watershedImageStack: np.ndarray return normalizedWatershed, watershedMask -def get_membrane_mask(membraneImages: np.ndarray) -> np.ndarray: +def get_membrane_mask(membraneImages: np.ndarray, + membraneChannelName: str, + compartmentChannelName: str) -> np.ndarray: """Calculate binary mask with 1's in membrane pixels and 0 otherwise. The images expected are some type of membrane label (WGA, ConA, - Lamin, Cadherins) + Lamin, Cadherins) or compartment images (DAPI, CD45, polyT) Args: membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). + membraneChannelName: A string with the name of a membrane channel. + compartmentChannelName: A string with the name of the compartment + channel Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) """ mask = np.zeros(membraneImages.shape) - fineBlockSize = 61 - for z in range(membraneImages.shape[0]): - mask[z, :, :] = (membraneImages[z, :, :] > - filters.threshold_local(membraneImages[z, :, :], - fineBlockSize, - offset=0)) - mask[z, :, :] = morphology.remove_small_objects( - mask[z, :, :].astype('bool'), - min_size=100, - connectivity=1) - mask[z, :, :] = morphology.binary_closing(mask[z, :, :], - morphology.selem.disk(5)) - mask[z, :, :] = morphology.skeletonize(mask[z, :, :]) - - # combine masks + if membraneChannelName != compartmentChannelName: + fineBlockSize = 61 + for z in range(membraneImages.shape[0]): + mask[z, :, :] = (membraneImages[z, :, :] > + filters.threshold_local(membraneImages[z, :, :], + fineBlockSize, + offset=0)) + mask[z, :, :] = morphology.remove_small_objects( + mask[z, :, :].astype('bool'), + min_size=100, + connectivity=1) + mask[z, :, :] = morphology.binary_closing(mask[z, :, :], + morphology.selem.disk(5)) + mask[z, :, :] = morphology.skeletonize(mask[z, :, :]) + else: + filterSigma2 = 5 + filterSize2 = int(2*np.ceil(2*filterSigma2)+1) + edgeSigma = 2 #1 #2 + lowThresh = 0.1 #0.5 #0.2 + hiThresh = 0.5 #0.7 #0.6 + for z in range(membraneImages.shape[0]): + blurredImage = cv2.GaussianBlur(img[:,:,1], + (filterSize2,filterSize2), + filterSigma2) + edge0 = feature.canny(membraneImages[z, :, :], + sigma=edgeSigma, + use_quantiles=True, + low_threshold=lowThresh, + high_threshold=hiThresh) + edge0 = morphology.remove_small_objects( + mask[z, :, :].astype('bool'), + min_size=20, + connectivity=1) + edge1 = feature.canny(blurredImage, + sigma=edgeSigma, + use_quantiles=True, + low_threshold=lowThresh, + high_threshold=hiThresh) + edge1 = morphology.remove_small_objects( + mask[z, :, :].astype('bool'), + min_size=20, + connectivity=1) + + mask[z, :, :] = edge0 + edge1 + return mask @@ -262,7 +298,7 @@ def get_cv2_watershed_markers(compartmentImages: np.ndarray, """ compartmentMask = get_compartment_mask(compartmentImages) - membraneMask = get_membrane_mask(membraneImages) + membraneMask = get_membrane_mask(membraneImages, membraneFlag) watershedMarker = np.zeros(compartmentMask.shape) @@ -371,7 +407,7 @@ def get_overlapping_objects(watershedZ0: np.ndarray, """ z1Indexes = np.unique(watershedZ1[watershedZ0 == n0]) - z1Indexes = z1Indexes[z1NucleiIndexes > 100] + z1Indexes = z1Indexes[z1Indexes > 100] if z1Indexes.shape[0] > 0: @@ -429,10 +465,10 @@ def combine_2d_segmentation_masks_into_3d(watershedOutput: # starting far from coverslip for z in range(watershedOutput.shape[0]-1, 0, -1): - zNucleiIndex = np.unique(watershedOutput[z, :, :])[ + zIndex = np.unique(watershedOutput[z, :, :])[ np.unique(watershedOutput[z, :, :]) > 100] - for n0 in zNucleiIndex: + for n0 in zIndex: n1, f0, f1 = get_overlapping_objects(watershedCombinedZ[z, :, :], watershedOutput[z-1, :, :], n0) From 1af4881c6b86a40a806ee2d4d917ca5686636f3f Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sun, 26 Apr 2020 10:55:13 -0400 Subject: [PATCH 307/419] modify get_membrane_mask function --- merlin/util/watershed.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index bdfd2d1b..53b0c7cf 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -181,30 +181,27 @@ def get_membrane_mask(membraneImages: np.ndarray, lowThresh = 0.1 #0.5 #0.2 hiThresh = 0.5 #0.7 #0.6 for z in range(membraneImages.shape[0]): - blurredImage = cv2.GaussianBlur(img[:,:,1], + blurredImage = cv2.GaussianBlur(membraneImages[z, :, :], (filterSize2,filterSize2), filterSigma2) - edge0 = feature.canny(membraneImages[z, :, :], + edge0 = feature.canny(membraneImages[z, :, :], sigma=edgeSigma, use_quantiles=True, low_threshold=lowThresh, high_threshold=hiThresh) - edge0 = morphology.remove_small_objects( - mask[z, :, :].astype('bool'), - min_size=20, - connectivity=1) + edge0 = morphology.dilation(edge0,morphology.selem.disk(15)) + edge1 = feature.canny(blurredImage, sigma=edgeSigma, use_quantiles=True, low_threshold=lowThresh, high_threshold=hiThresh) - edge1 = morphology.remove_small_objects( - mask[z, :, :].astype('bool'), - min_size=20, - connectivity=1) + edge1 = morphology.dilation(edge1,morphology.selem.disk(15)) mask[z, :, :] = edge0 + edge1 - + + mask[z, :, :] = morphology.skeletonize(mask[z, :, :]) + return mask @@ -260,7 +257,7 @@ def get_compartment_mask(compartmentImages: np.ndarray) -> np.ndarray: fineHessianMask[z, :, :]) # generate compartment mask from hessian, coarse - coarseHessianMask = np.zeros(comapartImages.shape) + coarseHessianMask = np.zeros(compartmentImages.shape) for z in range(compartmentImages.shape[0]): coarseHessian = filters.hessian(compartmentImages[z, :, :] - morphology.white_tophat( From 7c0cb8ac6b21a0e81e0493f6594729fd0bee054a Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sun, 26 Apr 2020 17:41:25 -0400 Subject: [PATCH 308/419] change watershed.py to segmentation.py, put contents of mls.py into segmentation.py --- merlin/analysis/segment.py | 33 +- merlin/util/machinelearningsegmentation.py | 43 -- merlin/util/segmentation.py | 518 +++++++++++++++++++++ merlin/util/watershed.py | 4 +- 4 files changed, 538 insertions(+), 60 deletions(-) delete mode 100755 merlin/util/machinelearningsegmentation.py create mode 100755 merlin/util/segmentation.py diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 13a7e495..f0ab0b39 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -1,7 +1,7 @@ import cv2 import numpy as np from skimage import measure -from skimage import segmentation +from skimage import segmentation as skiseg from skimage import morphology from skimage import feature from skimage import filters @@ -13,7 +13,7 @@ from merlin.core import dataset from merlin.core import analysistask from merlin.util import spatialfeature -from merlin.util import watershed +from merlin.util import segmentation import pandas import networkx as nx import time @@ -94,13 +94,13 @@ def _run_analysis(self, fragmentIndex): .get_data_channel_index(self.parameters['watershed_channel_name']) watershedImages = self._read_and_filter_image_stack(fragmentIndex, watershedIndex, 5) - seeds = watershed.separate_merged_seeds( - watershed.extract_seeds(seedImages)) - normalizedWatershed, watershedMask = watershed.prepare_watershed_images( + seeds = segmentation.separate_merged_seeds( + segmentation.extract_seeds(seedImages)) + normalizedWatershed, watershedMask = segmentation.prepare_watershed_images( watershedImages) seeds[np.invert(watershedMask)] = 0 - watershedOutput = segmentation.watershed( + watershedOutput = skiseg.watershed( normalizedWatershed, measure.label(seeds), mask=watershedMask, connectivity=np.ones((3, 3, 3)), watershed_line=True) @@ -219,7 +219,7 @@ def _run_analysis(self, fragmentIndex): + "," + str(compartmentImages.shape[2]) + "]") # Prepare masks for cv2 watershed - watershedMarkers = watershed.get_cv2_watershed_markers( + watershedMarkers = segmentation.get_cv2_watershed_markers( compartmentImages, membraneImages, self.parameters['membrane_channel_name']) @@ -229,15 +229,15 @@ def _run_analysis(self, fragmentIndex): (endTime - startTime) / 60)) # perform watershed in individual z positions - watershedOutput = watershed.apply_cv2_watershed(compartmentImages, - watershedMarkers) + watershedOutput = segmentation.apply_cv2_watershed(compartmentImages, + watershedMarkers) endTime = time.time() print(" watershed calculated, ET {:.2f} min".format( (endTime - startTime) / 60)) # combine all z positions in watershed - watershedCombinedOutput = watershed \ + watershedCombinedOutput = segmentation \ .combine_2d_segmentation_masks_into_3d(watershedOutput) endTime = time.time() @@ -286,9 +286,12 @@ def __init__(self, dataSet, parameters=None, analysisName=None): super().__init__(dataSet, parameters, analysisName) if 'method' not in self.parameters: - self.parameters['method'] = 'ilastik' + self.parameters['method'] = 'cellpose' if 'compartment_channel_name' not in self.parameters: self.parameters['compartment_channel_name'] = 'DAPI' + if 'compartment_channel_type' not in self.parameters: + self.parameters['compartment_channel_type'] = 'cytoplasm' # nuclei + def fragment_count(self): return len(self.dataSet.get_fovs()) @@ -338,10 +341,10 @@ def _run_analysis(self, fragmentIndex): print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) - segmentationOutput = machinelearningsegmentation. - apply_machine_learning_segmentation( - compartmentImages, - self.parameters['method']) + segmentationOutput = segmentation.apply_machine_learning_segmentation( + compartmentImages, + self.parameters['method'], + self.parameters['compartment_channel_name']) endTime = time.time() print(" Segmentation finished, ET {:.2f} min".format( diff --git a/merlin/util/machinelearningsegmentation.py b/merlin/util/machinelearningsegmentation.py deleted file mode 100755 index 085fa4df..00000000 --- a/merlin/util/machinelearningsegmentation.py +++ /dev/null @@ -1,43 +0,0 @@ -import numpy as np -import cv2 -from scipy import ndimage -from scipy.ndimage.morphology import binary_fill_holes -from skimage import morphology -from skimage import filters -from skimage import measure -from pyclustering.cluster import kmedoids -from typing import Tuple - -from merlin.util import matlab - -""" -This module contains utility functions for preparing imagmes for performing -segmentation using machine learning approaches -MAYBE COMBINE WITH WATERSHED.PY INTO A SINGLE FILE, SEGMENTATION.PY -""" -def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: - -def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: - -def segment_using_cellpose(imageStackIn: np.ndarray) -> np.ndarray: - - -def apply_machine_learning_segmentation(imageStackIn: np.ndarray, - method: str) -> np.ndarray: - """Select segmentation algorithm to use - Args: - imageStackIn: a 3 dimensional numpy array containing the images - arranged as (z, x, y). - Returns: - ndarray containing a 3 dimensional mask arranged as (z, x, y) - """ - if method == 'ilastik': - segmentOutput = segment_using_ilastik(imageStackIn) - elif method == 'cellpose': - segmentOutput = segment_using_cellpose(imageStackIn) - elif method == 'unet' - segmentOutput = segment_using_unet(imageStackIn) - - return segmentOutput - - diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py new file mode 100755 index 00000000..bf8d5d1c --- /dev/null +++ b/merlin/util/segmentation.py @@ -0,0 +1,518 @@ +import numpy as np +import cv2 +from scipy import ndimage +from scipy.ndimage.morphology import binary_fill_holes +from skimage import morphology +from skimage import filters +from skimage import measure +from skimage import feature +from pyclustering.cluster import kmedoids +from typing import Tuple + +from merlin.util import matlab + +from cellpose import models + + +""" +This module contains utility functions for preparing images for +watershed segmentation, as well as functions to perform segmentation +using macnine learning approaches +""" + +# To match Matlab's strel('disk', 20) +diskStruct = morphology.diamond(28)[9:48, 9:48] + + +def extract_seeds(seedImageStackIn: np.ndarray) -> np.ndarray: + """Determine seed positions from the input images. + + The initial seeds are determined by finding the regional intensity maximums + after erosion and filtering with an adaptive threshold. These initial + seeds are then expanded by dilation. + + Args: + seedImageStackIn: a 3 dimensional numpy array arranged as (z,x,y) + Returns: a boolean numpy array with the same dimensions as seedImageStackIn + where a given (z,x,y) coordinate is True if it corresponds to a seed + position and false otherwise. + """ + seedImages = seedImageStackIn.copy() + + seedImages = ndimage.grey_erosion( + seedImages, + footprint=ndimage.morphology.generate_binary_structure(3, 1)) + seedImages = np.array([cv2.erode(x, diskStruct, + borderType=cv2.BORDER_REFLECT) + for x in seedImages]) + + thresholdFilterSize = int(2 * np.floor(seedImages.shape[1] / 16) + 1) + seedMask = np.array([x < 1.1 * filters.threshold_local( + x, thresholdFilterSize, method='mean', mode='nearest') + for x in seedImages]) + + seedImages[seedMask] = 0 + + seeds = morphology.local_maxima(seedImages, allow_borders=True) + + seeds = ndimage.morphology.binary_dilation( + seeds, structure=ndimage.morphology.generate_binary_structure(3, 1)) + seeds = np.array([ndimage.morphology.binary_dilation( + x, structure=morphology.diamond(28)[9:48, 9:48]) for x in seeds]) + + return seeds + + +def separate_merged_seeds(seedsIn: np.ndarray) -> np.ndarray: + """Separate seeds that are merged in 3 dimensions but are separated + in some 2 dimensional slices. + + Args: + seedsIn: a 3 dimensional binary numpy array arranged as (z,x,y) where + True indicates the pixel corresponds with a seed. + Returns: a 3 dimensional binary numpy array of the same size as seedsIn + indicating the positions of seeds after processing. + """ + + def create_region_image(shape, c): + region = np.zeros(shape) + for x in c.coords: + region[x[0], x[1], x[2]] = 1 + return region + + components = measure.regionprops(measure.label(seedsIn)) + seeds = np.zeros(seedsIn.shape) + for c in components: + seedImage = create_region_image(seeds.shape, c) + localProps = [measure.regionprops(measure.label(x)) for x in seedImage] + seedCounts = [len(x) for x in localProps] + + if all([x < 2 for x in seedCounts]): + goodFrames = [i for i, x in enumerate(seedCounts) if x == 1] + goodProperties = [y for x in goodFrames for y in localProps[x]] + seedPositions = np.round([np.median( + [x.centroid for x in goodProperties], axis=0)]).astype(int) + else: + goodFrames = [i for i, x in enumerate(seedCounts) if x > 1] + goodProperties = [y for x in goodFrames for y in localProps[x]] + goodCentroids = [x.centroid for x in goodProperties] + km = kmedoids.kmedoids( + goodCentroids, + np.random.choice(np.arange(len(goodCentroids)), + size=np.max(seedCounts))) + km.process() + seedPositions = np.round( + [goodCentroids[x] for x in km.get_medoids()]).astype(int) + + for s in seedPositions: + for f in goodFrames: + seeds[f, s[0], s[1]] = 1 + + seeds = ndimage.morphology.binary_dilation( + seeds, structure=ndimage.morphology.generate_binary_structure(3, 1)) + seeds = np.array([ndimage.morphology.binary_dilation( + x, structure=diskStruct) for x in seeds]) + + return seeds + + +def prepare_watershed_images(watershedImageStack: np.ndarray + ) -> Tuple[np.ndarray, np.ndarray]: + """Prepare the given images as the input image for watershedding. + + A watershed mask is determined using an adaptive threshold and the watershed + images are inverted so the largest values in the watershed images become + minima and then the image stack is normalized to have values between 0 + and 1. + + Args: + watershedImageStack: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + Returns: a tuple containing the normalized watershed images and the + calculated watershed mask + """ + filterSize = int(2 * np.floor(watershedImageStack.shape[1] / 16) + 1) + + watershedMask = np.array([ndimage.morphology.binary_fill_holes( + x > 1.1 * filters.threshold_local(x, filterSize, method='mean', + mode='nearest')) + for x in watershedImageStack]) + + normalizedWatershed = 1 - (watershedImageStack + - np.min(watershedImageStack)) / \ + (np.max(watershedImageStack) + - np.min(watershedImageStack)) + normalizedWatershed[np.invert(watershedMask)] = 1 + + return normalizedWatershed, watershedMask + + +def get_membrane_mask(membraneImages: np.ndarray, + membraneChannelName: str, + compartmentChannelName: str) -> np.ndarray: + """Calculate binary mask with 1's in membrane pixels and 0 otherwise. + The images expected are some type of membrane label (WGA, ConA, + Lamin, Cadherins) or compartment images (DAPI, CD45, polyT) + + Args: + membraneImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + membraneChannelName: A string with the name of a membrane channel. + compartmentChannelName: A string with the name of the compartment + channel + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) + """ + mask = np.zeros(membraneImages.shape) + if membraneChannelName != compartmentChannelName: + fineBlockSize = 61 + for z in range(membraneImages.shape[0]): + mask[z, :, :] = (membraneImages[z, :, :] > + filters.threshold_local(membraneImages[z, :, :], + fineBlockSize, + offset=0)) + mask[z, :, :] = morphology.remove_small_objects( + mask[z, :, :].astype('bool'), + min_size=100, + connectivity=1) + mask[z, :, :] = morphology.binary_closing(mask[z, :, :], + morphology.selem.disk(5)) + mask[z, :, :] = morphology.skeletonize(mask[z, :, :]) + else: + filterSigma2 = 5 + filterSize2 = int(2*np.ceil(2*filterSigma2)+1) + edgeSigma = 2 #1 #2 + lowThresh = 0.1 #0.5 #0.2 + hiThresh = 0.5 #0.7 #0.6 + for z in range(membraneImages.shape[0]): + blurredImage = cv2.GaussianBlur(membraneImages[z, :, :], + (filterSize2,filterSize2), + filterSigma2) + edge0 = feature.canny(membraneImages[z, :, :], + sigma=edgeSigma, + use_quantiles=True, + low_threshold=lowThresh, + high_threshold=hiThresh) + edge0 = morphology.dilation(edge0,morphology.selem.disk(10)) + + edge1 = feature.canny(blurredImage, + sigma=edgeSigma, + use_quantiles=True, + low_threshold=lowThresh, + high_threshold=hiThresh) + edge1 = morphology.dilation(edge1,morphology.selem.disk(10)) + + mask[z, :, :] = edge0 + edge1 + + mask[z, :, :] = morphology.skeletonize(mask[z, :, :]) + + return mask + + +def get_compartment_mask(compartmentImages: np.ndarray) -> np.ndarray: + """Calculate binary mask with 1's in compartment (nuclei or cytoplasm) + pixels and 0 otherwise. The images expected are some type of compartment + label (e.g. Nuclei: DAPI, Cytoplasm: PolyT, CD45, etc) + + Args: + compartmentImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) + """ + + # generate compartment mask based on thresholding + thresholdingMask = np.zeros(compartmentImages.shape) + coarseBlockSize = 241 + fineBlockSize = 61 + for z in range(compartmentImages.shape[0]): + coarseThresholdingMask = (compartmentImages[z, :, :] > + filters.threshold_local( + compartmentImages[z, :, :], + coarseBlockSize, + offset=0)) + fineThresholdingMask = (compartmentImages[z, :, :] > + filters.threshold_local( + compartmentImages[z, :, :], + fineBlockSize, + offset=0)) + thresholdingMask[z, :, :] = (coarseThresholdingMask * + fineThresholdingMask) + thresholdingMask[z, :, :] = binary_fill_holes( + thresholdingMask[z, :, :]) + + # generate border mask, necessary to avoid making a single + # connected component when using binary_fill_holes below + borderMask = np.zeros((compartmentImages.shape[1], + compartmentImages.shape[2])) + borderMask[25:(compartmentImages.shape[1]-25), + 25:(compartmentImages.shape[2]-25)] = 1 + + # generate compartment mask from hessian, fine + fineHessianMask = np.zeros(compartmentImages.shape) + for z in range(compartmentImages.shape[0]): + fineHessian = filters.hessian(compartmentImages[z, :, :]) + fineHessianMask[z, :, :] = fineHessian == fineHessian.max() + fineHessianMask[z, :, :] = morphology.binary_closing( + fineHessianMask[z, :, :], + morphology.selem.disk(5)) + fineHessianMask[z, :, :] = fineHessianMask[z, :, :] * borderMask + fineHessianMask[z, :, :] = binary_fill_holes( + fineHessianMask[z, :, :]) + + # generate compartment mask from hessian, coarse + coarseHessianMask = np.zeros(compartmentImages.shape) + for z in range(compartmentImages.shape[0]): + coarseHessian = filters.hessian(compartmentImages[z, :, :] - + morphology.white_tophat( + compartmentImages[z, :, :], + morphology.selem.disk(20))) + coarseHessianMask[z, :, :] = coarseHessian == coarseHessian.max() + coarseHessianMask[z, :, :] = morphology.binary_closing( + coarseHessianMask[z, :, :], morphology.selem.disk(5)) + coarseHessianMask[z, :, :] = (coarseHessianMask[z, :, :] * + borderMask) + coarseHessianMask[z, :, :] = binary_fill_holes( + coarseHessianMask[z, :, :]) + + # combine masks + compartmentMask = thresholdingMask + fineHessianMask + coarseHessianMask + return binary_fill_holes(compartmentMask) + + +def get_cv2_watershed_markers(compartmentImages: np.ndarray, + membraneImages: np.ndarray, + membraneFlag: int) -> np.ndarray: + """Combine membrane and compartment markers into a single multilabel mask + for CV2 watershed + + Args: + compartmentImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + membraneImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + membraneFlag: 0 if compartment and membrane images are the same, 1 + otherwise + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) of + cv2-compatible watershed markers + """ + + compartmentMask = get_compartment_mask(compartmentImages) + membraneMask = get_membrane_mask(membraneImages, membraneFlag) + + watershedMarker = np.zeros(compartmentMask.shape) + + for z in range(compartmentImages.shape[0]): + + # generate areas of sure bg and fg, as well as the area of + # unknown classification + background = morphology.dilation(compartmentMask[z, :, :], + morphology.selem.disk(15)) + membraneDilated = morphology.dilation( + membraneMask[z, :, :].astype('bool'), + morphology.selem.disk(10)) + foreground = morphology.erosion(compartmentMask[z, :, :] * ~ + membraneDilated, + morphology.selem.disk(5)) + unknown = background * ~ foreground + + background = np.uint8(background) * 255 + foreground = np.uint8(foreground) * 255 + unknown = np.uint8(unknown) * 255 + + # Marker labelling + ret, markers = cv2.connectedComponents(foreground) + + # Add one to all labels so that sure background is not 0, but 1 + markers = markers + 100 + + # Now, mark the region of unknown with zero + markers[unknown == 255] = 0 + + watershedMarker[z, :, :] = markers + + return watershedMarker + + +def convert_grayscale_to_rgb(uint16Image: np.ndarray) -> np.ndarray: + """Convert a 16 bit 2D grayscale image into a 3D 8-bit RGB image. + cv2 only works in 8-bit. Based on https://stackoverflow.com/questions/ + 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv3D + + Args: + uint16Image: a 2 dimensional numpy array containing the 16-bit + image + Returns: + ndarray containing a 3 dimensional 8-bit image stack + """ + + # invert image + uint16Image = 2**16 - uint16Image + + # convert to uint8 + ratio = np.amax(uint16Image) / 256 + uint8Image = (uint16Image / ratio).astype('uint8') + + rgbImage = np.zeros((2048, 2048, 3)) + rgbImage[:, :, 0] = uint8Image + rgbImage[:, :, 1] = uint8Image + rgbImage[:, :, 2] = uint8Image + rgbImage = rgbImage.astype('uint8') + + return rgbImage + + +def apply_cv2_watershed(compartmentImages: np.ndarray, + watershedMarkers: np.ndarray) -> np.ndarray: + """Perform watershed using cv2 + + Args: + compartmentImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + watershedMarkers: a 3 dimensional numpy array containing the cv2 + markers arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) of + segmented cells. masks in different z positions are + independent + """ + + watershedOutput = np.zeros(watershedMarkers.shape) + for z in range(nucleiImages.shape[0]): + rgbImage = convert_grayscale_to_rgb(compartmentImages[z, :, :]) + watershedOutput[z, :, :] = cv2.watershed(rgbImage, + watershedMarkers[z, :, :]. + astype('int32')) + watershedOutput[z, :, :][watershedOutput[z, :, :] <= 100] = 0 + + return watershedOutput + + +def get_overlapping_objects(watershedZ0: np.ndarray, + watershedZ1: np.ndarray, n0: int): + """Perform watershed using cv2 + + Args: + watershedZ0: a 2 dimensional numpy array containing a + segmentation mask + watershedZ1: a 2 dimensional numpy array containing a + segmentation mask adjacent to watershedZ1 + n0: an integer with the index of the object (cell/nuclei) + to be compared between the provided watershed + segmentation masks + Returns: + a tuple (n1, f0, f1) containing the label of the cell in Z1 + overlapping n0 (n1), the fraction of n0 overlaping n1 (f0) and + the fraction of n1 overlapping n0 (f1) + """ + + z1Indexes = np.unique(watershedZ1[watershedZ0 == n0]) + z1Indexes = z1Indexes[z1Indexes > 100] + + if z1Indexes.shape[0] > 0: + + # calculate overlap fraction + n0Area = np.count_nonzero(watershedZ0 == n0) + n1Area = np.zeros(len(z1Indexes)) + overlapArea = np.zeros(len(z1Indexes)) + + for ii in range(len(z1Indexes)): + n1 = z1Indexes[ii] + n1Area[ii] = np.count_nonzero(watershedZ1 == n1) + overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * + (watershedZ1 == n1)) + + n0OverlapFraction = np.asarray(overlapArea / n0Area) + n1OverlapFraction = np.asarray(overlapArea / n1Area) + index = list(range(len(n0OverlapFraction))) + + # select the nuclei that has the highest fraction in n0 and n1 + r1, r2, indexSorted = zip(*sorted(zip(n0OverlapFraction, + n1OverlapFraction, + index), + reverse=True)) + + if (n0OverlapFraction[indexSorted[0]] > 0.2 and + n1OverlapFraction[indexSorted[0]] > 0.5): + return z1Indexes[indexSorted[0]], + n0OverlapFraction[indexSorted[0]], + n1OverlapFraction[indexSorted[0]] + else: + return False, False, False + else: + return False, False, False + + +def combine_2d_segmentation_masks_into_3d(watershedOutput: + np.ndarray) -> np.ndarray: + """Take a 3 dimensional watershed masks and relabel them so that + nuclei in adjacent sections have the same label if the area their + overlap surpases certain threshold + + Args: + watershedOutput: a 3 dimensional numpy array containing the + segmentation masks arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) of + relabeled segmented cells + """ + + # Initialize empty array with size as watershedOutput array + watershedCombinedZ = np.zeros(watershedOutput.shape) + + # copy the mask of the section farthest to the coverslip + watershedCombinedZ[-1, :, :] = watershedOutput[-1, :, :] + + # starting far from coverslip + for z in range(watershedOutput.shape[0]-1, 0, -1): + zIndex = np.unique(watershedOutput[z, :, :])[ + np.unique(watershedOutput[z, :, :]) > 100] + + for n0 in zIndex: + n1, f0, f1 = get_overlapping_objects(watershedCombinedZ[z, :, :], + watershedOutput[z-1, :, :], + n0) + if n1: + watershedCombinedZ[z-1, :, :][(watershedOutput[z-1, :, :] == + n1)] = n0 + return watershedCombinedZ + +def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: + +def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: + +def segment_using_cellpose(imageStackIn: np.ndarray, + channelName: str) -> np.ndarray: + """perform segmentation using cellpose + Args: + imageStackIn: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + channelName: a string with the channel name + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) + """ + + # DEFINE CELLPOSE MODEL + # model_type='cyto' or model_type='nuclei' + if channelName == + model = models.Cellpose(gpu=False, model_type='cyto') + +def apply_machine_learning_segmentation(imageStackIn: np.ndarray, + method: str, + channelName: str) -> np.ndarray: + """Select segmentation algorithm to use + Args: + imageStackIn: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) + """ + if method == 'ilastik': + segmentOutput = segment_using_ilastik(imageStackIn) + elif method == 'cellpose': + segmentOutput = segment_using_cellpose(imageStackIn, channelName) + elif method == 'unet' + segmentOutput = segment_using_unet(imageStackIn) + + return segmentOutput diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 53b0c7cf..f41a404b 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -189,14 +189,14 @@ def get_membrane_mask(membraneImages: np.ndarray, use_quantiles=True, low_threshold=lowThresh, high_threshold=hiThresh) - edge0 = morphology.dilation(edge0,morphology.selem.disk(15)) + edge0 = morphology.dilation(edge0,morphology.selem.disk(10)) edge1 = feature.canny(blurredImage, sigma=edgeSigma, use_quantiles=True, low_threshold=lowThresh, high_threshold=hiThresh) - edge1 = morphology.dilation(edge1,morphology.selem.disk(15)) + edge1 = morphology.dilation(edge1,morphology.selem.disk(10)) mask[z, :, :] = edge0 + edge1 From 80270d6a6ef2186934ef7b6baab800edf5a62c98 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 6 May 2020 10:48:45 -0400 Subject: [PATCH 309/419] consolidate functions into segment_using_cellpose --- merlin/analysis/segment.py | 3 --- merlin/util/segmentation.py | 48 ++++++++++++++++++++++++++++++++----- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index f0ab0b39..7626e5c6 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -289,9 +289,6 @@ def __init__(self, dataSet, parameters=None, analysisName=None): self.parameters['method'] = 'cellpose' if 'compartment_channel_name' not in self.parameters: self.parameters['compartment_channel_name'] = 'DAPI' - if 'compartment_channel_type' not in self.parameters: - self.parameters['compartment_channel_type'] = 'cytoplasm' # nuclei - def fragment_count(self): return len(self.dataSet.get_fovs()) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index bf8d5d1c..e1970b56 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -479,12 +479,16 @@ def combine_2d_segmentation_masks_into_3d(watershedOutput: return watershedCombinedZ def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: + return None def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: + return None def segment_using_cellpose(imageStackIn: np.ndarray, channelName: str) -> np.ndarray: - """perform segmentation using cellpose + """Perform segmentation using cellpose. Code adapted from + https://nbviewer.jupyter.org/github/MouseLand/cellpose/blob/ + master/notebooks/run_cellpose.ipynb Args: imageStackIn: a 3 dimensional numpy array containing the images arranged as (z, x, y). @@ -492,11 +496,43 @@ def segment_using_cellpose(imageStackIn: np.ndarray, Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) """ + channelName = channelName.lower() + + # Define cellpose model + if any([channelName == 'dapi', channelName == 'lamin']): + model = models.Cellpose(gpu=False, model_type='nuclei') + if any([channelName == 'polyt', channelName == 'polya', + channelName == 'ecadherin',channelName == 'cd45', + channelName == 'wga', channelName == 'cona']): + model = models.Cellpose(gpu=False, model_type='cyto') + + # define CHANNELS to run segementation on + # grayscale=0, R=1, G=2, B=3 + # channels = [cytoplasm, nucleus] + # if NUCLEUS channel does not exist, set the second channel to 0 + # channels = [0,0] + # IF ALL YOUR IMAGES ARE THE SAME TYPE, you can give a list with 2 elements + channels = [0,0] # IF YOU HAVE GRAYSCALE + # channels = [2,3] # IF YOU HAVE G=cytoplasm and B=nucleus + # channels = [2,1] # IF YOU HAVE G=cytoplasm and R=nucleus + + # or if you have different types of channels in each image + # channels = [[0,0],[0,0]] + + # if diameter is set to None, the size of the cells is estimated on a per + # image basis you can set the average cell `diameter` in pixels yourself + # (recommended) diameter can be a list or a single number for all images + + # put list of images in cellpose format + imageList = np.split(imageStackIn,imageStackIn.shape[0]) + + masks, flows, styles, diams = model.eval(imageList, diameter=None, + channels=channels) + # combine masks into array + masksArray = np.stack(masks) + + return masksArray - # DEFINE CELLPOSE MODEL - # model_type='cyto' or model_type='nuclei' - if channelName == - model = models.Cellpose(gpu=False, model_type='cyto') def apply_machine_learning_segmentation(imageStackIn: np.ndarray, method: str, @@ -512,7 +548,7 @@ def apply_machine_learning_segmentation(imageStackIn: np.ndarray, segmentOutput = segment_using_ilastik(imageStackIn) elif method == 'cellpose': segmentOutput = segment_using_cellpose(imageStackIn, channelName) - elif method == 'unet' + elif method == 'unet': segmentOutput = segment_using_unet(imageStackIn) return segmentOutput From 7db8f4eaddf3ecd3fe6a28eb40c6ffc0c658e984 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 18 Jun 2020 11:27:31 -0400 Subject: [PATCH 310/419] add diameter param to segment_using_cellpose --- merlin/util/segmentation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index e1970b56..dd943425 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -485,7 +485,8 @@ def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: return None def segment_using_cellpose(imageStackIn: np.ndarray, - channelName: str) -> np.ndarray: + channelName: str, + diameter: np.int) -> np.ndarray: """Perform segmentation using cellpose. Code adapted from https://nbviewer.jupyter.org/github/MouseLand/cellpose/blob/ master/notebooks/run_cellpose.ipynb From 92d04e9528beb3bb7064cbf801801cca745e641c Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 18 Jun 2020 11:28:30 -0400 Subject: [PATCH 311/419] add diameter param to segment_using_cellpose --- merlin/util/segmentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index dd943425..1f316848 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -527,7 +527,7 @@ def segment_using_cellpose(imageStackIn: np.ndarray, # put list of images in cellpose format imageList = np.split(imageStackIn,imageStackIn.shape[0]) - masks, flows, styles, diams = model.eval(imageList, diameter=None, + masks, flows, styles, diams = model.eval(imageList, diameter=diameter, channels=channels) # combine masks into array masksArray = np.stack(masks) From e33da73b91ff9433c8b4cc87b7bdc8a3cc1f8b8e Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 18 Jun 2020 12:23:26 -0400 Subject: [PATCH 312/419] put segmentation params into dict --- merlin/analysis/segment.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 7626e5c6..c42df126 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -287,6 +287,8 @@ def __init__(self, dataSet, parameters=None, analysisName=None): if 'method' not in self.parameters: self.parameters['method'] = 'cellpose' + if 'diameter' not in self.parameters: + self.parameters['diameter'] = 50 if 'compartment_channel_name' not in self.parameters: self.parameters['compartment_channel_name'] = 'DAPI' @@ -338,10 +340,15 @@ def _run_analysis(self, fragmentIndex): print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) + if self.parameters['method'] == 'cellpose': + segParameters = dict({ + 'method':'cellpose', + 'diameter':self.parameters['diameter'], + 'channel':self.parameters['compartment_channel_name'] + }) + segmentationOutput = segmentation.apply_machine_learning_segmentation( - compartmentImages, - self.parameters['method'], - self.parameters['compartment_channel_name']) + compartmentImages,segParameters) endTime = time.time() print(" Segmentation finished, ET {:.2f} min".format( From b81c87706f800a32dafb1b1b192504546f85984f Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 19 Jun 2020 08:23:26 -0400 Subject: [PATCH 313/419] add parameters as dict --- merlin/analysis/segment.py | 2 +- merlin/util/segmentation.py | 43 +++++++++++++++++++++---------------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index c42df126..57236425 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -348,7 +348,7 @@ def _run_analysis(self, fragmentIndex): }) segmentationOutput = segmentation.apply_machine_learning_segmentation( - compartmentImages,segParameters) + compartmentImages, segParameters) endTime = time.time() print(" Segmentation finished, ET {:.2f} min".format( diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 1f316848..b7c1bd4d 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -485,26 +485,29 @@ def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: return None def segment_using_cellpose(imageStackIn: np.ndarray, - channelName: str, - diameter: np.int) -> np.ndarray: + params: dict) -> np.ndarray: """Perform segmentation using cellpose. Code adapted from https://nbviewer.jupyter.org/github/MouseLand/cellpose/blob/ master/notebooks/run_cellpose.ipynb Args: imageStackIn: a 3 dimensional numpy array containing the images arranged as (z, x, y). - channelName: a string with the channel name + params: a dictionary with the parameters for segmentation Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) """ - channelName = channelName.lower() + channelName = params['channel'].lower() # Define cellpose model - if any([channelName == 'dapi', channelName == 'lamin']): + if any([channelName == 'dapi', + channelName == 'lamin']): model = models.Cellpose(gpu=False, model_type='nuclei') - if any([channelName == 'polyt', channelName == 'polya', - channelName == 'ecadherin',channelName == 'cd45', - channelName == 'wga', channelName == 'cona']): + if any([channelName == 'polyt', + channelName == 'polya', + channelName == 'ecadherin', + channelName == 'cd45', + channelName == 'wga', + channelName == 'cona']): model = models.Cellpose(gpu=False, model_type='cyto') # define CHANNELS to run segementation on @@ -527,7 +530,8 @@ def segment_using_cellpose(imageStackIn: np.ndarray, # put list of images in cellpose format imageList = np.split(imageStackIn,imageStackIn.shape[0]) - masks, flows, styles, diams = model.eval(imageList, diameter=diameter, + masks, flows, styles, diams = model.eval(imageList, + diameter=params['diameter'], channels=channels) # combine masks into array masksArray = np.stack(masks) @@ -535,21 +539,24 @@ def segment_using_cellpose(imageStackIn: np.ndarray, return masksArray -def apply_machine_learning_segmentation(imageStackIn: np.ndarray, - method: str, - channelName: str) -> np.ndarray: +def apply_machine_learning_segmentation(imageStackIn: np.ndarray, + params: dict) -> np.ndarray: """Select segmentation algorithm to use Args: imageStackIn: a 3 dimensional numpy array containing the images arranged as (z, x, y). + params: dictionary with key:value pairs with parameters to be passed + to the segmentation code. Keys used are 'method', 'diameter', + 'channel' + Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) """ - if method == 'ilastik': - segmentOutput = segment_using_ilastik(imageStackIn) - elif method == 'cellpose': - segmentOutput = segment_using_cellpose(imageStackIn, channelName) - elif method == 'unet': - segmentOutput = segment_using_unet(imageStackIn) + if params['method'] == 'ilastik': + segmentOutput = segment_using_ilastik(imageStackIn, params) + elif params['method'] == 'cellpose': + segmentOutput = segment_using_cellpose(imageStackIn, params) + elif params['method'] == 'unet': + segmentOutput = segment_using_unet(imageStackIn, params) return segmentOutput From 479d9dbde3a12f595bbe16acb742eb4282b5fb34 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 19 Jun 2020 09:31:49 -0400 Subject: [PATCH 314/419] change indentation --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 57236425..fe479331 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -353,7 +353,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" Segmentation finished, ET {:.2f} min".format( (endTime - startTime) / 60)) -""" + """ endTime = time.time() print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) @@ -393,7 +393,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" watershed z positions combined, ET {:.2f} min".format( (endTime - startTime) / 60)) -""" + """ # get features from mask. This is the slowestart (6 min for the # previous part, 15+ for the rest, for a 7 frame Image. From a87d601eac4884735ba823fff796bfe0020fdd52 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 19 Jun 2020 13:19:11 -0400 Subject: [PATCH 315/419] return watershed class name to previous state --- merlin/analysis/segment.py | 2 +- merlin/core/dataset.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index fe479331..0977614b 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -124,7 +124,7 @@ def _read_and_filter_image_stack(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions()))]) -class WatershedSegmentCV2(FeatureSavingAnalysisTask): +class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): """ An analysis task that determines the boundaries of features in the diff --git a/merlin/core/dataset.py b/merlin/core/dataset.py index bc120af6..1a411619 100755 --- a/merlin/core/dataset.py +++ b/merlin/core/dataset.py @@ -616,7 +616,7 @@ def load_analysis_task(self, analysisTaskName: str) \ -> analysistask.AnalysisTask: loadName = os.sep.join([self.get_task_subdirectory( analysisTaskName), 'task.json']) - + print(loadName) with open(loadName, 'r') as inFile: parameters = json.load(inFile) analysisModule = importlib.import_module(parameters['module']) From 96d59bc677708e741d3c845c328161fc2e32d590 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 19 Jun 2020 13:58:55 -0400 Subject: [PATCH 316/419] add 2D>3D segmentation mask --- merlin/analysis/segment.py | 41 ++------------------------------------ 1 file changed, 2 insertions(+), 39 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 0977614b..f7364c1d 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -353,47 +353,10 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" Segmentation finished, ET {:.2f} min".format( (endTime - startTime) / 60)) - """ - endTime = time.time() - print(" images read, ET {:.2f} min".format( - (endTime - startTime) / 60)) - print(" membraneImages Type: " + str(type(membraneImages))) - print(" membraneImages Size: [" - + str(membraneImages.shape[0]) - + "," + str(membraneImages.shape[1]) - + "," + str(membraneImages.shape[2]) + "]") - print(" compartmentImages Type: " + str(type(compartmentImages))) - print(" compartmentImages Size: [" - + str(compartmentImages.shape[0]) - + "," + str(compartmentImages.shape[1]) - + "," + str(compartmentImages.shape[2]) + "]") - - # Prepare masks for cv2 watershed - watershedMarkers = watershed.get_cv2_watershed_markers( - compartmentImages, - membraneImages, - membraneFlag) - - endTime = time.time() - print(" markers calculated, ET {:.2f} min".format( - (endTime - startTime) / 60)) - - # perform watershed in individual z positions - watershedOutput = watershed.apply_cv2_watershed(compartmentImages, - watershedMarkers) - - endTime = time.time() - print(" watershed calculated, ET {:.2f} min".format( - (endTime - startTime) / 60)) # combine all z positions in watershed - watershedCombinedOutput = watershed \ - .combine_2d_segmentation_masks_into_3d(watershedOutput) - - endTime = time.time() - print(" watershed z positions combined, ET {:.2f} min".format( - (endTime - startTime) / 60)) - """ + watershedCombinedOutput = segmentation \ + .combine_2d_segmentation_masks_into_3d(segmentationOutput) # get features from mask. This is the slowestart (6 min for the # previous part, 15+ for the rest, for a 7 frame Image. From 08ce7fc559c69ab02a83712f0b8eb312446bb1ee Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 19 Jun 2020 14:58:36 -0400 Subject: [PATCH 317/419] changed segmentation output variable name --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index f7364c1d..b2aae913 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -250,7 +250,7 @@ def _run_analysis(self, fragmentIndex): featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( (watershedCombinedOutput == i), fragmentIndex, globalTask.fov_to_global_transform(fragmentIndex), zPos) - for i in np.unique(watershedOutput) if i != 0] + for i in np.unique(watershedCombinedOutput) if i != 0] featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) @@ -364,7 +364,7 @@ def _run_analysis(self, fragmentIndex): featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( (watershedCombinedOutput == i), fragmentIndex, globalTask.fov_to_global_transform(fragmentIndex), zPos) - for i in np.unique(watershedOutput) if i != 0] + for i in np.unique(watershedCombinedOutput) if i != 0] featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) From ec5b51ff815b75fabee63736125b50c78b530404 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 24 Jun 2020 10:52:08 -0400 Subject: [PATCH 318/419] changed value of the background mask from 100 to 0 --- merlin/util/segmentation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index b7c1bd4d..6ef7f35b 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -408,7 +408,7 @@ def get_overlapping_objects(watershedZ0: np.ndarray, """ z1Indexes = np.unique(watershedZ1[watershedZ0 == n0]) - z1Indexes = z1Indexes[z1Indexes > 100] + z1Indexes = z1Indexes[z1Indexes > 0] if z1Indexes.shape[0] > 0: @@ -467,7 +467,7 @@ def combine_2d_segmentation_masks_into_3d(watershedOutput: # starting far from coverslip for z in range(watershedOutput.shape[0]-1, 0, -1): zIndex = np.unique(watershedOutput[z, :, :])[ - np.unique(watershedOutput[z, :, :]) > 100] + np.unique(watershedOutput[z, :, :]) > 0] for n0 in zIndex: n1, f0, f1 = get_overlapping_objects(watershedCombinedZ[z, :, :], From 7ad7836c285ee78a6a14e46f0677a539960f13d5 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 24 Jun 2020 11:09:57 -0400 Subject: [PATCH 319/419] mofified botched indentation in combine 2d>3d --- merlin/util/segmentation.py | 62 +++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 6ef7f35b..7989b280 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -389,39 +389,38 @@ def apply_cv2_watershed(compartmentImages: np.ndarray, return watershedOutput -def get_overlapping_objects(watershedZ0: np.ndarray, - watershedZ1: np.ndarray, n0: int): - """Perform watershed using cv2 +def get_overlapping_objects(segmentationZ0: np.ndarray, + segmentationZ1: np.ndarray, n0: int): + """compare cell labels in adjacent image masks Args: - watershedZ0: a 2 dimensional numpy array containing a - segmentation mask - watershedZ1: a 2 dimensional numpy array containing a - segmentation mask adjacent to watershedZ1 + segmentationZ0: a 2 dimensional numpy array containing a + segmentation mask in position Z + segmentationZ1: a 2 dimensional numpy array containing a + segmentation mask adjacent tosegmentationZ0 n0: an integer with the index of the object (cell/nuclei) - to be compared between the provided watershed - segmentation masks + to be compared between the provided segmentation masks Returns: a tuple (n1, f0, f1) containing the label of the cell in Z1 overlapping n0 (n1), the fraction of n0 overlaping n1 (f0) and the fraction of n1 overlapping n0 (f1) """ - z1Indexes = np.unique(watershedZ1[watershedZ0 == n0]) + z1Indexes = np.unique(segmentationZ1[segmentationZ0 == n0]) z1Indexes = z1Indexes[z1Indexes > 0] if z1Indexes.shape[0] > 0: # calculate overlap fraction - n0Area = np.count_nonzero(watershedZ0 == n0) + n0Area = np.count_nonzero(segmentationZ0 == n0) n1Area = np.zeros(len(z1Indexes)) overlapArea = np.zeros(len(z1Indexes)) for ii in range(len(z1Indexes)): n1 = z1Indexes[ii] - n1Area[ii] = np.count_nonzero(watershedZ1 == n1) - overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * - (watershedZ1 == n1)) + n1Area[ii] = np.count_nonzero(segmentationZ1 == n1) + overlapArea[ii] = np.count_nonzero((segmentationZ0 == n0) * + (segmentationZ1 == n1)) n0OverlapFraction = np.asarray(overlapArea / n0Area) n1OverlapFraction = np.asarray(overlapArea / n1Area) @@ -444,39 +443,42 @@ def get_overlapping_objects(watershedZ0: np.ndarray, return False, False, False -def combine_2d_segmentation_masks_into_3d(watershedOutput: +def combine_2d_segmentation_masks_into_3d(segmentationOutput: np.ndarray) -> np.ndarray: - """Take a 3 dimensional watershed masks and relabel them so that + """Take a 3 dimensional segmentation masks and relabel them so that nuclei in adjacent sections have the same label if the area their overlap surpases certain threshold Args: - watershedOutput: a 3 dimensional numpy array containing the + segmentationOutput: a 3 dimensional numpy array containing the segmentation masks arranged as (z, x, y). Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) of relabeled segmented cells """ - # Initialize empty array with size as watershedOutput array - watershedCombinedZ = np.zeros(watershedOutput.shape) + # Initialize empty array with size as segmentationOutput array + segmentationCombinedZ = np.zeros(segmentationOutput.shape) - # copy the mask of the section farthest to the coverslip - watershedCombinedZ[-1, :, :] = watershedOutput[-1, :, :] + # copy the mask of the section farthest to the coverslip to start + segmentationCombinedZ[-1, :, :] = segmentationOutput[-1, :, :] # starting far from coverslip - for z in range(watershedOutput.shape[0]-1, 0, -1): - zIndex = np.unique(watershedOutput[z, :, :])[ - np.unique(watershedOutput[z, :, :]) > 0] + for z in range(segmentationOutput.shape[0]-1, 0, -1): + + # get non-background cell indexes + zIndex = np.unique(segmentationOutput[z, :, :])[ + np.unique(segmentationOutput[z, :, :]) > 0] - for n0 in zIndex: - n1, f0, f1 = get_overlapping_objects(watershedCombinedZ[z, :, :], - watershedOutput[z-1, :, :], + # compare each cell in z0 + for n0 in zIndex: + n1, f0, f1 = get_overlapping_objects(segmentationCombinedZ[z, :, :], + segmentationOutput[z-1, :, :], n0) - if n1: - watershedCombinedZ[z-1, :, :][(watershedOutput[z-1, :, :] == + if n1: + segmentationCombinedZ[z-1, :, :][(segmentationOutput[z-1, :, :] == n1)] = n0 - return watershedCombinedZ + return segmentationCombinedZ def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: return None From 5c3bc449edb58b8c162649c9ee4230248e811fe6 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 24 Jun 2020 12:20:33 -0400 Subject: [PATCH 320/419] modifying tuple output in 2d>3d --- merlin/util/segmentation.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 7989b280..1c762418 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -378,7 +378,7 @@ def apply_cv2_watershed(compartmentImages: np.ndarray, independent """ - watershedOutput = np.zeros(watershedMarkers.shape) + watershedOutput = np.zeros(watershedMarkers.shape) for z in range(nucleiImages.shape[0]): rgbImage = convert_grayscale_to_rgb(compartmentImages[z, :, :]) watershedOutput[z, :, :] = cv2.watershed(rgbImage, @@ -390,7 +390,7 @@ def apply_cv2_watershed(compartmentImages: np.ndarray, def get_overlapping_objects(segmentationZ0: np.ndarray, - segmentationZ1: np.ndarray, n0: int): + segmentationZ1: np.ndarray, n0: int): """compare cell labels in adjacent image masks Args: @@ -435,8 +435,8 @@ def get_overlapping_objects(segmentationZ0: np.ndarray, if (n0OverlapFraction[indexSorted[0]] > 0.2 and n1OverlapFraction[indexSorted[0]] > 0.5): return z1Indexes[indexSorted[0]], - n0OverlapFraction[indexSorted[0]], - n1OverlapFraction[indexSorted[0]] + n0OverlapFraction[indexSorted[0]], + n1OverlapFraction[indexSorted[0]] else: return False, False, False else: @@ -472,9 +472,12 @@ def combine_2d_segmentation_masks_into_3d(segmentationOutput: # compare each cell in z0 for n0 in zIndex: - n1, f0, f1 = get_overlapping_objects(segmentationCombinedZ[z, :, :], + out = get_overlapping_objects(segmentationCombinedZ[z, :, :], segmentationOutput[z-1, :, :], n0) + n1 = out[0] + f0 = out[1] + f1 = out[2] if n1: segmentationCombinedZ[z-1, :, :][(segmentationOutput[z-1, :, :] == n1)] = n0 From eed7cc78117696d545ffac554556dcac65b598d6 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 24 Jun 2020 12:26:30 -0400 Subject: [PATCH 321/419] modifying tuple output in 2d>3d --- merlin/util/segmentation.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 1c762418..e54cea3f 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -472,12 +472,9 @@ def combine_2d_segmentation_masks_into_3d(segmentationOutput: # compare each cell in z0 for n0 in zIndex: - out = get_overlapping_objects(segmentationCombinedZ[z, :, :], + n1, f0, f1 = get_overlapping_objects(segmentationCombinedZ[z, :, :], segmentationOutput[z-1, :, :], n0) - n1 = out[0] - f0 = out[1] - f1 = out[2] if n1: segmentationCombinedZ[z-1, :, :][(segmentationOutput[z-1, :, :] == n1)] = n0 From cbb8aae66742d71a628111d65b64604ee80c4216 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 24 Jun 2020 12:35:36 -0400 Subject: [PATCH 322/419] made tuple explicit --- merlin/util/segmentation.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index e54cea3f..832e962d 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -434,13 +434,13 @@ def get_overlapping_objects(segmentationZ0: np.ndarray, if (n0OverlapFraction[indexSorted[0]] > 0.2 and n1OverlapFraction[indexSorted[0]] > 0.5): - return z1Indexes[indexSorted[0]], - n0OverlapFraction[indexSorted[0]], - n1OverlapFraction[indexSorted[0]] + return (z1Indexes[indexSorted[0]], + n0OverlapFraction[indexSorted[0]], + n1OverlapFraction[indexSorted[0]]) else: - return False, False, False + return (False, False, False) else: - return False, False, False + return (False, False, False) def combine_2d_segmentation_masks_into_3d(segmentationOutput: From 4ae5515febcb7d4c36a1859ac2a4978092a9becd Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 1 Jul 2020 16:47:50 -0400 Subject: [PATCH 323/419] pep8 compliance --- merlin/analysis/segment.py | 6 +++--- merlin/util/segmentation.py | 37 +++++++++++++++++++------------------ 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index b2aae913..e4333a75 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -96,8 +96,8 @@ def _run_analysis(self, fragmentIndex): watershedIndex, 5) seeds = segmentation.separate_merged_seeds( segmentation.extract_seeds(seedImages)) - normalizedWatershed, watershedMask = segmentation.prepare_watershed_images( - watershedImages) + normalizedWatershed, watershedMask = segmentation\ + .prepare_watershed_images(watershedImages) seeds[np.invert(watershedMask)] = 0 watershedOutput = skiseg.watershed( @@ -322,7 +322,7 @@ def _run_analysis(self, fragmentIndex): print(' globalTask loaded') - # read membrane and compartment indexes + # read membrane and compartment indexes compartmentIndex = self.dataSet \ .get_data_organization() \ .get_data_channel_index( diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 832e962d..e33aeaa5 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -17,7 +17,7 @@ """ This module contains utility functions for preparing images for watershed segmentation, as well as functions to perform segmentation -using macnine learning approaches +using machine learning approaches """ # To match Matlab's strel('disk', 20) @@ -152,13 +152,13 @@ def get_membrane_mask(membraneImages: np.ndarray, compartmentChannelName: str) -> np.ndarray: """Calculate binary mask with 1's in membrane pixels and 0 otherwise. The images expected are some type of membrane label (WGA, ConA, - Lamin, Cadherins) or compartment images (DAPI, CD45, polyT) + Lamin, Cadherins) or compartment images (DAPI, CD45, polyT) Args: membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). membraneChannelName: A string with the name of a membrane channel. - compartmentChannelName: A string with the name of the compartment + compartmentChannelName: A string with the name of the compartment channel Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) @@ -210,8 +210,8 @@ def get_membrane_mask(membraneImages: np.ndarray, def get_compartment_mask(compartmentImages: np.ndarray) -> np.ndarray: - """Calculate binary mask with 1's in compartment (nuclei or cytoplasm) - pixels and 0 otherwise. The images expected are some type of compartment + """Calculate binary mask with 1's in compartment (nuclei or cytoplasm) + pixels and 0 otherwise. The images expected are some type of compartment label (e.g. Nuclei: DAPI, Cytoplasm: PolyT, CD45, etc) Args: @@ -243,7 +243,7 @@ def get_compartment_mask(compartmentImages: np.ndarray) -> np.ndarray: # generate border mask, necessary to avoid making a single # connected component when using binary_fill_holes below - borderMask = np.zeros((compartmentImages.shape[1], + borderMask = np.zeros((compartmentImages.shape[1], compartmentImages.shape[2])) borderMask[25:(compartmentImages.shape[1]-25), 25:(compartmentImages.shape[2]-25)] = 1 @@ -291,7 +291,7 @@ def get_cv2_watershed_markers(compartmentImages: np.ndarray, arranged as (z, x, y). membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). - membraneFlag: 0 if compartment and membrane images are the same, 1 + membraneFlag: 0 if compartment and membrane images are the same, 1 otherwise Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) of @@ -390,7 +390,8 @@ def apply_cv2_watershed(compartmentImages: np.ndarray, def get_overlapping_objects(segmentationZ0: np.ndarray, - segmentationZ1: np.ndarray, n0: int): + segmentationZ1: np.ndarray, + n0: int) -> Tuple[]: """compare cell labels in adjacent image masks Args: @@ -398,7 +399,7 @@ def get_overlapping_objects(segmentationZ0: np.ndarray, segmentation mask in position Z segmentationZ1: a 2 dimensional numpy array containing a segmentation mask adjacent tosegmentationZ0 - n0: an integer with the index of the object (cell/nuclei) + n0: an integer with the index of the object (cell/nuclei) to be compared between the provided segmentation masks Returns: a tuple (n1, f0, f1) containing the label of the cell in Z1 @@ -470,14 +471,14 @@ def combine_2d_segmentation_masks_into_3d(segmentationOutput: zIndex = np.unique(segmentationOutput[z, :, :])[ np.unique(segmentationOutput[z, :, :]) > 0] - # compare each cell in z0 + # compare each cell in z0 for n0 in zIndex: n1, f0, f1 = get_overlapping_objects(segmentationCombinedZ[z, :, :], segmentationOutput[z-1, :, :], n0) if n1: - segmentationCombinedZ[z-1, :, :][(segmentationOutput[z-1, :, :] == - n1)] = n0 + segmentationCombinedZ[z-1, :, :][ + (segmentationOutput[z-1, :, :] ==n1)] = n0 return segmentationCombinedZ def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: @@ -488,7 +489,7 @@ def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: def segment_using_cellpose(imageStackIn: np.ndarray, params: dict) -> np.ndarray: - """Perform segmentation using cellpose. Code adapted from + """Perform segmentation using cellpose. Code adapted from https://nbviewer.jupyter.org/github/MouseLand/cellpose/blob/ master/notebooks/run_cellpose.ipynb Args: @@ -525,14 +526,14 @@ def segment_using_cellpose(imageStackIn: np.ndarray, # or if you have different types of channels in each image # channels = [[0,0],[0,0]] - # if diameter is set to None, the size of the cells is estimated on a per - # image basis you can set the average cell `diameter` in pixels yourself + # if diameter is set to None, the size of the cells is estimated on a per + # image basis you can set the average cell `diameter` in pixels yourself # (recommended) diameter can be a list or a single number for all images # put list of images in cellpose format imageList = np.split(imageStackIn,imageStackIn.shape[0]) - masks, flows, styles, diams = model.eval(imageList, + masks, flows, styles, diams = model.eval(imageList, diameter=params['diameter'], channels=channels) # combine masks into array @@ -541,7 +542,7 @@ def segment_using_cellpose(imageStackIn: np.ndarray, return masksArray -def apply_machine_learning_segmentation(imageStackIn: np.ndarray, +def apply_machine_learning_segmentation(imageStackIn: np.ndarray, params: dict) -> np.ndarray: """Select segmentation algorithm to use Args: @@ -549,7 +550,7 @@ def apply_machine_learning_segmentation(imageStackIn: np.ndarray, arranged as (z, x, y). params: dictionary with key:value pairs with parameters to be passed to the segmentation code. Keys used are 'method', 'diameter', - 'channel' + 'channel' Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) From 64ed96dfff53ae6df26d4e44ed075e34d536da5a Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 1 Jul 2020 17:02:22 -0400 Subject: [PATCH 324/419] pep8 compliance --- merlin/analysis/segment.py | 15 +++++---- merlin/util/segmentation.py | 65 +++++++++++++++++++------------------ 2 files changed, 42 insertions(+), 38 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index e4333a75..46de62e9 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -150,7 +150,7 @@ class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): def __init__(self, dataSet, parameters=None, analysisName=None): super().__init__(dataSet, parameters, analysisName) - + if 'membrane_channel_name' not in self.parameters: self.parameters['membrane_channel_name'] = 'DAPI' if 'compartment_channel_name' not in self.parameters: @@ -265,6 +265,7 @@ def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: return np.array([warpTask.get_aligned_image(fov, channelIndex, z) for z in range(len(self.dataSet.get_z_positions()))]) + class MachineLearningSegment(FeatureSavingAnalysisTask): """ @@ -273,9 +274,9 @@ class MachineLearningSegment(FeatureSavingAnalysisTask): method. The available methods are: unet: - + ilastik: - + cellpose: TODO: ADD FLAT FIELD CORRECTION TASK @@ -284,7 +285,7 @@ class MachineLearningSegment(FeatureSavingAnalysisTask): def __init__(self, dataSet, parameters=None, analysisName=None): super().__init__(dataSet, parameters, analysisName) - + if 'method' not in self.parameters: self.parameters['method'] = 'cellpose' if 'diameter' not in self.parameters: @@ -342,9 +343,9 @@ def _run_analysis(self, fragmentIndex): if self.parameters['method'] == 'cellpose': segParameters = dict({ - 'method':'cellpose', - 'diameter':self.parameters['diameter'], - 'channel':self.parameters['compartment_channel_name'] + 'method': 'cellpose', + 'diameter': self.parameters['diameter'], + 'channel': self.parameters['compartment_channel_name'] }) segmentationOutput = segmentation.apply_machine_learning_segmentation( diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index e33aeaa5..528bd15b 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -140,8 +140,8 @@ def prepare_watershed_images(watershedImageStack: np.ndarray normalizedWatershed = 1 - (watershedImageStack - np.min(watershedImageStack)) / \ - (np.max(watershedImageStack) - - np.min(watershedImageStack)) + (np.max(watershedImageStack) + - np.min(watershedImageStack)) normalizedWatershed[np.invert(watershedMask)] = 1 return normalizedWatershed, watershedMask @@ -181,26 +181,26 @@ def get_membrane_mask(membraneImages: np.ndarray, else: filterSigma2 = 5 filterSize2 = int(2*np.ceil(2*filterSigma2)+1) - edgeSigma = 2 #1 #2 - lowThresh = 0.1 #0.5 #0.2 - hiThresh = 0.5 #0.7 #0.6 + edgeSigma = 2# 1 #2 + lowThresh = 0.1# 0.5 #0.2 + hiThresh = 0.5# 0.7 #0.6 for z in range(membraneImages.shape[0]): blurredImage = cv2.GaussianBlur(membraneImages[z, :, :], - (filterSize2,filterSize2), + (filterSize2, filterSize2), filterSigma2) edge0 = feature.canny(membraneImages[z, :, :], - sigma=edgeSigma, - use_quantiles=True, - low_threshold=lowThresh, - high_threshold=hiThresh) - edge0 = morphology.dilation(edge0,morphology.selem.disk(10)) - - edge1 = feature.canny(blurredImage, - sigma=edgeSigma, - use_quantiles=True, - low_threshold=lowThresh, - high_threshold=hiThresh) - edge1 = morphology.dilation(edge1,morphology.selem.disk(10)) + sigma=edgeSigma, + use_quantiles=True, + low_threshold=lowThresh, + high_threshold=hiThresh) + edge0 = morphology.dilation(edge0, morphology.selem.disk(10)) + + edge1 = feature.canny(blurredImage, + sigma=edgeSigma, + use_quantiles=True, + low_threshold=lowThresh, + high_threshold=hiThresh) + edge1 = morphology.dilation(edge1, morphology.selem.disk(10)) mask[z, :, :] = edge0 + edge1 @@ -245,7 +245,7 @@ def get_compartment_mask(compartmentImages: np.ndarray) -> np.ndarray: # connected component when using binary_fill_holes below borderMask = np.zeros((compartmentImages.shape[1], compartmentImages.shape[2])) - borderMask[25:(compartmentImages.shape[1]-25), + borderMask[25:(compartmentImages.shape[1]-25), 25:(compartmentImages.shape[2]-25)] = 1 # generate compartment mask from hessian, fine @@ -378,7 +378,7 @@ def apply_cv2_watershed(compartmentImages: np.ndarray, independent """ - watershedOutput = np.zeros(watershedMarkers.shape) + watershedOutput = np.zeros(watershedMarkers.shape) for z in range(nucleiImages.shape[0]): rgbImage = convert_grayscale_to_rgb(compartmentImages[z, :, :]) watershedOutput[z, :, :] = cv2.watershed(rgbImage, @@ -390,7 +390,7 @@ def apply_cv2_watershed(compartmentImages: np.ndarray, def get_overlapping_objects(segmentationZ0: np.ndarray, - segmentationZ1: np.ndarray, + segmentationZ1: np.ndarray, n0: int) -> Tuple[]: """compare cell labels in adjacent image masks @@ -436,8 +436,8 @@ def get_overlapping_objects(segmentationZ0: np.ndarray, if (n0OverlapFraction[indexSorted[0]] > 0.2 and n1OverlapFraction[indexSorted[0]] > 0.5): return (z1Indexes[indexSorted[0]], - n0OverlapFraction[indexSorted[0]], - n1OverlapFraction[indexSorted[0]]) + n0OverlapFraction[indexSorted[0]], + n1OverlapFraction[indexSorted[0]]) else: return (False, False, False) else: @@ -474,19 +474,22 @@ def combine_2d_segmentation_masks_into_3d(segmentationOutput: # compare each cell in z0 for n0 in zIndex: n1, f0, f1 = get_overlapping_objects(segmentationCombinedZ[z, :, :], - segmentationOutput[z-1, :, :], - n0) + segmentationOutput[z-1, :, :], + n0) if n1: segmentationCombinedZ[z-1, :, :][ - (segmentationOutput[z-1, :, :] ==n1)] = n0 + (segmentationOutput[z-1, :, :] == n1)] = n0 return segmentationCombinedZ + def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: return None + def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: return None + def segment_using_cellpose(imageStackIn: np.ndarray, params: dict) -> np.ndarray: """Perform segmentation using cellpose. Code adapted from @@ -502,14 +505,14 @@ def segment_using_cellpose(imageStackIn: np.ndarray, channelName = params['channel'].lower() # Define cellpose model - if any([channelName == 'dapi', + if any([channelName == 'dapi', channelName == 'lamin']): model = models.Cellpose(gpu=False, model_type='nuclei') - if any([channelName == 'polyt', + if any([channelName == 'polyt', channelName == 'polya', channelName == 'ecadherin', channelName == 'cd45', - channelName == 'wga', + channelName == 'wga', channelName == 'cona']): model = models.Cellpose(gpu=False, model_type='cyto') @@ -519,7 +522,7 @@ def segment_using_cellpose(imageStackIn: np.ndarray, # if NUCLEUS channel does not exist, set the second channel to 0 # channels = [0,0] # IF ALL YOUR IMAGES ARE THE SAME TYPE, you can give a list with 2 elements - channels = [0,0] # IF YOU HAVE GRAYSCALE + channels = [0,0] # IF YOU HAVE GRAYSCALE # channels = [2,3] # IF YOU HAVE G=cytoplasm and B=nucleus # channels = [2,1] # IF YOU HAVE G=cytoplasm and R=nucleus @@ -531,7 +534,7 @@ def segment_using_cellpose(imageStackIn: np.ndarray, # (recommended) diameter can be a list or a single number for all images # put list of images in cellpose format - imageList = np.split(imageStackIn,imageStackIn.shape[0]) + imageList = np.split(imageStackIn, imageStackIn.shape[0]) masks, flows, styles, diams = model.eval(imageList, diameter=params['diameter'], From a655cf73b3e0d4428411f1a5ac4e24b6d12d099d Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 1 Jul 2020 18:14:37 -0400 Subject: [PATCH 325/419] pep8 compliance --- merlin/util/watershed.py | 44 ++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index f41a404b..11870e8d 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -148,13 +148,13 @@ def get_membrane_mask(membraneImages: np.ndarray, compartmentChannelName: str) -> np.ndarray: """Calculate binary mask with 1's in membrane pixels and 0 otherwise. The images expected are some type of membrane label (WGA, ConA, - Lamin, Cadherins) or compartment images (DAPI, CD45, polyT) + Lamin, Cadherins) or compartment images (DAPI, CD45, polyT) Args: membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). membraneChannelName: A string with the name of a membrane channel. - compartmentChannelName: A string with the name of the compartment + compartmentChannelName: A string with the name of the compartment channel Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) @@ -177,26 +177,26 @@ def get_membrane_mask(membraneImages: np.ndarray, else: filterSigma2 = 5 filterSize2 = int(2*np.ceil(2*filterSigma2)+1) - edgeSigma = 2 #1 #2 - lowThresh = 0.1 #0.5 #0.2 - hiThresh = 0.5 #0.7 #0.6 + edgeSigma = 2 #1 #2 + lowThresh = 0.1 #0.5 #0.2 + hiThresh = 0.5 #0.7 #0.6 for z in range(membraneImages.shape[0]): blurredImage = cv2.GaussianBlur(membraneImages[z, :, :], - (filterSize2,filterSize2), + (filterSize2, filterSize2), filterSigma2) edge0 = feature.canny(membraneImages[z, :, :], sigma=edgeSigma, use_quantiles=True, low_threshold=lowThresh, high_threshold=hiThresh) - edge0 = morphology.dilation(edge0,morphology.selem.disk(10)) - - edge1 = feature.canny(blurredImage, - sigma=edgeSigma, - use_quantiles=True, - low_threshold=lowThresh, - high_threshold=hiThresh) - edge1 = morphology.dilation(edge1,morphology.selem.disk(10)) + edge0 = morphology.dilation(edge0, morphology.selem.disk(10)) + + edge1 = feature.canny(blurredImage, + sigma=edgeSigma, + use_quantiles=True, + low_threshold=lowThresh, + high_threshold=hiThresh) + edge1 = morphology.dilation(edge1, morphology.selem.disk(10)) mask[z, :, :] = edge0 + edge1 @@ -206,8 +206,8 @@ def get_membrane_mask(membraneImages: np.ndarray, def get_compartment_mask(compartmentImages: np.ndarray) -> np.ndarray: - """Calculate binary mask with 1's in compartment (nuclei or cytoplasm) - pixels and 0 otherwise. The images expected are some type of compartment + """Calculate binary mask with 1's in compartment (nuclei or cytoplasm) + pixels and 0 otherwise. The images expected are some type of compartment label (e.g. Nuclei: DAPI, Cytoplasm: PolyT, CD45, etc) Args: @@ -239,9 +239,9 @@ def get_compartment_mask(compartmentImages: np.ndarray) -> np.ndarray: # generate border mask, necessary to avoid making a single # connected component when using binary_fill_holes below - borderMask = np.zeros((compartmentImages.shape[1], + borderMask = np.zeros((compartmentImages.shape[1], compartmentImages.shape[2])) - borderMask[25:(compartmentImages.shape[1]-25), + borderMask[25:(compartmentImages.shape[1]-25), 25:(compartmentImages.shape[2]-25)] = 1 # generate compartment mask from hessian, fine @@ -287,7 +287,7 @@ def get_cv2_watershed_markers(compartmentImages: np.ndarray, arranged as (z, x, y). membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). - membraneFlag: 0 if compartment and membrane images are the same, 1 + membraneFlag: 0 if compartment and membrane images are the same, 1 otherwise Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) of @@ -386,7 +386,7 @@ def apply_cv2_watershed(compartmentImages: np.ndarray, def get_overlapping_objects(watershedZ0: np.ndarray, - watershedZ1: np.ndarray, n0: int): + watershedZ1: np.ndarray, n0: int): """Perform watershed using cv2 Args: @@ -394,8 +394,8 @@ def get_overlapping_objects(watershedZ0: np.ndarray, segmentation mask watershedZ1: a 2 dimensional numpy array containing a segmentation mask adjacent to watershedZ1 - n0: an integer with the index of the object (cell/nuclei) - to be compared between the provided watershed + n0: an integer with the index of the object (cell/nuclei) + to be compared between the provided watershed segmentation masks Returns: a tuple (n1, f0, f1) containing the label of the cell in Z1 From dc7e3bc04a61f8b1211f32d62190a2bb452248fe Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 1 Jul 2020 18:18:52 -0400 Subject: [PATCH 326/419] pep8 compliance --- merlin/util/segmentation.py | 2 +- merlin/util/watershed.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 528bd15b..3a1c201b 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -522,7 +522,7 @@ def segment_using_cellpose(imageStackIn: np.ndarray, # if NUCLEUS channel does not exist, set the second channel to 0 # channels = [0,0] # IF ALL YOUR IMAGES ARE THE SAME TYPE, you can give a list with 2 elements - channels = [0,0] # IF YOU HAVE GRAYSCALE + channels = [0, 0] # IF YOU HAVE GRAYSCALE # channels = [2,3] # IF YOU HAVE G=cytoplasm and B=nucleus # channels = [2,1] # IF YOU HAVE G=cytoplasm and R=nucleus diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 11870e8d..762c3e4d 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -177,18 +177,18 @@ def get_membrane_mask(membraneImages: np.ndarray, else: filterSigma2 = 5 filterSize2 = int(2*np.ceil(2*filterSigma2)+1) - edgeSigma = 2 #1 #2 - lowThresh = 0.1 #0.5 #0.2 - hiThresh = 0.5 #0.7 #0.6 + edgeSigma = 2 # 1, 2 + lowThresh = 0.1 # 0.5, 0.2 + hiThresh = 0.5 # 0.7, 0.6 for z in range(membraneImages.shape[0]): blurredImage = cv2.GaussianBlur(membraneImages[z, :, :], (filterSize2, filterSize2), filterSigma2) edge0 = feature.canny(membraneImages[z, :, :], - sigma=edgeSigma, - use_quantiles=True, - low_threshold=lowThresh, - high_threshold=hiThresh) + sigma=edgeSigma, + use_quantiles=True, + low_threshold=lowThresh, + high_threshold=hiThresh) edge0 = morphology.dilation(edge0, morphology.selem.disk(10)) edge1 = feature.canny(blurredImage, From 1414abaf71ea7f97aa12267102203bba267c61f3 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 1 Jul 2020 18:36:19 -0400 Subject: [PATCH 327/419] added Machine Learning Segment and CV2 to tests --- .../test_analysis_parameters.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/auxiliary_files/test_analysis_parameters.json b/test/auxiliary_files/test_analysis_parameters.json index 2e9f9212..e426e860 100755 --- a/test/auxiliary_files/test_analysis_parameters.json +++ b/test/auxiliary_files/test_analysis_parameters.json @@ -115,6 +115,25 @@ "global_align_task": "SimpleGlobalAlignment" } }, + { + "task": "WatershedSegmentNucleiCV2", + "module": "merlin.analysis.segment", + "parameters": { + "warp_task": "FiducialCorrelationWarp", + "global_align_task": "SimpleGlobalAlignment" + } + }, + { + "task": "MachineLearningSegment", + "module": "merlin.analysis.segment", + "parameters": { + "warp_task": "FiducialCorrelationWarp", + "global_align_task": "SimpleGlobalAlignment", + "diameter": 50, + "method": "cellpose", + "compartment_channel_name": "DAPI" + } + }, { "task": "CleanCellBoundaries", "module": "merlin.analysis.segment", From 0d234419207828a080553f1693ad5f3448006dca Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 1 Jul 2020 19:16:44 -0400 Subject: [PATCH 328/419] remove print statements for timing --- merlin/analysis/segment.py | 72 ++------------------------------------ 1 file changed, 3 insertions(+), 69 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 46de62e9..e964e45f 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -178,13 +178,9 @@ def get_cell_boundaries(self) -> List[spatialfeature.SpatialFeature]: def _run_analysis(self, fragmentIndex): startTime = time.time() - print('Entered the _run_analysis method, FOV ' + str(fragmentIndex)) - globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) - print(' globalTask loaded') - # read membrane and compartment indexes membraneIndex = self.dataSet \ .get_data_organization() \ @@ -195,55 +191,25 @@ def _run_analysis(self, fragmentIndex): .get_data_channel_index( self.parameters['compartment_channel_name']) - endTime = time.time() - print(" image indexes read, ET {:.2f} min".format( - (endTime - startTime) / 60)) - # read membrane and compartment images membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) compartmentImages = self._read_image_stack(fragmentIndex, compartmentIndex) - endTime = time.time() - print(" images read, ET {:.2f} min".format( - (endTime - startTime) / 60)) - print(" membraneImages Type: " + str(type(membraneImages))) - print(" membraneImages Size: [" - + str(membraneImages.shape[0]) - + "," + str(membraneImages.shape[1]) - + "," + str(membraneImages.shape[2]) + "]") - print(" compartmentImages Type: " + str(type(compartmentImages))) - print(" compartmentImages Size: [" - + str(compartmentImages.shape[0]) - + "," + str(compartmentImages.shape[1]) - + "," + str(compartmentImages.shape[2]) + "]") - # Prepare masks for cv2 watershed watershedMarkers = segmentation.get_cv2_watershed_markers( compartmentImages, membraneImages, self.parameters['membrane_channel_name']) - endTime = time.time() - print(" markers calculated, ET {:.2f} min".format( - (endTime - startTime) / 60)) - # perform watershed in individual z positions watershedOutput = segmentation.apply_cv2_watershed(compartmentImages, watershedMarkers) - endTime = time.time() - print(" watershed calculated, ET {:.2f} min".format( - (endTime - startTime) / 60)) - # combine all z positions in watershed watershedCombinedOutput = segmentation \ .combine_2d_segmentation_masks_into_3d(watershedOutput) - endTime = time.time() - print(" watershed z positions combined, ET {:.2f} min".format( - (endTime - startTime) / 60)) - # get features from mask. This is the slowestart (6 min for the # previous part, 15+ for the rest, for a 7 frame Image. zPos = np.array(self.dataSet.get_data_organization().get_z_positions()) @@ -255,10 +221,6 @@ def _run_analysis(self, fragmentIndex): featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) - endTime = time.time() - print(" features written, ET {:.2f} min".format( - (endTime - startTime) / 60)) - def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) @@ -271,16 +233,10 @@ class MachineLearningSegment(FeatureSavingAnalysisTask): """ An analysis task that determines the boundaries of features in the image data in each field of view using a the specified machine learning - method. The available methods are: - - unet: - - ilastik: - - cellpose: - - TODO: ADD FLAT FIELD CORRECTION TASK + method. The available method is cellpose (https://github.com/MouseLand/ + cellpose). + TODO: implement unets / Ilastik """ def __init__(self, dataSet, parameters=None, analysisName=None): @@ -313,34 +269,20 @@ def get_cell_boundaries(self) -> List[spatialfeature.SpatialFeature]: return featureDB.read_features() def _run_analysis(self, fragmentIndex): - startTime = time.time() - - print('Entered the _run_analysis method, FOV ' + str(fragmentIndex)) - print('Using ' + self.parameters['method'] + ' method.') globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) - print(' globalTask loaded') - # read membrane and compartment indexes compartmentIndex = self.dataSet \ .get_data_organization() \ .get_data_channel_index( self.parameters['compartment_channel_name']) - endTime = time.time() - print(" image indexes read, ET {:.2f} min".format( - (endTime - startTime) / 60)) - # Read images and perform segmentation compartmentImages = self._read_image_stack(fragmentIndex, compartmentIndex) - endTime = time.time() - print(" images read, ET {:.2f} min".format( - (endTime - startTime) / 60)) - if self.parameters['method'] == 'cellpose': segParameters = dict({ 'method': 'cellpose', @@ -351,10 +293,6 @@ def _run_analysis(self, fragmentIndex): segmentationOutput = segmentation.apply_machine_learning_segmentation( compartmentImages, segParameters) - endTime = time.time() - print(" Segmentation finished, ET {:.2f} min".format( - (endTime - startTime) / 60)) - # combine all z positions in watershed watershedCombinedOutput = segmentation \ .combine_2d_segmentation_masks_into_3d(segmentationOutput) @@ -370,10 +308,6 @@ def _run_analysis(self, fragmentIndex): featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) - endTime = time.time() - print(" features written, ET {:.2f} min".format( - (endTime - startTime) / 60)) - def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) From 322c009ff6317f06be6a0b97ba3033156983e057 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 07:18:42 -0400 Subject: [PATCH 329/419] removed explicit tuple output from function --- merlin/util/segmentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 3a1c201b..2d7fe73f 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -391,7 +391,7 @@ def apply_cv2_watershed(compartmentImages: np.ndarray, def get_overlapping_objects(segmentationZ0: np.ndarray, segmentationZ1: np.ndarray, - n0: int) -> Tuple[]: + n0: int): """compare cell labels in adjacent image masks Args: From bbedf08fd915c5bf19bfd7efbbf05e59b46bf1a1 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 07:33:30 -0400 Subject: [PATCH 330/419] added explicit tuple output --- merlin/util/segmentation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 2d7fe73f..3055ebb2 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -391,7 +391,8 @@ def apply_cv2_watershed(compartmentImages: np.ndarray, def get_overlapping_objects(segmentationZ0: np.ndarray, segmentationZ1: np.ndarray, - n0: int): + n0: int) -> Tuple[np.float64, + np.float64, np.float64]: """compare cell labels in adjacent image masks Args: From dfee36dfe0a243db45f5789b75b295df1fd1c613 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 07:56:34 -0400 Subject: [PATCH 331/419] add cellpose to requirements --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index dfc4de44..35d39c48 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,3 +29,4 @@ boto3 xmltodict google-cloud-storage docutils<0.16,>=0.10 + From bc812756ba79f04e77e81d5764fbf42af37689e2 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 08:04:52 -0400 Subject: [PATCH 332/419] updated docutils and pillow requirements --- requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 35d39c48..c65e9715 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,5 +28,4 @@ tables boto3 xmltodict google-cloud-storage -docutils<0.16,>=0.10 - +docutils<0.16,>=0.10 \ No newline at end of file From b32ff15aa126954f67cb118045475241a8f9eddc Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 10:14:29 -0400 Subject: [PATCH 333/419] changed marker bg in ge_cv2_markers --- merlin/util/segmentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 3055ebb2..8a2ef68d 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -325,7 +325,7 @@ def get_cv2_watershed_markers(compartmentImages: np.ndarray, ret, markers = cv2.connectedComponents(foreground) # Add one to all labels so that sure background is not 0, but 1 - markers = markers + 100 + markers = markers + 1 # Now, mark the region of unknown with zero markers[unknown == 255] = 0 From d34b73b523a9f314f8174a8c7c967b9ea80dfcec Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 11:23:03 -0400 Subject: [PATCH 334/419] added input variables to get_membrane_mask --- merlin/util/segmentation.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 8a2ef68d..2e9b924d 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -148,8 +148,8 @@ def prepare_watershed_images(watershedImageStack: np.ndarray def get_membrane_mask(membraneImages: np.ndarray, - membraneChannelName: str, - compartmentChannelName: str) -> np.ndarray: + compartmentChannelName: str, + membraneChannelName: str) -> np.ndarray: """Calculate binary mask with 1's in membrane pixels and 0 otherwise. The images expected are some type of membrane label (WGA, ConA, Lamin, Cadherins) or compartment images (DAPI, CD45, polyT) @@ -282,7 +282,8 @@ def get_compartment_mask(compartmentImages: np.ndarray) -> np.ndarray: def get_cv2_watershed_markers(compartmentImages: np.ndarray, membraneImages: np.ndarray, - membraneFlag: int) -> np.ndarray: + compartmentChannelName: str, + membraneChannelName: str) -> np.ndarray: """Combine membrane and compartment markers into a single multilabel mask for CV2 watershed @@ -291,15 +292,19 @@ def get_cv2_watershed_markers(compartmentImages: np.ndarray, arranged as (z, x, y). membraneImages: a 3 dimensional numpy array containing the images arranged as (z, x, y). - membraneFlag: 0 if compartment and membrane images are the same, 1 - otherwise + compartmentChannelName: str with the name of the compartment channel + to use + membraneChannelName: str with the name of the membrane channel + to use Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) of cv2-compatible watershed markers """ compartmentMask = get_compartment_mask(compartmentImages) - membraneMask = get_membrane_mask(membraneImages, membraneFlag) + membraneMask = get_membrane_mask(membraneImages, + compartmentChannelName, + membraneChannelName) watershedMarker = np.zeros(compartmentMask.shape) From 2987aaa5425206788a16a430061175e980b58680 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 12:49:56 -0400 Subject: [PATCH 335/419] added additional variable --- merlin/analysis/segment.py | 1 + 1 file changed, 1 insertion(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index e964e45f..e7aede09 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -200,6 +200,7 @@ def _run_analysis(self, fragmentIndex): watershedMarkers = segmentation.get_cv2_watershed_markers( compartmentImages, membraneImages, + self.parameters['compartment_channel_name'], self.parameters['membrane_channel_name']) # perform watershed in individual z positions From c926ae84fd3d5f51c60f0a9048fb7ba7d5d06306 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 18:09:50 -0400 Subject: [PATCH 336/419] updated variable name --- merlin/util/segmentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 2e9b924d..461e4fee 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -384,7 +384,7 @@ def apply_cv2_watershed(compartmentImages: np.ndarray, """ watershedOutput = np.zeros(watershedMarkers.shape) - for z in range(nucleiImages.shape[0]): + for z in range(compartmentImages.shape[0]): rgbImage = convert_grayscale_to_rgb(compartmentImages[z, :, :]) watershedOutput[z, :, :] = cv2.watershed(rgbImage, watershedMarkers[z, :, :]. From 0ce75ca2ae38bfa4ee20016157ebdf51b349dc43 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Jan 2020 14:11:30 -0500 Subject: [PATCH 337/419] added method _generate_markers to WatershedSegmentNucleiCV2 --- merlin/analysis/segment.py | 1 - 1 file changed, 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index e7aede09..a04615f6 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -230,7 +230,6 @@ def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: class MachineLearningSegment(FeatureSavingAnalysisTask): - """ An analysis task that determines the boundaries of features in the image data in each field of view using a the specified machine learning From bdd35e63aff06b7c180acb41ed5c466a6ec8ed4b Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:44:45 -0500 Subject: [PATCH 338/419] pep8 compliance --- merlin/analysis/segment.py | 113 +++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index a04615f6..e2d082d0 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -239,8 +239,121 @@ class MachineLearningSegment(FeatureSavingAnalysisTask): TODO: implement unets / Ilastik """ +<<<<<<< HEAD def __init__(self, dataSet, parameters=None, analysisName=None): super().__init__(dataSet, parameters, analysisName) +======= + imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) + for z in range(len(self.dataSet. + get_z_positions()))]) + + # generate nuclei mask based on thresholding + thresholdingMask = np.zeros(imageStack.shape) + coarseBlockSize = 241 + fineBlockSize = 61 + for z in range(len(self.dataSet.get_z_positions())): + coarseThresholdingMask = imageStack[:, :, z] > + threshold_local(imageStack[:, :, z], coarseBlockSize, offset=0) + fineThresholdingMask = imageStack[:, :, z] > + threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) + thresholdingMask[:, :, z] = coarseThresholdingMask * + fineThresholdingMask + thresholdingMask[:, :, z] = binary_fill_holes( + thresholdingMask[:, :, z]) + + # generate border mask, necessary to avoid making a single + # connected component when using binary_fill_holes below + borderMask = np.zeros((2048, 2048)) + borderMask[25:2023, 25:2023] = 1 + + # TODO - use the image size variable for borderMask + + # generate nuclei mask from hessian, fine + fineHessianMask = np.zeros(imageStack.shape) + for z in range(len(self.dataSet.get_z_positions())): + fineHessian = hessian(imageStack[:, :, z]) + fineHessianMask[:, :, z] = fineHessian == fineHessian.max() + fineHessianMask[:, :, z] = binary_closing(fineHessianMask[:, :, z], + selem.disk(5)) + fineHessianMask[:, :, z] = fineHessianMask[:, :, z]*borderMask + fineHessianMask[:, :, z] = binary_fill_holes( + fineHessianMask[:, :, z]) + + # generate dapi mask from hessian, coarse + coarseHessianMask = np.zeros(imageStack.shape) + for z in range(len(self.dataSet.get_z_positions())): + coarseHessian = hessian(imageStack[:, :, z] - + white_tophat(imageStack[:, :, z], + selem.disk(20))) + coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() + coarseHessianMask[:, :, z] = binary_closing( + coarseHessianMask[:, :, z], selem.disk(5)) + coarseHessianMask[:, :, z] = coarseHessianMask[:, :, z]*borderMask + coarseHessianMask[:, :, z] = binary_fill_holes( + coarseHessianMask[:, :, z]) + + # combine masks + nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask + return binary_fill_holes(nucleiMask) + + def _get_watershed_markers(self, nucleiMask: np.ndarray, + membraneMask: np.ndarray) -> np.ndarray: + watershedMarker = np.zeros(nucleiMask.shape) + + for z in range(len(self.dataSet.get_z_positions())): + + # generate areas of sure bg and fg, as well as the area of + # unknown classification + background = sm.dilation(nucleiMask[:, :, z], sm.selem.disk(15)) + membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), + sm.selem.disk(10)) + foreground = sm.erosion(nucleiMask[:, :, z]*~membraneDilated, + sm.selem.disk(5)) + unknown = background*~foreground + + background = np.uint8(background)*255 + foreground = np.uint8(foreground)*255 + unknown = np.uint8(unknown)*255 + + # Marker labelling + ret, markers = cv2.connectedComponents(foreground) + + # Add one to all labels so that sure background is not 0, but 1 + markers = markers+100 + + # Now, mark the region of unknown with zero + markers[unknown==255] = 0 + + watershedMarker[:, :, z] = markers + + return watershedMarker + + def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: + # cv2 only works in 3D images of 8bit. Make a 3D grayscale by + # using the same grayscale image in each of the rgb channels + # code below based on https://stackoverflow.com/questions/ + # 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv + + # invert image + uint16Image = 2**16 - uint16Image + + # convert to uint8 + ratio = np.amax(uint16Image) / 256 + uint8Image = (uint16Image / ratio).astype('uint8') + + rgbImage = np.zeros((2048, 2048, 3)) + rgbImage[:, :, 0] = uint8Image + rgbImage[:, :, 1] = uint8Image + rgbImage[:, :, 2] = uint8Image + rgbImage = rgbImage.astype('uint8') + + return rgbImage + + def _apply_watershed(self, fov: int, channelIndex: int, + watershedMarkers: np.ndarray) -> np.ndarray: + warpTask = self.dataSet.load_analysis_task( + self.parameters['warp_task']) +>>>>>>> pep8 compliance if 'method' not in self.parameters: self.parameters['method'] = 'cellpose' From 3fcf517d73ba61d3122d9d968027a4c2f08df36d Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:48:35 -0500 Subject: [PATCH 339/419] pep8 compliance --- merlin/analysis/segment.py | 113 ------------------------------------- 1 file changed, 113 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index e2d082d0..a04615f6 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -239,121 +239,8 @@ class MachineLearningSegment(FeatureSavingAnalysisTask): TODO: implement unets / Ilastik """ -<<<<<<< HEAD def __init__(self, dataSet, parameters=None, analysisName=None): super().__init__(dataSet, parameters, analysisName) -======= - imageStack = np.array([warpTask.get_aligned_image(fov, channelIndex, z) - for z in range(len(self.dataSet. - get_z_positions()))]) - - # generate nuclei mask based on thresholding - thresholdingMask = np.zeros(imageStack.shape) - coarseBlockSize = 241 - fineBlockSize = 61 - for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], coarseBlockSize, offset=0) - fineThresholdingMask = imageStack[:, :, z] > - threshold_local(imageStack[:, :, z], fineBlockSize, offset=0) - thresholdingMask[:, :, z] = coarseThresholdingMask * - fineThresholdingMask - thresholdingMask[:, :, z] = binary_fill_holes( - thresholdingMask[:, :, z]) - - # generate border mask, necessary to avoid making a single - # connected component when using binary_fill_holes below - borderMask = np.zeros((2048, 2048)) - borderMask[25:2023, 25:2023] = 1 - - # TODO - use the image size variable for borderMask - - # generate nuclei mask from hessian, fine - fineHessianMask = np.zeros(imageStack.shape) - for z in range(len(self.dataSet.get_z_positions())): - fineHessian = hessian(imageStack[:, :, z]) - fineHessianMask[:, :, z] = fineHessian == fineHessian.max() - fineHessianMask[:, :, z] = binary_closing(fineHessianMask[:, :, z], - selem.disk(5)) - fineHessianMask[:, :, z] = fineHessianMask[:, :, z]*borderMask - fineHessianMask[:, :, z] = binary_fill_holes( - fineHessianMask[:, :, z]) - - # generate dapi mask from hessian, coarse - coarseHessianMask = np.zeros(imageStack.shape) - for z in range(len(self.dataSet.get_z_positions())): - coarseHessian = hessian(imageStack[:, :, z] - - white_tophat(imageStack[:, :, z], - selem.disk(20))) - coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() - coarseHessianMask[:, :, z] = binary_closing( - coarseHessianMask[:, :, z], selem.disk(5)) - coarseHessianMask[:, :, z] = coarseHessianMask[:, :, z]*borderMask - coarseHessianMask[:, :, z] = binary_fill_holes( - coarseHessianMask[:, :, z]) - - # combine masks - nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask - return binary_fill_holes(nucleiMask) - - def _get_watershed_markers(self, nucleiMask: np.ndarray, - membraneMask: np.ndarray) -> np.ndarray: - watershedMarker = np.zeros(nucleiMask.shape) - - for z in range(len(self.dataSet.get_z_positions())): - - # generate areas of sure bg and fg, as well as the area of - # unknown classification - background = sm.dilation(nucleiMask[:, :, z], sm.selem.disk(15)) - membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), - sm.selem.disk(10)) - foreground = sm.erosion(nucleiMask[:, :, z]*~membraneDilated, - sm.selem.disk(5)) - unknown = background*~foreground - - background = np.uint8(background)*255 - foreground = np.uint8(foreground)*255 - unknown = np.uint8(unknown)*255 - - # Marker labelling - ret, markers = cv2.connectedComponents(foreground) - - # Add one to all labels so that sure background is not 0, but 1 - markers = markers+100 - - # Now, mark the region of unknown with zero - markers[unknown==255] = 0 - - watershedMarker[:, :, z] = markers - - return watershedMarker - - def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: - # cv2 only works in 3D images of 8bit. Make a 3D grayscale by - # using the same grayscale image in each of the rgb channels - # code below based on https://stackoverflow.com/questions/ - # 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv - - # invert image - uint16Image = 2**16 - uint16Image - - # convert to uint8 - ratio = np.amax(uint16Image) / 256 - uint8Image = (uint16Image / ratio).astype('uint8') - - rgbImage = np.zeros((2048, 2048, 3)) - rgbImage[:, :, 0] = uint8Image - rgbImage[:, :, 1] = uint8Image - rgbImage[:, :, 2] = uint8Image - rgbImage = rgbImage.astype('uint8') - - return rgbImage - - def _apply_watershed(self, fov: int, channelIndex: int, - watershedMarkers: np.ndarray) -> np.ndarray: - warpTask = self.dataSet.load_analysis_task( - self.parameters['warp_task']) ->>>>>>> pep8 compliance if 'method' not in self.parameters: self.parameters['method'] = 'cellpose' From 5612c6c9ec850bb52e800f66b7f692670918ea8b Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:53:48 -0500 Subject: [PATCH 340/419] pep8 compliance --- merlin/analysis/segment.py | 1 + 1 file changed, 1 insertion(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index a04615f6..bf6d51c9 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -309,6 +309,7 @@ def _run_analysis(self, fragmentIndex): featureDB.write_features(featureList, fragmentIndex) def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: + warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) return np.array([warpTask.get_aligned_image(fov, channelIndex, z) From 04bca44d1c893107bb84dab7753900458d9ad9ee Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:56:41 -0500 Subject: [PATCH 341/419] pep8 compliance --- merlin/analysis/segment.py | 1 - 1 file changed, 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index bf6d51c9..a04615f6 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -309,7 +309,6 @@ def _run_analysis(self, fragmentIndex): featureDB.write_features(featureList, fragmentIndex) def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: - warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) return np.array([warpTask.get_aligned_image(fov, channelIndex, z) From 85b408e6e585a96c16f4c350061b78428e39acac Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 11:59:20 -0500 Subject: [PATCH 342/419] pep8 compliance --- merlin/analysis/segment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index a04615f6..59244def 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -485,4 +485,4 @@ def _run_analysis(self): df = self.segmentTask.get_feature_database().read_feature_metadata() self.dataSet.save_dataframe_to_csv(df, 'feature_metadata', - self.analysisName) + self.analysisName) \ No newline at end of file From ba00486581f01d3e57672e644aee4014e3fa19e6 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 12:00:52 -0500 Subject: [PATCH 343/419] pep8 compliance --- merlin/analysis/segment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 59244def..a04615f6 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -485,4 +485,4 @@ def _run_analysis(self): df = self.segmentTask.get_feature_database().read_feature_metadata() self.dataSet.save_dataframe_to_csv(df, 'feature_metadata', - self.analysisName) \ No newline at end of file + self.analysisName) From 70cc46a98f307d51676442e203961732cb08ebe6 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 14:10:20 -0500 Subject: [PATCH 344/419] pep8 compliance --- merlin/analysis/segment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index a04615f6..59244def 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -485,4 +485,4 @@ def _run_analysis(self): df = self.segmentTask.get_feature_database().read_feature_metadata() self.dataSet.save_dataframe_to_csv(df, 'feature_metadata', - self.analysisName) + self.analysisName) \ No newline at end of file From 1be3c3d13b62228784c478675801e488a5c7b7ef Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 14:12:19 -0500 Subject: [PATCH 345/419] pep8 compliance --- merlin/analysis/segment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 59244def..a04615f6 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -485,4 +485,4 @@ def _run_analysis(self): df = self.segmentTask.get_feature_database().read_feature_metadata() self.dataSet.save_dataframe_to_csv(df, 'feature_metadata', - self.analysisName) \ No newline at end of file + self.analysisName) From 6f5ab0ca54b7d63fae3c073ed43538ee769cffaa Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 16:44:01 -0500 Subject: [PATCH 346/419] cleaned watershed --- merlin/analysis/segment.py | 212 +++++++++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index a04615f6..752bf9ba 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -314,6 +314,218 @@ def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: return np.array([warpTask.get_aligned_image(fov, channelIndex, z) for z in range(len(self.dataSet.get_z_positions()))]) +<<<<<<< HEAD +======= + def _get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: + # generate mask based on thresholding + mask = np.zeros(membraneImages.shape) + fineBlockSize = 61 + for z in range(len(self.dataSet.get_z_positions())): + mask[:, :, z] = (membraneImages[:, :, z] > + threshold_local(membraneImages[:, :, z], + fineBlockSize, + offset=0)) + mask[:, :, z] = remove_small_objects(membraneImages[:, :, z]. + astype('bool'), + min_size=100, + connectivity=1) + mask[:, :, z] = binary_closing(membraneImages[:, :, z], + selem.disk(5)) + mask[:, :, z] = skeletonize(membraneImages[:, :, z]) + + # combine masks + return mask + + def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: + # generate nuclei mask based on thresholding + thresholdingMask = np.zeros(nucleiImages.shape) + coarseBlockSize = 241 + fineBlockSize = 61 + for z in range(len(self.dataSet.get_z_positions())): + coarseThresholdingMask = (nucleiImages[:, :, z] > + threshold_local(nucleiImages[:, :, z], + coarseBlockSize, + offset=0)) + fineThresholdingMask = (nucleiImages[:, :, z] > + threshold_local(nucleiImages[:, :, z], + fineBlockSize, + offset=0)) + thresholdingMask[:, :, z] = (coarseThresholdingMask * + fineThresholdingMask) + thresholdingMask[:, :, z] = binary_fill_holes( + thresholdingMask[:, :, z]) + + # generate border mask, necessary to avoid making a single + # connected component when using binary_fill_holes below + borderMask = np.zeros((2048, 2048)) + borderMask[25:2023, 25:2023] = 1 + + # TODO - use the image size variable for borderMask + + # generate nuclei mask from hessian, fine + fineHessianMask = np.zeros(nucleiImages.shape) + for z in range(len(self.dataSet.get_z_positions())): + fineHessian = hessian(nucleiImages[:, :, z]) + fineHessianMask[:, :, z] = fineHessian == fineHessian.max() + fineHessianMask[:, :, z] = binary_closing(fineHessianMask[:, :, z], + selem.disk(5)) + fineHessianMask[:, :, z] = fineHessianMask[:, :, z] * borderMask + fineHessianMask[:, :, z] = binary_fill_holes( + fineHessianMask[:, :, z]) + + # generate dapi mask from hessian, coarse + coarseHessianMask = np.zeros(nucleiImages.shape) + for z in range(len(self.dataSet.get_z_positions())): + coarseHessian = hessian(nucleiImages[:, :, z] - + white_tophat(nucleiImages[:, :, z], + selem.disk(20))) + coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() + coarseHessianMask[:, :, z] = binary_closing( + coarseHessianMask[:, :, z], selem.disk(5)) + coarseHessianMask[:, :, z] = (coarseHessianMask[:, :, z] * + borderMask) + coarseHessianMask[:, :, z] = binary_fill_holes( + coarseHessianMask[:, :, z]) + + # combine masks + nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask + return binary_fill_holes(nucleiMask) + + def _get_watershed_markers(self, nucleiImages: np.ndarray, + membraneImages: np.ndarray) -> np.ndarray: + + nucleiMask = self._get_nuclei_mask(nucleiImages) + membraneMask = self._get_membrane_mask(membraneImages) + + watershedMarker = np.zeros(nucleiMask.shape) + + for z in range(len(self.dataSet.get_z_positions())): + + # generate areas of sure bg and fg, as well as the area of + # unknown classification + background = sm.dilation(nucleiMask[:, :, z], sm.selem.disk(15)) + membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), + sm.selem.disk(10)) + foreground = sm.erosion(nucleiMask[:, :, z] * ~ membraneDilated, + sm.selem.disk(5)) + unknown = background * ~ foreground + + background = np.uint8(background) * 255 + foreground = np.uint8(foreground) * 255 + unknown = np.uint8(unknown) * 255 + + # Marker labelling + ret, markers = cv2.connectedComponents(foreground) + + # Add one to all labels so that sure background is not 0, but 1 + markers = markers + 100 + + # Now, mark the region of unknown with zero + markers[unknown == 255] = 0 + + watershedMarker[:, :, z] = markers + + return watershedMarker + + def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: + # cv2 only works in 3D images of 8bit. Make a 3D grayscale by + # using the same grayscale image in each of the rgb channels + # code below based on https://stackoverflow.com/questions/ + # 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv + + # invert image + uint16Image = 2**16 - uint16Image + + # convert to uint8 + ratio = np.amax(uint16Image) / 256 + uint8Image = (uint16Image / ratio).astype('uint8') + + rgbImage = np.zeros((2048, 2048, 3)) + rgbImage[:, :, 0] = uint8Image + rgbImage[:, :, 1] = uint8Image + rgbImage[:, :, 2] = uint8Image + rgbImage = rgbImage.astype('uint8') + + return rgbImage + + def _apply_watershed(self, nucleiImages: np.ndarray, + watershedMarkers: np.ndarray) -> np.ndarray: + + watershedOutput = np.zeros(watershedMarkers.shape) + for z in range(len(self.dataSet.get_z_positions())): + rgbImage = _convert_grayscale_to_rgb(nucleiImages[:, :, z]) + watershedOutput[:, :, z] = cv2.watershed(rgbImage, + watershedMarkers[:, :, z]. + astype('int32')) + watershedOutput[:, :, z][watershedOutput[:, :, z] <= 100] = 0 + + return watershedOutput + + def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, + watershedZ1: np.ndarray, n0: int): + z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) + z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes > 100] + + if z1NucleiIndexes.shape[0] > 0: + + # calculate overlap fraction + n0Area = np.count_nonzero(watershedZ0 == n0) + n1Area = np.zeros(len(z1NucleiIndexes)) + overlapArea = np.zeros(len(z1NucleiIndexes)) + + for ii in range(len(z1NucleiIndexes)): + n1 = z1NucleiIndexes[ii] + n1Area[ii] = np.count_nonzero(watershedZ1 == n1) + overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * + (watershedZ1 == n1)) + + n0OverlapFraction = np.asarray(overlapArea / n0Area) + n1OverlapFraction = np.asarray(overlapArea / n1Area) + index = list(range(len(n0OverlapFraction))) + + # select the nuclei that has the highest fraction in n0 and n1 + r1, r2, indexSorted = zip(*sorted(zip(n0OverlapFraction, + n1OverlapFraction, + index), + reverse=True)) + + if (n0OverlapFraction[indexSorted[0]] > 0.2 and + n1OverlapFraction[indexSorted[0]] > 0.5): + return m1NucleiIndexes[indexSorted[0]], + n0OverlapFraction[indexSorted[0]], + n1OverlapFraction[indexSorted[0]] + else: + return False, False, False + else: + return False, False, False + + def _combine_watershed_z_positions(self, + watershedOutput: + np.ndarray) -> np.ndarray: + + # TO DO: this implementation is very rough, needs to be improved. + # good just for testing purposes + + # Initialize empty array with size as watershedOutput array + watershedCombinedZ = np.zeros(watershedOutput.shape) + + # copy the mask of the section farthest to the coverslip + watershedCombinedZ[:, :, -1] = watershedOutput[:, :, -1] + + # starting far from coverslip + for z in range(len(self.dataSet.get_z_positions())-1, 0, -1): + zNucleiIndex = np.unique(watershedOutput[:, :, z])[ + np.unique(watershedOutput[:, :, z]) > 100] + + for n0 in zNucleiIndex: + n1, f0, f1 = _get_overlapping_nuclei(watershedCombinedZ[:, :, z], + watershedOutput[:, :, z-1], + n0) + if n1: + watershedCombinedZ[:, :, z-1][(watershedOutput[:, :, z-1] == + n1)] = n0 + return watershedCombinedZ +>>>>>>> cleaned watershed class CleanCellBoundaries(analysistask.ParallelAnalysisTask): ''' From 5fd0ab03c7eb554ee3bb95e7728498102cb4b9fd Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 16:47:37 -0500 Subject: [PATCH 347/419] pep8 compliance --- merlin/analysis/segment.py | 212 ------------------------------------- 1 file changed, 212 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 752bf9ba..a04615f6 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -314,218 +314,6 @@ def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: return np.array([warpTask.get_aligned_image(fov, channelIndex, z) for z in range(len(self.dataSet.get_z_positions()))]) -<<<<<<< HEAD -======= - def _get_membrane_mask(self, membraneImages: np.ndarray) -> np.ndarray: - # generate mask based on thresholding - mask = np.zeros(membraneImages.shape) - fineBlockSize = 61 - for z in range(len(self.dataSet.get_z_positions())): - mask[:, :, z] = (membraneImages[:, :, z] > - threshold_local(membraneImages[:, :, z], - fineBlockSize, - offset=0)) - mask[:, :, z] = remove_small_objects(membraneImages[:, :, z]. - astype('bool'), - min_size=100, - connectivity=1) - mask[:, :, z] = binary_closing(membraneImages[:, :, z], - selem.disk(5)) - mask[:, :, z] = skeletonize(membraneImages[:, :, z]) - - # combine masks - return mask - - def _get_nuclei_mask(self, nucleiImages: np.ndarray) -> np.ndarray: - # generate nuclei mask based on thresholding - thresholdingMask = np.zeros(nucleiImages.shape) - coarseBlockSize = 241 - fineBlockSize = 61 - for z in range(len(self.dataSet.get_z_positions())): - coarseThresholdingMask = (nucleiImages[:, :, z] > - threshold_local(nucleiImages[:, :, z], - coarseBlockSize, - offset=0)) - fineThresholdingMask = (nucleiImages[:, :, z] > - threshold_local(nucleiImages[:, :, z], - fineBlockSize, - offset=0)) - thresholdingMask[:, :, z] = (coarseThresholdingMask * - fineThresholdingMask) - thresholdingMask[:, :, z] = binary_fill_holes( - thresholdingMask[:, :, z]) - - # generate border mask, necessary to avoid making a single - # connected component when using binary_fill_holes below - borderMask = np.zeros((2048, 2048)) - borderMask[25:2023, 25:2023] = 1 - - # TODO - use the image size variable for borderMask - - # generate nuclei mask from hessian, fine - fineHessianMask = np.zeros(nucleiImages.shape) - for z in range(len(self.dataSet.get_z_positions())): - fineHessian = hessian(nucleiImages[:, :, z]) - fineHessianMask[:, :, z] = fineHessian == fineHessian.max() - fineHessianMask[:, :, z] = binary_closing(fineHessianMask[:, :, z], - selem.disk(5)) - fineHessianMask[:, :, z] = fineHessianMask[:, :, z] * borderMask - fineHessianMask[:, :, z] = binary_fill_holes( - fineHessianMask[:, :, z]) - - # generate dapi mask from hessian, coarse - coarseHessianMask = np.zeros(nucleiImages.shape) - for z in range(len(self.dataSet.get_z_positions())): - coarseHessian = hessian(nucleiImages[:, :, z] - - white_tophat(nucleiImages[:, :, z], - selem.disk(20))) - coarseHessianMask[:, :, z] = coarseHessian == coarseHessian.max() - coarseHessianMask[:, :, z] = binary_closing( - coarseHessianMask[:, :, z], selem.disk(5)) - coarseHessianMask[:, :, z] = (coarseHessianMask[:, :, z] * - borderMask) - coarseHessianMask[:, :, z] = binary_fill_holes( - coarseHessianMask[:, :, z]) - - # combine masks - nucleiMask = thresholdingMask + fineHessianMask + coarseHessianMask - return binary_fill_holes(nucleiMask) - - def _get_watershed_markers(self, nucleiImages: np.ndarray, - membraneImages: np.ndarray) -> np.ndarray: - - nucleiMask = self._get_nuclei_mask(nucleiImages) - membraneMask = self._get_membrane_mask(membraneImages) - - watershedMarker = np.zeros(nucleiMask.shape) - - for z in range(len(self.dataSet.get_z_positions())): - - # generate areas of sure bg and fg, as well as the area of - # unknown classification - background = sm.dilation(nucleiMask[:, :, z], sm.selem.disk(15)) - membraneDilated = sm.dilation(membraneMask[:, :, z].astype('bool'), - sm.selem.disk(10)) - foreground = sm.erosion(nucleiMask[:, :, z] * ~ membraneDilated, - sm.selem.disk(5)) - unknown = background * ~ foreground - - background = np.uint8(background) * 255 - foreground = np.uint8(foreground) * 255 - unknown = np.uint8(unknown) * 255 - - # Marker labelling - ret, markers = cv2.connectedComponents(foreground) - - # Add one to all labels so that sure background is not 0, but 1 - markers = markers + 100 - - # Now, mark the region of unknown with zero - markers[unknown == 255] = 0 - - watershedMarker[:, :, z] = markers - - return watershedMarker - - def _convert_grayscale_to_rgb(self, uint16Image: np.ndarray) -> np.ndarray: - # cv2 only works in 3D images of 8bit. Make a 3D grayscale by - # using the same grayscale image in each of the rgb channels - # code below based on https://stackoverflow.com/questions/ - # 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv - - # invert image - uint16Image = 2**16 - uint16Image - - # convert to uint8 - ratio = np.amax(uint16Image) / 256 - uint8Image = (uint16Image / ratio).astype('uint8') - - rgbImage = np.zeros((2048, 2048, 3)) - rgbImage[:, :, 0] = uint8Image - rgbImage[:, :, 1] = uint8Image - rgbImage[:, :, 2] = uint8Image - rgbImage = rgbImage.astype('uint8') - - return rgbImage - - def _apply_watershed(self, nucleiImages: np.ndarray, - watershedMarkers: np.ndarray) -> np.ndarray: - - watershedOutput = np.zeros(watershedMarkers.shape) - for z in range(len(self.dataSet.get_z_positions())): - rgbImage = _convert_grayscale_to_rgb(nucleiImages[:, :, z]) - watershedOutput[:, :, z] = cv2.watershed(rgbImage, - watershedMarkers[:, :, z]. - astype('int32')) - watershedOutput[:, :, z][watershedOutput[:, :, z] <= 100] = 0 - - return watershedOutput - - def _get_overlapping_nuclei(self, watershedZ0: np.ndarray, - watershedZ1: np.ndarray, n0: int): - z1NucleiIndexes = np.unique(watershedZ1[watershedZ0 == n0]) - z1NucleiIndexes = z1NucleiIndexes[z1NucleiIndexes > 100] - - if z1NucleiIndexes.shape[0] > 0: - - # calculate overlap fraction - n0Area = np.count_nonzero(watershedZ0 == n0) - n1Area = np.zeros(len(z1NucleiIndexes)) - overlapArea = np.zeros(len(z1NucleiIndexes)) - - for ii in range(len(z1NucleiIndexes)): - n1 = z1NucleiIndexes[ii] - n1Area[ii] = np.count_nonzero(watershedZ1 == n1) - overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * - (watershedZ1 == n1)) - - n0OverlapFraction = np.asarray(overlapArea / n0Area) - n1OverlapFraction = np.asarray(overlapArea / n1Area) - index = list(range(len(n0OverlapFraction))) - - # select the nuclei that has the highest fraction in n0 and n1 - r1, r2, indexSorted = zip(*sorted(zip(n0OverlapFraction, - n1OverlapFraction, - index), - reverse=True)) - - if (n0OverlapFraction[indexSorted[0]] > 0.2 and - n1OverlapFraction[indexSorted[0]] > 0.5): - return m1NucleiIndexes[indexSorted[0]], - n0OverlapFraction[indexSorted[0]], - n1OverlapFraction[indexSorted[0]] - else: - return False, False, False - else: - return False, False, False - - def _combine_watershed_z_positions(self, - watershedOutput: - np.ndarray) -> np.ndarray: - - # TO DO: this implementation is very rough, needs to be improved. - # good just for testing purposes - - # Initialize empty array with size as watershedOutput array - watershedCombinedZ = np.zeros(watershedOutput.shape) - - # copy the mask of the section farthest to the coverslip - watershedCombinedZ[:, :, -1] = watershedOutput[:, :, -1] - - # starting far from coverslip - for z in range(len(self.dataSet.get_z_positions())-1, 0, -1): - zNucleiIndex = np.unique(watershedOutput[:, :, z])[ - np.unique(watershedOutput[:, :, z]) > 100] - - for n0 in zNucleiIndex: - n1, f0, f1 = _get_overlapping_nuclei(watershedCombinedZ[:, :, z], - watershedOutput[:, :, z-1], - n0) - if n1: - watershedCombinedZ[:, :, z-1][(watershedOutput[:, :, z-1] == - n1)] = n0 - return watershedCombinedZ ->>>>>>> cleaned watershed class CleanCellBoundaries(analysistask.ParallelAnalysisTask): ''' From 70b0ee46f2f992d05f5ddf09db98a88aa19c729a Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 17:05:54 -0500 Subject: [PATCH 348/419] fixing invalid syntax --- merlin/analysis/segment.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index a04615f6..d158ab96 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -181,17 +181,30 @@ def _run_analysis(self, fragmentIndex): globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) +<<<<<<< HEAD # read membrane and compartment indexes +======= + # read membrane (seed) and nuclei (watershed) indexes +>>>>>>> fixing invalid syntax membraneIndex = self.dataSet \ .get_data_organization() \ .get_data_channel_index( self.parameters['membrane_channel_name']) +<<<<<<< HEAD compartmentIndex = self.dataSet \ .get_data_organization() \ .get_data_channel_index( self.parameters['compartment_channel_name']) # read membrane and compartment images +======= + nucleiIndex = self.dataSet \ + .get_data_organization() \ + .get_data_channel_index( + self.parameters['nuclei_channel_name']) + + # read membrane (seed) and nuclei (watershed) images +>>>>>>> fixing invalid syntax membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) compartmentImages = self._read_image_stack(fragmentIndex, compartmentIndex) From 983f803a067c9bed4fa4ba11c1de4797e5063eb7 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Jan 2020 17:53:10 -0500 Subject: [PATCH 349/419] correct skimage function names --- merlin/analysis/segment.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index d158ab96..a04615f6 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -181,30 +181,17 @@ def _run_analysis(self, fragmentIndex): globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) -<<<<<<< HEAD # read membrane and compartment indexes -======= - # read membrane (seed) and nuclei (watershed) indexes ->>>>>>> fixing invalid syntax membraneIndex = self.dataSet \ .get_data_organization() \ .get_data_channel_index( self.parameters['membrane_channel_name']) -<<<<<<< HEAD compartmentIndex = self.dataSet \ .get_data_organization() \ .get_data_channel_index( self.parameters['compartment_channel_name']) # read membrane and compartment images -======= - nucleiIndex = self.dataSet \ - .get_data_organization() \ - .get_data_channel_index( - self.parameters['nuclei_channel_name']) - - # read membrane (seed) and nuclei (watershed) images ->>>>>>> fixing invalid syntax membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) compartmentImages = self._read_image_stack(fragmentIndex, compartmentIndex) From c2b1340847ebf1cdc9eebd34bb725d47f667d863 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sun, 26 Jan 2020 09:04:09 -0500 Subject: [PATCH 350/419] changed Image dimension order to fit MERlin's --- merlin/analysis/segment.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index a04615f6..db662875 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -196,6 +196,9 @@ def _run_analysis(self, fragmentIndex): compartmentImages = self._read_image_stack(fragmentIndex, compartmentIndex) + print('membraneImages = ' + str(membraneImages.shape)) + print('nucleiImages = ' + str(nucleiImages.shape)) + # Prepare masks for cv2 watershed watershedMarkers = segmentation.get_cv2_watershed_markers( compartmentImages, From 083db37f07c056d2189119008b135acc50448759 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sun, 26 Jan 2020 13:50:42 -0500 Subject: [PATCH 351/419] adding print statements for debugging --- merlin/analysis/segment.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index db662875..fe1e703f 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -18,7 +18,6 @@ import networkx as nx import time - class FeatureSavingAnalysisTask(analysistask.ParallelAnalysisTask): """ @@ -196,8 +195,9 @@ def _run_analysis(self, fragmentIndex): compartmentImages = self._read_image_stack(fragmentIndex, compartmentIndex) - print('membraneImages = ' + str(membraneImages.shape)) - print('nucleiImages = ' + str(nucleiImages.shape)) + endTime = time.time() + print("Images read, ET {:.2f} min" \ + .format((endTime - startTime) / 60)) # Prepare masks for cv2 watershed watershedMarkers = segmentation.get_cv2_watershed_markers( @@ -206,10 +206,18 @@ def _run_analysis(self, fragmentIndex): self.parameters['compartment_channel_name'], self.parameters['membrane_channel_name']) + endTime = time.time() + print("Markers calculated, ET {:.2f} min" \ + .format((endTime - startTime) / 60)) + # perform watershed in individual z positions watershedOutput = segmentation.apply_cv2_watershed(compartmentImages, watershedMarkers) + endTime = time.time() + print("watershed calculated, ET {:.2f} min" \ + .format((endTime - startTime) / 60)) + # combine all z positions in watershed watershedCombinedOutput = segmentation \ .combine_2d_segmentation_masks_into_3d(watershedOutput) @@ -311,6 +319,10 @@ def _run_analysis(self, fragmentIndex): featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) + endTime = time.time() + print("features written, ET {:.2f} min" \ + .format((endTime - startTime) / 60)) + def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) From 54424adf97d150d45204b9161fb97af8383b097f Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 15:29:03 -0500 Subject: [PATCH 352/419] moving utility functions from segment.py to watershed.py --- merlin/util/watershed.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 762c3e4d..e9afab0e 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -330,7 +330,6 @@ def get_cv2_watershed_markers(compartmentImages: np.ndarray, return watershedMarker - def convert_grayscale_to_rgb(uint16Image: np.ndarray) -> np.ndarray: """Convert a 16 bit 2D grayscale image into a 3D 8-bit RGB image. cv2 only works in 8-bit. Based on https://stackoverflow.com/questions/ @@ -358,7 +357,6 @@ def convert_grayscale_to_rgb(uint16Image: np.ndarray) -> np.ndarray: return rgbImage - def apply_cv2_watershed(compartmentImages: np.ndarray, watershedMarkers: np.ndarray) -> np.ndarray: """Perform watershed using cv2 @@ -384,7 +382,6 @@ def apply_cv2_watershed(compartmentImages: np.ndarray, return watershedOutput - def get_overlapping_objects(watershedZ0: np.ndarray, watershedZ1: np.ndarray, n0: int): """Perform watershed using cv2 @@ -439,7 +436,6 @@ def get_overlapping_objects(watershedZ0: np.ndarray, else: return False, False, False - def combine_2d_segmentation_masks_into_3d(watershedOutput: np.ndarray) -> np.ndarray: """Take a 3 dimensional watershed masks and relabel them so that From 7c5b0fc6429ca84116ab339c9b9d126fea0a1a05 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 15:30:14 -0500 Subject: [PATCH 353/419] removing utility functions from segment.py --- merlin/analysis/segment.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index fe1e703f..4577f24e 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -177,6 +177,8 @@ def get_cell_boundaries(self) -> List[spatialfeature.SpatialFeature]: def _run_analysis(self, fragmentIndex): startTime = time.time() + print('Entered the _run_analysis method, FOV ' + str(fragmentIndex) ) + globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) @@ -196,7 +198,7 @@ def _run_analysis(self, fragmentIndex): compartmentIndex) endTime = time.time() - print("Images read, ET {:.2f} min" \ + print(" images read, ET {:.2f} min" \ .format((endTime - startTime) / 60)) # Prepare masks for cv2 watershed @@ -207,7 +209,7 @@ def _run_analysis(self, fragmentIndex): self.parameters['membrane_channel_name']) endTime = time.time() - print("Markers calculated, ET {:.2f} min" \ + print(" markers calculated, ET {:.2f} min" \ .format((endTime - startTime) / 60)) # perform watershed in individual z positions @@ -215,7 +217,7 @@ def _run_analysis(self, fragmentIndex): watershedMarkers) endTime = time.time() - print("watershed calculated, ET {:.2f} min" \ + print(" watershed calculated, ET {:.2f} min" \ .format((endTime - startTime) / 60)) # combine all z positions in watershed @@ -320,7 +322,7 @@ def _run_analysis(self, fragmentIndex): featureDB.write_features(featureList, fragmentIndex) endTime = time.time() - print("features written, ET {:.2f} min" \ + print(" features written, ET {:.2f} min" \ .format((endTime - startTime) / 60)) def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: From 5bccb0e361a44f7d651a450665e244dfba1dddb0 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 16:39:26 -0500 Subject: [PATCH 354/419] add comments to the header of multiple methods. --- merlin/util/watershed.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index e9afab0e..05f2827c 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -204,7 +204,6 @@ def get_membrane_mask(membraneImages: np.ndarray, return mask - def get_compartment_mask(compartmentImages: np.ndarray) -> np.ndarray: """Calculate binary mask with 1's in compartment (nuclei or cytoplasm) pixels and 0 otherwise. The images expected are some type of compartment @@ -275,7 +274,6 @@ def get_compartment_mask(compartmentImages: np.ndarray) -> np.ndarray: compartmentMask = thresholdingMask + fineHessianMask + coarseHessianMask return binary_fill_holes(compartmentMask) - def get_cv2_watershed_markers(compartmentImages: np.ndarray, membraneImages: np.ndarray, membraneFlag: int) -> np.ndarray: From 373258c47127d79a2552e35ace37e6a4c24374ca Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 16:59:49 -0500 Subject: [PATCH 355/419] pep8 compliance --- merlin/analysis/segment.py | 10 +++++----- merlin/util/watershed.py | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 4577f24e..1e92b692 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -177,7 +177,7 @@ def get_cell_boundaries(self) -> List[spatialfeature.SpatialFeature]: def _run_analysis(self, fragmentIndex): startTime = time.time() - print('Entered the _run_analysis method, FOV ' + str(fragmentIndex) ) + print('Entered the _run_analysis method, FOV ' + str(fragmentIndex)) globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) @@ -199,7 +199,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" images read, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + .format((endTime - startTime) / 60)) # Prepare masks for cv2 watershed watershedMarkers = segmentation.get_cv2_watershed_markers( @@ -210,7 +210,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" markers calculated, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + .format((endTime - startTime) / 60)) # perform watershed in individual z positions watershedOutput = segmentation.apply_cv2_watershed(compartmentImages, @@ -218,7 +218,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" watershed calculated, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + .format((endTime - startTime) / 60)) # combine all z positions in watershed watershedCombinedOutput = segmentation \ @@ -323,7 +323,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" features written, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + .format((endTime - startTime) / 60)) def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 05f2827c..d9c5b6ec 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -156,6 +156,7 @@ def get_membrane_mask(membraneImages: np.ndarray, membraneChannelName: A string with the name of a membrane channel. compartmentChannelName: A string with the name of the compartment channel + Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) """ From a339af67ebf981eb1dcf8110cbeae7d50d50f84c Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 11 Feb 2020 17:11:46 -0500 Subject: [PATCH 356/419] pep8 compliance --- merlin/analysis/segment.py | 16 ++++++++-------- merlin/util/watershed.py | 1 - 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 1e92b692..b564ec4a 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -198,8 +198,8 @@ def _run_analysis(self, fragmentIndex): compartmentIndex) endTime = time.time() - print(" images read, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + print(" images read, ET {:.2f} min".format( + (endTime - startTime) / 60)) # Prepare masks for cv2 watershed watershedMarkers = segmentation.get_cv2_watershed_markers( @@ -209,16 +209,16 @@ def _run_analysis(self, fragmentIndex): self.parameters['membrane_channel_name']) endTime = time.time() - print(" markers calculated, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + print(" markers calculated, ET {:.2f} min".format( + (endTime - startTime) / 60)) # perform watershed in individual z positions watershedOutput = segmentation.apply_cv2_watershed(compartmentImages, watershedMarkers) endTime = time.time() - print(" watershed calculated, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + print(" watershed calculated, ET {:.2f} min".format( + (endTime - startTime) / 60)) # combine all z positions in watershed watershedCombinedOutput = segmentation \ @@ -322,8 +322,8 @@ def _run_analysis(self, fragmentIndex): featureDB.write_features(featureList, fragmentIndex) endTime = time.time() - print(" features written, ET {:.2f} min" \ - .format((endTime - startTime) / 60)) + print(" features written, ET {:.2f} min".format( + (endTime - startTime) / 60)) def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index d9c5b6ec..2a83dc64 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -142,7 +142,6 @@ def prepare_watershed_images(watershedImageStack: np.ndarray return normalizedWatershed, watershedMask - def get_membrane_mask(membraneImages: np.ndarray, membraneChannelName: str, compartmentChannelName: str) -> np.ndarray: From 63d34bf778abf443ac71cef22c4332550cc61d9c Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 12 Feb 2020 13:03:26 -0500 Subject: [PATCH 357/419] add george's modifications to dataportal --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c65e9715..dfc4de44 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,4 +28,4 @@ tables boto3 xmltodict google-cloud-storage -docutils<0.16,>=0.10 \ No newline at end of file +docutils<0.16,>=0.10 From 594cc10f56df25d9b7649998b4d4c3d081db15c3 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 18 Feb 2020 14:53:18 -0500 Subject: [PATCH 358/419] pep8 compliance --- merlin/util/dataportal.py | 1 + 1 file changed, 1 insertion(+) diff --git a/merlin/util/dataportal.py b/merlin/util/dataportal.py index bba38f97..c01e7a17 100755 --- a/merlin/util/dataportal.py +++ b/merlin/util/dataportal.py @@ -344,6 +344,7 @@ def get_sibling_with_extension(self, newExtension: str): def _error_tolerant_reading(self, method, startByte=None, endByte=None): + backoffSeries = [1, 2, 4, 8, 16, 32, 64, 128, 256] for sleepDuration in backoffSeries: try: From d65d4be5850d1a9ae04b58daee5fc1deb245949d Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Mon, 2 Mar 2020 14:03:44 -0500 Subject: [PATCH 359/419] added printing for debuging --- merlin/analysis/segment.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index b564ec4a..ce1c2171 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -200,6 +200,8 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) + print(" membraneImages Type: " + type(membraneImages)) + print(" nucleiImages Type: " + type(nucleiImages)) # Prepare masks for cv2 watershed watershedMarkers = segmentation.get_cv2_watershed_markers( From 3afd0c8645b4a881d9a6b34bd3ed72785a86634f Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Mon, 2 Mar 2020 14:26:51 -0500 Subject: [PATCH 360/419] added printing for debuging --- merlin/analysis/segment.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index ce1c2171..bae7e44e 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -200,8 +200,16 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) - print(" membraneImages Type: " + type(membraneImages)) - print(" nucleiImages Type: " + type(nucleiImages)) + print(" membraneImages Type: " + str(type(membraneImages))) + print(" membraneImages Size: [" + + str(membraneImages.shape[0]) + + "," + str(membraneImages.shape[1]) + + "," + str(membraneImages.shape[2]) "]" ) + print(" nucleiImages Type: " + str(type(nucleiImages))) + print(" nucleiImages Size: [" + + str(nucleiImages.shape[0]) + + "," + str(nucleiImages.shape[1]) + + "," + str(nucleiImages.shape[2]) "]" ) # Prepare masks for cv2 watershed watershedMarkers = segmentation.get_cv2_watershed_markers( From fa1dc3dac5f9accbcc2057351f06cd88b92e768d Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Mon, 2 Mar 2020 14:31:30 -0500 Subject: [PATCH 361/419] added printing for debuging --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index bae7e44e..63b624ec 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -204,12 +204,12 @@ def _run_analysis(self, fragmentIndex): print(" membraneImages Size: [" + str(membraneImages.shape[0]) + "," + str(membraneImages.shape[1]) - + "," + str(membraneImages.shape[2]) "]" ) + + "," + str(membraneImages.shape[2]) + "]" ) print(" nucleiImages Type: " + str(type(nucleiImages))) print(" nucleiImages Size: [" + str(nucleiImages.shape[0]) + "," + str(nucleiImages.shape[1]) - + "," + str(nucleiImages.shape[2]) "]" ) + + "," + str(nucleiImages.shape[2]) + "]" ) # Prepare masks for cv2 watershed watershedMarkers = segmentation.get_cv2_watershed_markers( From 8fdc54b3c2c35e28363b01da42606dcd27ce41af Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Mon, 2 Mar 2020 16:22:34 -0500 Subject: [PATCH 362/419] removing self calls --- merlin/util/watershed.py | 1 + 1 file changed, 1 insertion(+) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 2a83dc64..3b34fc1d 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -355,6 +355,7 @@ def convert_grayscale_to_rgb(uint16Image: np.ndarray) -> np.ndarray: return rgbImage +<<<<<<< HEAD def apply_cv2_watershed(compartmentImages: np.ndarray, watershedMarkers: np.ndarray) -> np.ndarray: """Perform watershed using cv2 From 42147612dac06d407b95b704321db935dbb23d56 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 3 Mar 2020 09:02:53 -0500 Subject: [PATCH 363/419] remove self calls --- merlin/util/watershed.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index 3b34fc1d..f4e4bb59 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -294,11 +294,9 @@ def get_cv2_watershed_markers(compartmentImages: np.ndarray, compartmentMask = get_compartment_mask(compartmentImages) membraneMask = get_membrane_mask(membraneImages, membraneFlag) - watershedMarker = np.zeros(compartmentMask.shape) for z in range(compartmentImages.shape[0]): - # generate areas of sure bg and fg, as well as the area of # unknown classification background = morphology.dilation(compartmentMask[z, :, :], @@ -355,7 +353,6 @@ def convert_grayscale_to_rgb(uint16Image: np.ndarray) -> np.ndarray: return rgbImage -<<<<<<< HEAD def apply_cv2_watershed(compartmentImages: np.ndarray, watershedMarkers: np.ndarray) -> np.ndarray: """Perform watershed using cv2 From 6eabe1d4111dca259fa03fea2f5afe60eff1eec4 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 3 Mar 2020 10:50:52 -0500 Subject: [PATCH 364/419] pep8 compliance --- merlin/analysis/segment.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 63b624ec..6abda652 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -201,15 +201,15 @@ def _run_analysis(self, fragmentIndex): print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) print(" membraneImages Type: " + str(type(membraneImages))) - print(" membraneImages Size: [" - + str(membraneImages.shape[0]) - + "," + str(membraneImages.shape[1]) - + "," + str(membraneImages.shape[2]) + "]" ) + print(" membraneImages Size: [" + + str(membraneImages.shape[0]) + + "," + str(membraneImages.shape[1]) + + "," + str(membraneImages.shape[2]) + "]") print(" nucleiImages Type: " + str(type(nucleiImages))) - print(" nucleiImages Size: [" - + str(nucleiImages.shape[0]) - + "," + str(nucleiImages.shape[1]) - + "," + str(nucleiImages.shape[2]) + "]" ) + print(" nucleiImages Size: [" + + str(nucleiImages.shape[0]) + + "," + str(nucleiImages.shape[1]) + + "," + str(nucleiImages.shape[2]) + "]") # Prepare masks for cv2 watershed watershedMarkers = segmentation.get_cv2_watershed_markers( From 26edb2d42b4025ff5ebe51349ded362b95a59290 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 22 Apr 2020 21:44:41 -0400 Subject: [PATCH 365/419] change WatershedSegmentNucleiCV2 to WatershedSegmentCV2, modify accordingly to allow for cytoplasmic or nuclei segmentation --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 6abda652..a234ac03 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -123,7 +123,7 @@ def _read_and_filter_image_stack(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions()))]) -class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): +class WatershedSegmentCV2(FeatureSavingAnalysisTask): """ An analysis task that determines the boundaries of features in the @@ -149,7 +149,7 @@ class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): def __init__(self, dataSet, parameters=None, analysisName=None): super().__init__(dataSet, parameters, analysisName) - + if 'membrane_channel_name' not in self.parameters: self.parameters['membrane_channel_name'] = 'DAPI' if 'compartment_channel_name' not in self.parameters: From f7beb3ac71d837544fc939c80e41877415906a87 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 22 Apr 2020 21:57:33 -0400 Subject: [PATCH 366/419] change nuclei to compartment --- merlin/analysis/segment.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index a234ac03..2e04b61d 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -205,11 +205,11 @@ def _run_analysis(self, fragmentIndex): + str(membraneImages.shape[0]) + "," + str(membraneImages.shape[1]) + "," + str(membraneImages.shape[2]) + "]") - print(" nucleiImages Type: " + str(type(nucleiImages))) - print(" nucleiImages Size: [" - + str(nucleiImages.shape[0]) - + "," + str(nucleiImages.shape[1]) - + "," + str(nucleiImages.shape[2]) + "]") + print(" compartmentImages Type: " + str(type(compartmentImages))) + print(" compartmentImages Size: [" + + str(compartmentImages.shape[0]) + + "," + str(compartmentImages.shape[1]) + + "," + str(compartmentImages.shape[2]) + "]") # Prepare masks for cv2 watershed watershedMarkers = segmentation.get_cv2_watershed_markers( From caa32571973d4e5929a75806981a384799f6faac Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 22 Apr 2020 22:08:40 -0400 Subject: [PATCH 367/419] change nuclei to compartment --- merlin/util/watershed.py | 1 - 1 file changed, 1 deletion(-) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index f4e4bb59..e15e7afb 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -398,7 +398,6 @@ def get_overlapping_objects(watershedZ0: np.ndarray, z1Indexes = np.unique(watershedZ1[watershedZ0 == n0]) z1Indexes = z1Indexes[z1Indexes > 100] - if z1Indexes.shape[0] > 0: # calculate overlap fraction From e1118bddd4e793d62c41e191f1d469b28029a178 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 22 Apr 2020 22:10:53 -0400 Subject: [PATCH 368/419] added framework for MachineLearningSegment class --- merlin/analysis/segment.py | 147 +++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 2e04b61d..ea3d4dc1 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -341,6 +341,153 @@ def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: return np.array([warpTask.get_aligned_image(fov, channelIndex, z) for z in range(len(self.dataSet.get_z_positions()))]) +class MachineLearningSegment(FeatureSavingAnalysisTask): + + """ + An analysis task that determines the boundaries of features in the + image data in each field of view using a watershed algorithm + implemented in CV2. + + A tutorial explaining the general scheme of the method can be + found in https://opencv-python-tutroals.readthedocs.io/en/latest/ + py_tutorials/py_imgproc/py_watershed/py_watershed.html. + + The watershed segmentation is performed in each z-position + independently and combined into 3D objects in a later step + + The class can be used to segment either nuclear or cytoplasmic + compartments. If both the compartment and membrane channels are the + same, the membrane channel is calculated from the edge transform of + the provided channel. + + Since each field of view is analyzed individually, the segmentation + results should be cleaned in order to merge cells that cross the + field of view boundary. + """ + + def __init__(self, dataSet, parameters=None, analysisName=None): + super().__init__(dataSet, parameters, analysisName) + + if 'membrane_channel_name' not in self.parameters: + self.parameters['membrane_channel_name'] = 'DAPI' + if 'compartment_channel_name' not in self.parameters: + self.parameters['compartment_channel_name'] = 'DAPI' + + def fragment_count(self): + return len(self.dataSet.get_fovs()) + + def get_estimated_memory(self): + # TODO - refine estimate + return 2048 + + def get_estimated_time(self): + # TODO - refine estimate + return 5 + + def get_dependencies(self): + return [self.parameters['warp_task'], + self.parameters['global_align_task']] + + def get_cell_boundaries(self) -> List[spatialfeature.SpatialFeature]: + featureDB = self.get_feature_database() + return featureDB.read_features() + + def _run_analysis(self, fragmentIndex): + startTime = time.time() + + print('Entered the _run_analysis method, FOV ' + str(fragmentIndex)) + + globalTask = self.dataSet.load_analysis_task( + self.parameters['global_align_task']) + + print(' globalTask loaded') + + # read membrane and compartment indexes + membraneIndex = self.dataSet \ + .get_data_organization() \ + .get_data_channel_index( + self.parameters['membrane_channel_name']) + compartmentIndex = self.dataSet \ + .get_data_organization() \ + .get_data_channel_index( + self.parameters['compartment_channel_name']) + + if self.parameters['membrane_channel_name'] == + self.parameters['compartment_channel_name']: + membraneFlag = 0 + else: + membraneFlag = 1 + + endTime = time.time() + print(" image indexes read, ET {:.2f} min".format( + (endTime - startTime) / 60)) + + # read membrane and compartment images + membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) + compartmentImages = self._read_image_stack(fragmentIndex, + compartmentIndex) + + endTime = time.time() + print(" images read, ET {:.2f} min".format( + (endTime - startTime) / 60)) + print(" membraneImages Type: " + str(type(membraneImages))) + print(" membraneImages Size: [" + + str(membraneImages.shape[0]) + + "," + str(membraneImages.shape[1]) + + "," + str(membraneImages.shape[2]) + "]") + print(" compartmentImages Type: " + str(type(compartmentImages))) + print(" compartmentImages Size: [" + + str(compartmentImages.shape[0]) + + "," + str(compartmentImages.shape[1]) + + "," + str(compartmentImages.shape[2]) + "]") + + # Prepare masks for cv2 watershed + watershedMarkers = watershed.get_cv2_watershed_markers( + compartmentImages, + membraneImages, + membraneFlag) + + endTime = time.time() + print(" markers calculated, ET {:.2f} min".format( + (endTime - startTime) / 60)) + + # perform watershed in individual z positions + watershedOutput = watershed.apply_cv2_watershed(compartmentImages, + watershedMarkers) + + endTime = time.time() + print(" watershed calculated, ET {:.2f} min".format( + (endTime - startTime) / 60)) + + # combine all z positions in watershed + watershedCombinedOutput = watershed \ + .combine_2d_segmentation_masks_into_3d(watershedOutput) + + endTime = time.time() + print(" watershed z positions combined, ET {:.2f} min".format( + (endTime - startTime) / 60)) + + # get features from mask. This is the slowestart (6 min for the + # previous part, 15+ for the rest, for a 7 frame Image. + zPos = np.array(self.dataSet.get_data_organization().get_z_positions()) + featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( + (watershedCombinedOutput == i), fragmentIndex, + globalTask.fov_to_global_transform(fragmentIndex), zPos) + for i in np.unique(watershedOutput) if i != 0] + + featureDB = self.get_feature_database() + featureDB.write_features(featureList, fragmentIndex) + + endTime = time.time() + print(" features written, ET {:.2f} min".format( + (endTime - startTime) / 60)) + + def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: + warpTask = self.dataSet.load_analysis_task( + self.parameters['warp_task']) + return np.array([warpTask.get_aligned_image(fov, channelIndex, z) + for z in range(len(self.dataSet.get_z_positions()))]) + class CleanCellBoundaries(analysistask.ParallelAnalysisTask): ''' From 331b77352db3b20d0e2187623a1da8c4a80bbae1 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Apr 2020 12:02:57 -0400 Subject: [PATCH 369/419] adding mls utils function --- merlin/analysis/segment.py | 43 ++++++++++++++-------- merlin/util/machinelearningsegmentation.py | 11 ++++++ 2 files changed, 38 insertions(+), 16 deletions(-) create mode 100755 merlin/util/machinelearningsegmentation.py diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index ea3d4dc1..15389a28 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -345,8 +345,15 @@ class MachineLearningSegment(FeatureSavingAnalysisTask): """ An analysis task that determines the boundaries of features in the - image data in each field of view using a watershed algorithm - implemented in CV2. + image data in each field of view using a the specified machine learning + method. The available methods are: + + unet: + + ilastik: + + cellpose: + A tutorial explaining the general scheme of the method can be found in https://opencv-python-tutroals.readthedocs.io/en/latest/ @@ -368,8 +375,8 @@ class MachineLearningSegment(FeatureSavingAnalysisTask): def __init__(self, dataSet, parameters=None, analysisName=None): super().__init__(dataSet, parameters, analysisName) - if 'membrane_channel_name' not in self.parameters: - self.parameters['membrane_channel_name'] = 'DAPI' + if 'method' not in self.parameters: + self.parameters['method'] = 'ilastik' if 'compartment_channel_name' not in self.parameters: self.parameters['compartment_channel_name'] = 'DAPI' @@ -396,6 +403,7 @@ def _run_analysis(self, fragmentIndex): startTime = time.time() print('Entered the _run_analysis method, FOV ' + str(fragmentIndex)) + print('Using ' + self.parameters['method'] + ' method.') globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) @@ -403,30 +411,32 @@ def _run_analysis(self, fragmentIndex): print(' globalTask loaded') # read membrane and compartment indexes - membraneIndex = self.dataSet \ - .get_data_organization() \ - .get_data_channel_index( - self.parameters['membrane_channel_name']) compartmentIndex = self.dataSet \ .get_data_organization() \ .get_data_channel_index( self.parameters['compartment_channel_name']) - if self.parameters['membrane_channel_name'] == - self.parameters['compartment_channel_name']: - membraneFlag = 0 - else: - membraneFlag = 1 - endTime = time.time() print(" image indexes read, ET {:.2f} min".format( (endTime - startTime) / 60)) - # read membrane and compartment images - membraneImages = self._read_image_stack(fragmentIndex, membraneIndex) + # Read images and perform segmentation compartmentImages = self._read_image_stack(fragmentIndex, compartmentIndex) + endTime = time.time() + print(" images read, ET {:.2f} min".format( + (endTime - startTime) / 60)) + + segmentationOutput = machinelearningsegmentation. + apply_machine_learning_segmentation( + compartmentImages, + self.parameters['method']) + + endTime = time.time() + print(" Segmentation finished, ET {:.2f} min".format( + (endTime - startTime) / 60)) +""" endTime = time.time() print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) @@ -466,6 +476,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" watershed z positions combined, ET {:.2f} min".format( (endTime - startTime) / 60)) +""" # get features from mask. This is the slowestart (6 min for the # previous part, 15+ for the rest, for a 7 frame Image. diff --git a/merlin/util/machinelearningsegmentation.py b/merlin/util/machinelearningsegmentation.py new file mode 100755 index 00000000..f882a06e --- /dev/null +++ b/merlin/util/machinelearningsegmentation.py @@ -0,0 +1,11 @@ +import numpy as np +import cv2 +from scipy import ndimage +from scipy.ndimage.morphology import binary_fill_holes +from skimage import morphology +from skimage import filters +from skimage import measure +from pyclustering.cluster import kmedoids +from typing import Tuple + +from merlin.util import matlab \ No newline at end of file From 05239a0b3c37cd2a4afdfd62da3437c282922f6a Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Apr 2020 12:19:53 -0400 Subject: [PATCH 370/419] add function definitions to mls.py --- merlin/util/machinelearningsegmentation.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/merlin/util/machinelearningsegmentation.py b/merlin/util/machinelearningsegmentation.py index f882a06e..fbc05f0e 100755 --- a/merlin/util/machinelearningsegmentation.py +++ b/merlin/util/machinelearningsegmentation.py @@ -8,4 +8,19 @@ from pyclustering.cluster import kmedoids from typing import Tuple -from merlin.util import matlab \ No newline at end of file +from merlin.util import matlab + +""" +This module contains utility functions for preparing imagmes for performing +segmentation using machine learning approaches +""" + +def apply_machine_learning_segmentation(imageStackIn: np.ndarray, + method: str) -> np.ndarray: + +def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: + +def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: + +def segment_using_cellpose(imageStackIn: np.ndarray) -> np.ndarray: + From 42f9c09a6b72bee883105d87187d48db49ae0c95 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 23 Apr 2020 22:16:23 -0400 Subject: [PATCH 371/419] add body of apply_machine_learning_segmentation function --- merlin/analysis/segment.py | 16 +------------- merlin/util/machinelearningsegmentation.py | 25 +++++++++++++++++++--- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 15389a28..04603d12 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -354,22 +354,8 @@ class MachineLearningSegment(FeatureSavingAnalysisTask): cellpose: + TODO: ADD FLAT FIELD CORRECTION TASK - A tutorial explaining the general scheme of the method can be - found in https://opencv-python-tutroals.readthedocs.io/en/latest/ - py_tutorials/py_imgproc/py_watershed/py_watershed.html. - - The watershed segmentation is performed in each z-position - independently and combined into 3D objects in a later step - - The class can be used to segment either nuclear or cytoplasmic - compartments. If both the compartment and membrane channels are the - same, the membrane channel is calculated from the edge transform of - the provided channel. - - Since each field of view is analyzed individually, the segmentation - results should be cleaned in order to merge cells that cross the - field of view boundary. """ def __init__(self, dataSet, parameters=None, analysisName=None): diff --git a/merlin/util/machinelearningsegmentation.py b/merlin/util/machinelearningsegmentation.py index fbc05f0e..9a0076c4 100755 --- a/merlin/util/machinelearningsegmentation.py +++ b/merlin/util/machinelearningsegmentation.py @@ -13,14 +13,33 @@ """ This module contains utility functions for preparing imagmes for performing segmentation using machine learning approaches +MAYBE COMBINE WITH WATERSHED.PY INTO A SINGLE FILE, SEGMENTATION.PY """ +def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: + +def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: + +def segment_using_cellpose(imageStackIn: np.ndarray) -> np.ndarray: + def apply_machine_learning_segmentation(imageStackIn: np.ndarray, method: str) -> np.ndarray: + """Calculate binary mask with 1's in membrane pixels and 0 otherwise. + The images expected are some type of Nuclei label (e.g. DAPI) -def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: + Args: + membraneImages: a 3 dimensional numpy array containing the images + arranged as (z, x, y). + Returns: + ndarray containing a 3 dimensional mask arranged as (z, x, y) + """ + if method == 'ilastik': + segmentOutput = segment_using_ilastik(imageStackIn) + elif method == 'cellpose': + segmentOutput = segment_using_cellpose(imageStackIn) + elif method == 'unet' + segmentOutput = segment_using_unet(imageStackIn) -def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: + return segmentOutput -def segment_using_cellpose(imageStackIn: np.ndarray) -> np.ndarray: From c138a44b49b5ab7a6e3998f6717ac4f3d065d135 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Apr 2020 16:48:14 -0400 Subject: [PATCH 372/419] change nuclei to compartment in cv2 segmentation functions --- merlin/util/machinelearningsegmentation.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/merlin/util/machinelearningsegmentation.py b/merlin/util/machinelearningsegmentation.py index 9a0076c4..e11ab397 100755 --- a/merlin/util/machinelearningsegmentation.py +++ b/merlin/util/machinelearningsegmentation.py @@ -24,11 +24,10 @@ def segment_using_cellpose(imageStackIn: np.ndarray) -> np.ndarray: def apply_machine_learning_segmentation(imageStackIn: np.ndarray, method: str) -> np.ndarray: - """Calculate binary mask with 1's in membrane pixels and 0 otherwise. - The images expected are some type of Nuclei label (e.g. DAPI) + """Calculate Args: - membraneImages: a 3 dimensional numpy array containing the images + imageStackIn: a 3 dimensional numpy array containing the images arranged as (z, x, y). Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) From bb82925c5286df55dd3e9a0fd5a586eee6b8f7f4 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sat, 25 Apr 2020 17:34:33 -0400 Subject: [PATCH 373/419] adding edge memebrane calculation to CV2 segmenetation --- merlin/util/machinelearningsegmentation.py | 3 +-- merlin/util/watershed.py | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/merlin/util/machinelearningsegmentation.py b/merlin/util/machinelearningsegmentation.py index e11ab397..085fa4df 100755 --- a/merlin/util/machinelearningsegmentation.py +++ b/merlin/util/machinelearningsegmentation.py @@ -24,8 +24,7 @@ def segment_using_cellpose(imageStackIn: np.ndarray) -> np.ndarray: def apply_machine_learning_segmentation(imageStackIn: np.ndarray, method: str) -> np.ndarray: - """Calculate - + """Select segmentation algorithm to use Args: imageStackIn: a 3 dimensional numpy array containing the images arranged as (z, x, y). diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index e15e7afb..d3f2019f 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -294,6 +294,7 @@ def get_cv2_watershed_markers(compartmentImages: np.ndarray, compartmentMask = get_compartment_mask(compartmentImages) membraneMask = get_membrane_mask(membraneImages, membraneFlag) + watershedMarker = np.zeros(compartmentMask.shape) for z in range(compartmentImages.shape[0]): @@ -398,6 +399,7 @@ def get_overlapping_objects(watershedZ0: np.ndarray, z1Indexes = np.unique(watershedZ1[watershedZ0 == n0]) z1Indexes = z1Indexes[z1Indexes > 100] + if z1Indexes.shape[0] > 0: # calculate overlap fraction From 2ca4eba7a0d897b78508725b0007171f6ac23bdd Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Sun, 26 Apr 2020 17:41:25 -0400 Subject: [PATCH 374/419] change watershed.py to segmentation.py, put contents of mls.py into segmentation.py --- merlin/analysis/segment.py | 13 ++++--- merlin/util/machinelearningsegmentation.py | 43 ---------------------- merlin/util/segmentation.py | 1 + 3 files changed, 9 insertions(+), 48 deletions(-) delete mode 100755 merlin/util/machinelearningsegmentation.py diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 04603d12..b89dac90 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -362,9 +362,12 @@ def __init__(self, dataSet, parameters=None, analysisName=None): super().__init__(dataSet, parameters, analysisName) if 'method' not in self.parameters: - self.parameters['method'] = 'ilastik' + self.parameters['method'] = 'cellpose' if 'compartment_channel_name' not in self.parameters: self.parameters['compartment_channel_name'] = 'DAPI' + if 'compartment_channel_type' not in self.parameters: + self.parameters['compartment_channel_type'] = 'cytoplasm' # nuclei + def fragment_count(self): return len(self.dataSet.get_fovs()) @@ -414,10 +417,10 @@ def _run_analysis(self, fragmentIndex): print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) - segmentationOutput = machinelearningsegmentation. - apply_machine_learning_segmentation( - compartmentImages, - self.parameters['method']) + segmentationOutput = segmentation.apply_machine_learning_segmentation( + compartmentImages, + self.parameters['method'], + self.parameters['compartment_channel_name']) endTime = time.time() print(" Segmentation finished, ET {:.2f} min".format( diff --git a/merlin/util/machinelearningsegmentation.py b/merlin/util/machinelearningsegmentation.py deleted file mode 100755 index 085fa4df..00000000 --- a/merlin/util/machinelearningsegmentation.py +++ /dev/null @@ -1,43 +0,0 @@ -import numpy as np -import cv2 -from scipy import ndimage -from scipy.ndimage.morphology import binary_fill_holes -from skimage import morphology -from skimage import filters -from skimage import measure -from pyclustering.cluster import kmedoids -from typing import Tuple - -from merlin.util import matlab - -""" -This module contains utility functions for preparing imagmes for performing -segmentation using machine learning approaches -MAYBE COMBINE WITH WATERSHED.PY INTO A SINGLE FILE, SEGMENTATION.PY -""" -def segment_using_ilastik(imageStackIn: np.ndarray) -> np.ndarray: - -def segment_using_unet(imageStackIn: np.ndarray) -> np.ndarray: - -def segment_using_cellpose(imageStackIn: np.ndarray) -> np.ndarray: - - -def apply_machine_learning_segmentation(imageStackIn: np.ndarray, - method: str) -> np.ndarray: - """Select segmentation algorithm to use - Args: - imageStackIn: a 3 dimensional numpy array containing the images - arranged as (z, x, y). - Returns: - ndarray containing a 3 dimensional mask arranged as (z, x, y) - """ - if method == 'ilastik': - segmentOutput = segment_using_ilastik(imageStackIn) - elif method == 'cellpose': - segmentOutput = segment_using_cellpose(imageStackIn) - elif method == 'unet' - segmentOutput = segment_using_unet(imageStackIn) - - return segmentOutput - - diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 461e4fee..3c1c1a49 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -407,6 +407,7 @@ def get_overlapping_objects(segmentationZ0: np.ndarray, segmentation mask adjacent tosegmentationZ0 n0: an integer with the index of the object (cell/nuclei) to be compared between the provided segmentation masks + Returns: a tuple (n1, f0, f1) containing the label of the cell in Z1 overlapping n0 (n1), the fraction of n0 overlaping n1 (f0) and From e16ac11ce11144cb495de3560addd91d2ab85594 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 6 May 2020 10:48:45 -0400 Subject: [PATCH 375/419] consolidate functions into segment_using_cellpose --- merlin/analysis/segment.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index b89dac90..e758dbca 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -365,9 +365,6 @@ def __init__(self, dataSet, parameters=None, analysisName=None): self.parameters['method'] = 'cellpose' if 'compartment_channel_name' not in self.parameters: self.parameters['compartment_channel_name'] = 'DAPI' - if 'compartment_channel_type' not in self.parameters: - self.parameters['compartment_channel_type'] = 'cytoplasm' # nuclei - def fragment_count(self): return len(self.dataSet.get_fovs()) From ddd19121d3d1e0d7e2d2b635d9120ad051feecb2 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 18 Jun 2020 12:23:26 -0400 Subject: [PATCH 376/419] put segmentation params into dict --- merlin/analysis/segment.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index e758dbca..5f0109c6 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -363,6 +363,8 @@ def __init__(self, dataSet, parameters=None, analysisName=None): if 'method' not in self.parameters: self.parameters['method'] = 'cellpose' + if 'diameter' not in self.parameters: + self.parameters['diameter'] = 50 if 'compartment_channel_name' not in self.parameters: self.parameters['compartment_channel_name'] = 'DAPI' @@ -414,10 +416,15 @@ def _run_analysis(self, fragmentIndex): print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) + if self.parameters['method'] == 'cellpose': + segParameters = dict({ + 'method':'cellpose', + 'diameter':self.parameters['diameter'], + 'channel':self.parameters['compartment_channel_name'] + }) + segmentationOutput = segmentation.apply_machine_learning_segmentation( - compartmentImages, - self.parameters['method'], - self.parameters['compartment_channel_name']) + compartmentImages,segParameters) endTime = time.time() print(" Segmentation finished, ET {:.2f} min".format( From b24ebf835f514043953de16eee597c85d9e87229 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 19 Jun 2020 08:23:26 -0400 Subject: [PATCH 377/419] add parameters as dict --- merlin/analysis/segment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 5f0109c6..4356849c 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -424,7 +424,7 @@ def _run_analysis(self, fragmentIndex): }) segmentationOutput = segmentation.apply_machine_learning_segmentation( - compartmentImages,segParameters) + compartmentImages, segParameters) endTime = time.time() print(" Segmentation finished, ET {:.2f} min".format( From 0be81d5f67fea2867ac5fcfaac34d97a4ebd1aba Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 19 Jun 2020 09:31:49 -0400 Subject: [PATCH 378/419] change indentation --- merlin/analysis/segment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 4356849c..115599ca 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -429,7 +429,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" Segmentation finished, ET {:.2f} min".format( (endTime - startTime) / 60)) -""" + """ endTime = time.time() print(" images read, ET {:.2f} min".format( (endTime - startTime) / 60)) @@ -469,7 +469,7 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" watershed z positions combined, ET {:.2f} min".format( (endTime - startTime) / 60)) -""" + """ # get features from mask. This is the slowestart (6 min for the # previous part, 15+ for the rest, for a 7 frame Image. From 142accca0e3cb429a00a4793538b5f59d33525e5 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 19 Jun 2020 13:19:11 -0400 Subject: [PATCH 379/419] return watershed class name to previous state --- merlin/analysis/segment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 115599ca..0d61e6a8 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -123,7 +123,7 @@ def _read_and_filter_image_stack(self, fov: int, channelIndex: int, for z in range(len(self.dataSet.get_z_positions()))]) -class WatershedSegmentCV2(FeatureSavingAnalysisTask): +class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): """ An analysis task that determines the boundaries of features in the From 96e9c9c273c475703b3059c5f607221940db14e3 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 19 Jun 2020 13:58:55 -0400 Subject: [PATCH 380/419] add 2D>3D segmentation mask --- merlin/analysis/segment.py | 41 ++------------------------------------ 1 file changed, 2 insertions(+), 39 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 0d61e6a8..bfadd2e4 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -429,47 +429,10 @@ def _run_analysis(self, fragmentIndex): endTime = time.time() print(" Segmentation finished, ET {:.2f} min".format( (endTime - startTime) / 60)) - """ - endTime = time.time() - print(" images read, ET {:.2f} min".format( - (endTime - startTime) / 60)) - print(" membraneImages Type: " + str(type(membraneImages))) - print(" membraneImages Size: [" - + str(membraneImages.shape[0]) - + "," + str(membraneImages.shape[1]) - + "," + str(membraneImages.shape[2]) + "]") - print(" compartmentImages Type: " + str(type(compartmentImages))) - print(" compartmentImages Size: [" - + str(compartmentImages.shape[0]) - + "," + str(compartmentImages.shape[1]) - + "," + str(compartmentImages.shape[2]) + "]") - - # Prepare masks for cv2 watershed - watershedMarkers = watershed.get_cv2_watershed_markers( - compartmentImages, - membraneImages, - membraneFlag) - - endTime = time.time() - print(" markers calculated, ET {:.2f} min".format( - (endTime - startTime) / 60)) - - # perform watershed in individual z positions - watershedOutput = watershed.apply_cv2_watershed(compartmentImages, - watershedMarkers) - - endTime = time.time() - print(" watershed calculated, ET {:.2f} min".format( - (endTime - startTime) / 60)) # combine all z positions in watershed - watershedCombinedOutput = watershed \ - .combine_2d_segmentation_masks_into_3d(watershedOutput) - - endTime = time.time() - print(" watershed z positions combined, ET {:.2f} min".format( - (endTime - startTime) / 60)) - """ + watershedCombinedOutput = segmentation \ + .combine_2d_segmentation_masks_into_3d(segmentationOutput) # get features from mask. This is the slowestart (6 min for the # previous part, 15+ for the rest, for a 7 frame Image. From d5db3cf655b070d2749f786f15f4ce0dbd8904af Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 19 Jun 2020 14:58:36 -0400 Subject: [PATCH 381/419] changed segmentation output variable name --- merlin/analysis/segment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index bfadd2e4..63f5de92 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -440,7 +440,7 @@ def _run_analysis(self, fragmentIndex): featureList = [spatialfeature.SpatialFeature.feature_from_label_matrix( (watershedCombinedOutput == i), fragmentIndex, globalTask.fov_to_global_transform(fragmentIndex), zPos) - for i in np.unique(watershedOutput) if i != 0] + for i in np.unique(watershedCombinedOutput) if i != 0] featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) From 75ff5633f5c4eecde9b219945ebb468d962af7d9 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 24 Jun 2020 10:52:08 -0400 Subject: [PATCH 382/419] changed value of the background mask from 100 to 0 --- merlin/util/segmentation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 3c1c1a49..9a0bff1d 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -415,6 +415,7 @@ def get_overlapping_objects(segmentationZ0: np.ndarray, """ z1Indexes = np.unique(segmentationZ1[segmentationZ0 == n0]) + z1Indexes = z1Indexes[z1Indexes > 0] if z1Indexes.shape[0] > 0: From 17d0158f0acd85edb7ae83bbba06f963c6e3db7f Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 24 Jun 2020 12:20:33 -0400 Subject: [PATCH 383/419] modifying tuple output in 2d>3d --- merlin/util/segmentation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 9a0bff1d..43c3c535 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -398,6 +398,7 @@ def get_overlapping_objects(segmentationZ0: np.ndarray, segmentationZ1: np.ndarray, n0: int) -> Tuple[np.float64, np.float64, np.float64]: + """compare cell labels in adjacent image masks Args: From d1f19f4dbabe65214e2c6f2a78d2b290bc85d020 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 1 Jul 2020 16:47:50 -0400 Subject: [PATCH 384/419] pep8 compliance --- merlin/analysis/segment.py | 2 +- merlin/util/segmentation.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 63f5de92..08eb9d3b 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -398,7 +398,7 @@ def _run_analysis(self, fragmentIndex): print(' globalTask loaded') - # read membrane and compartment indexes + # read membrane and compartment indexes compartmentIndex = self.dataSet \ .get_data_organization() \ .get_data_channel_index( diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 43c3c535..1a02f5f2 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -296,6 +296,7 @@ def get_cv2_watershed_markers(compartmentImages: np.ndarray, to use membraneChannelName: str with the name of the membrane channel to use + Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) of cv2-compatible watershed markers @@ -488,6 +489,7 @@ def combine_2d_segmentation_masks_into_3d(segmentationOutput: if n1: segmentationCombinedZ[z-1, :, :][ (segmentationOutput[z-1, :, :] == n1)] = n0 + return segmentationCombinedZ From a1d5070894a9101595211bb21d9ad6cccb50022c Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 1 Jul 2020 17:02:22 -0400 Subject: [PATCH 385/419] pep8 compliance --- merlin/analysis/segment.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 08eb9d3b..799208cd 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -149,7 +149,7 @@ class WatershedSegmentNucleiCV2(FeatureSavingAnalysisTask): def __init__(self, dataSet, parameters=None, analysisName=None): super().__init__(dataSet, parameters, analysisName) - + if 'membrane_channel_name' not in self.parameters: self.parameters['membrane_channel_name'] = 'DAPI' if 'compartment_channel_name' not in self.parameters: @@ -341,6 +341,7 @@ def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: return np.array([warpTask.get_aligned_image(fov, channelIndex, z) for z in range(len(self.dataSet.get_z_positions()))]) + class MachineLearningSegment(FeatureSavingAnalysisTask): """ @@ -349,9 +350,9 @@ class MachineLearningSegment(FeatureSavingAnalysisTask): method. The available methods are: unet: - + ilastik: - + cellpose: TODO: ADD FLAT FIELD CORRECTION TASK @@ -360,7 +361,7 @@ class MachineLearningSegment(FeatureSavingAnalysisTask): def __init__(self, dataSet, parameters=None, analysisName=None): super().__init__(dataSet, parameters, analysisName) - + if 'method' not in self.parameters: self.parameters['method'] = 'cellpose' if 'diameter' not in self.parameters: @@ -418,9 +419,9 @@ def _run_analysis(self, fragmentIndex): if self.parameters['method'] == 'cellpose': segParameters = dict({ - 'method':'cellpose', - 'diameter':self.parameters['diameter'], - 'channel':self.parameters['compartment_channel_name'] + 'method': 'cellpose', + 'diameter': self.parameters['diameter'], + 'channel': self.parameters['compartment_channel_name'] }) segmentationOutput = segmentation.apply_machine_learning_segmentation( From 94f91b30e7c60c3a80b8cea95c3f9591ac0490e8 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 1 Jul 2020 18:36:19 -0400 Subject: [PATCH 386/419] added Machine Learning Segment and CV2 to tests --- .../test_analysis_parameters.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/auxiliary_files/test_analysis_parameters.json b/test/auxiliary_files/test_analysis_parameters.json index e426e860..9302e1d7 100755 --- a/test/auxiliary_files/test_analysis_parameters.json +++ b/test/auxiliary_files/test_analysis_parameters.json @@ -134,6 +134,25 @@ "compartment_channel_name": "DAPI" } }, + { + "task": "WatershedSegmentNucleiCV2", + "module": "merlin.analysis.segment", + "parameters": { + "warp_task": "FiducialCorrelationWarp", + "global_align_task": "SimpleGlobalAlignment" + } + }, + { + "task": "MachineLearningSegment", + "module": "merlin.analysis.segment", + "parameters": { + "warp_task": "FiducialCorrelationWarp", + "global_align_task": "SimpleGlobalAlignment", + "diameter": 50, + "method": "cellpose", + "compartment_channel_name": "DAPI" + } + }, { "task": "CleanCellBoundaries", "module": "merlin.analysis.segment", From 01f0e3ad3b81f6c051778e7274f6814e31fc0d7d Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 1 Jul 2020 19:16:44 -0400 Subject: [PATCH 387/419] remove print statements for timing --- merlin/analysis/segment.py | 62 ++------------------------------------ 1 file changed, 3 insertions(+), 59 deletions(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index 799208cd..049a3e9d 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -177,8 +177,6 @@ def get_cell_boundaries(self) -> List[spatialfeature.SpatialFeature]: def _run_analysis(self, fragmentIndex): startTime = time.time() - print('Entered the _run_analysis method, FOV ' + str(fragmentIndex)) - globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) @@ -197,20 +195,6 @@ def _run_analysis(self, fragmentIndex): compartmentImages = self._read_image_stack(fragmentIndex, compartmentIndex) - endTime = time.time() - print(" images read, ET {:.2f} min".format( - (endTime - startTime) / 60)) - print(" membraneImages Type: " + str(type(membraneImages))) - print(" membraneImages Size: [" - + str(membraneImages.shape[0]) - + "," + str(membraneImages.shape[1]) - + "," + str(membraneImages.shape[2]) + "]") - print(" compartmentImages Type: " + str(type(compartmentImages))) - print(" compartmentImages Size: [" - + str(compartmentImages.shape[0]) - + "," + str(compartmentImages.shape[1]) - + "," + str(compartmentImages.shape[2]) + "]") - # Prepare masks for cv2 watershed watershedMarkers = segmentation.get_cv2_watershed_markers( compartmentImages, @@ -218,18 +202,10 @@ def _run_analysis(self, fragmentIndex): self.parameters['compartment_channel_name'], self.parameters['membrane_channel_name']) - endTime = time.time() - print(" markers calculated, ET {:.2f} min".format( - (endTime - startTime) / 60)) - # perform watershed in individual z positions watershedOutput = segmentation.apply_cv2_watershed(compartmentImages, watershedMarkers) - endTime = time.time() - print(" watershed calculated, ET {:.2f} min".format( - (endTime - startTime) / 60)) - # combine all z positions in watershed watershedCombinedOutput = segmentation \ .combine_2d_segmentation_masks_into_3d(watershedOutput) @@ -331,10 +307,6 @@ def _run_analysis(self, fragmentIndex): featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) - endTime = time.time() - print(" features written, ET {:.2f} min".format( - (endTime - startTime) / 60)) - def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) @@ -347,16 +319,10 @@ class MachineLearningSegment(FeatureSavingAnalysisTask): """ An analysis task that determines the boundaries of features in the image data in each field of view using a the specified machine learning - method. The available methods are: - - unet: - - ilastik: - - cellpose: - - TODO: ADD FLAT FIELD CORRECTION TASK + method. The available method is cellpose (https://github.com/MouseLand/ + cellpose). + TODO: implement unets / Ilastik """ def __init__(self, dataSet, parameters=None, analysisName=None): @@ -389,34 +355,20 @@ def get_cell_boundaries(self) -> List[spatialfeature.SpatialFeature]: return featureDB.read_features() def _run_analysis(self, fragmentIndex): - startTime = time.time() - - print('Entered the _run_analysis method, FOV ' + str(fragmentIndex)) - print('Using ' + self.parameters['method'] + ' method.') globalTask = self.dataSet.load_analysis_task( self.parameters['global_align_task']) - print(' globalTask loaded') - # read membrane and compartment indexes compartmentIndex = self.dataSet \ .get_data_organization() \ .get_data_channel_index( self.parameters['compartment_channel_name']) - endTime = time.time() - print(" image indexes read, ET {:.2f} min".format( - (endTime - startTime) / 60)) - # Read images and perform segmentation compartmentImages = self._read_image_stack(fragmentIndex, compartmentIndex) - endTime = time.time() - print(" images read, ET {:.2f} min".format( - (endTime - startTime) / 60)) - if self.parameters['method'] == 'cellpose': segParameters = dict({ 'method': 'cellpose', @@ -427,10 +379,6 @@ def _run_analysis(self, fragmentIndex): segmentationOutput = segmentation.apply_machine_learning_segmentation( compartmentImages, segParameters) - endTime = time.time() - print(" Segmentation finished, ET {:.2f} min".format( - (endTime - startTime) / 60)) - # combine all z positions in watershed watershedCombinedOutput = segmentation \ .combine_2d_segmentation_masks_into_3d(segmentationOutput) @@ -446,10 +394,6 @@ def _run_analysis(self, fragmentIndex): featureDB = self.get_feature_database() featureDB.write_features(featureList, fragmentIndex) - endTime = time.time() - print(" features written, ET {:.2f} min".format( - (endTime - startTime) / 60)) - def _read_image_stack(self, fov: int, channelIndex: int) -> np.ndarray: warpTask = self.dataSet.load_analysis_task( self.parameters['warp_task']) From 487fb44db5dacc9ded8a56c91555f9b59e70b3ad Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 07:12:40 -0400 Subject: [PATCH 388/419] copied dataportal.py from master --- merlin/util/dataportal.py | 1 - 1 file changed, 1 deletion(-) diff --git a/merlin/util/dataportal.py b/merlin/util/dataportal.py index c01e7a17..bba38f97 100755 --- a/merlin/util/dataportal.py +++ b/merlin/util/dataportal.py @@ -344,7 +344,6 @@ def get_sibling_with_extension(self, newExtension: str): def _error_tolerant_reading(self, method, startByte=None, endByte=None): - backoffSeries = [1, 2, 4, 8, 16, 32, 64, 128, 256] for sleepDuration in backoffSeries: try: From 82f7fa2f37568b7e0d69491b8338edef80639e8b Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 07:33:30 -0400 Subject: [PATCH 389/419] added explicit tuple output --- merlin/util/segmentation.py | 1 - 1 file changed, 1 deletion(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 1a02f5f2..e7291a05 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -399,7 +399,6 @@ def get_overlapping_objects(segmentationZ0: np.ndarray, segmentationZ1: np.ndarray, n0: int) -> Tuple[np.float64, np.float64, np.float64]: - """compare cell labels in adjacent image masks Args: From 18584fdf26d8cd512b2bb7ffdc84ce75bfe908ca Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 07:56:34 -0400 Subject: [PATCH 390/419] add cellpose to requirements --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index dfc4de44..35d39c48 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,3 +29,4 @@ boto3 xmltodict google-cloud-storage docutils<0.16,>=0.10 + From 1d0a7b0ec0d3e65e8f215f9f4364b58c59c492da Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 08:04:52 -0400 Subject: [PATCH 391/419] updated docutils and pillow requirements --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 35d39c48..dfc4de44 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,4 +29,3 @@ boto3 xmltodict google-cloud-storage docutils<0.16,>=0.10 - From 6ea18aab93583ad8a9887bf1eafb5046b8b56b0c Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 10:21:12 -0400 Subject: [PATCH 392/419] removed docutils requirement to avoid conflict with v017 --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index dfc4de44..35d39c48 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,3 +29,4 @@ boto3 xmltodict google-cloud-storage docutils<0.16,>=0.10 + From 3ef602bcf8088de9d62051a4fd9102a1fda2abc5 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 2 Jul 2020 11:23:03 -0400 Subject: [PATCH 393/419] added input variables to get_membrane_mask --- requirements.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/requirements.txt b/requirements.txt index 35d39c48..5f41daa4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,4 +29,9 @@ boto3 xmltodict google-cloud-storage docutils<0.16,>=0.10 +<<<<<<< HEAD +======= +pillow<=7.0.0 +cellpose +>>>>>>> added input variables to get_membrane_mask From 1b07f355a4e70242c5878455785e47bc9b57d18b Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Mon, 6 Jul 2020 18:13:42 -0400 Subject: [PATCH 394/419] added pillow and cellpose requirements --- requirements.txt | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/requirements.txt b/requirements.txt index 5f41daa4..c65e9715 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,10 +28,4 @@ tables boto3 xmltodict google-cloud-storage -docutils<0.16,>=0.10 -<<<<<<< HEAD - -======= -pillow<=7.0.0 -cellpose ->>>>>>> added input variables to get_membrane_mask +docutils<0.16,>=0.10 \ No newline at end of file From f40d2faf570f4ef2a33a022f0867f53d346da03b Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 7 Jul 2020 12:44:32 -0400 Subject: [PATCH 395/419] remove redundant functions from watershed.py --- merlin/util/watershed.py | 327 +-------------------------------------- 1 file changed, 1 insertion(+), 326 deletions(-) diff --git a/merlin/util/watershed.py b/merlin/util/watershed.py index d3f2019f..6bb453c7 100644 --- a/merlin/util/watershed.py +++ b/merlin/util/watershed.py @@ -140,329 +140,4 @@ def prepare_watershed_images(watershedImageStack: np.ndarray - np.min(watershedImageStack)) normalizedWatershed[np.invert(watershedMask)] = 1 - return normalizedWatershed, watershedMask - -def get_membrane_mask(membraneImages: np.ndarray, - membraneChannelName: str, - compartmentChannelName: str) -> np.ndarray: - """Calculate binary mask with 1's in membrane pixels and 0 otherwise. - The images expected are some type of membrane label (WGA, ConA, - Lamin, Cadherins) or compartment images (DAPI, CD45, polyT) - - Args: - membraneImages: a 3 dimensional numpy array containing the images - arranged as (z, x, y). - membraneChannelName: A string with the name of a membrane channel. - compartmentChannelName: A string with the name of the compartment - channel - - Returns: - ndarray containing a 3 dimensional mask arranged as (z, x, y) - """ - mask = np.zeros(membraneImages.shape) - if membraneChannelName != compartmentChannelName: - fineBlockSize = 61 - for z in range(membraneImages.shape[0]): - mask[z, :, :] = (membraneImages[z, :, :] > - filters.threshold_local(membraneImages[z, :, :], - fineBlockSize, - offset=0)) - mask[z, :, :] = morphology.remove_small_objects( - mask[z, :, :].astype('bool'), - min_size=100, - connectivity=1) - mask[z, :, :] = morphology.binary_closing(mask[z, :, :], - morphology.selem.disk(5)) - mask[z, :, :] = morphology.skeletonize(mask[z, :, :]) - else: - filterSigma2 = 5 - filterSize2 = int(2*np.ceil(2*filterSigma2)+1) - edgeSigma = 2 # 1, 2 - lowThresh = 0.1 # 0.5, 0.2 - hiThresh = 0.5 # 0.7, 0.6 - for z in range(membraneImages.shape[0]): - blurredImage = cv2.GaussianBlur(membraneImages[z, :, :], - (filterSize2, filterSize2), - filterSigma2) - edge0 = feature.canny(membraneImages[z, :, :], - sigma=edgeSigma, - use_quantiles=True, - low_threshold=lowThresh, - high_threshold=hiThresh) - edge0 = morphology.dilation(edge0, morphology.selem.disk(10)) - - edge1 = feature.canny(blurredImage, - sigma=edgeSigma, - use_quantiles=True, - low_threshold=lowThresh, - high_threshold=hiThresh) - edge1 = morphology.dilation(edge1, morphology.selem.disk(10)) - - mask[z, :, :] = edge0 + edge1 - - mask[z, :, :] = morphology.skeletonize(mask[z, :, :]) - - return mask - -def get_compartment_mask(compartmentImages: np.ndarray) -> np.ndarray: - """Calculate binary mask with 1's in compartment (nuclei or cytoplasm) - pixels and 0 otherwise. The images expected are some type of compartment - label (e.g. Nuclei: DAPI, Cytoplasm: PolyT, CD45, etc) - - Args: - compartmentImages: a 3 dimensional numpy array containing the images - arranged as (z, x, y). - Returns: - ndarray containing a 3 dimensional mask arranged as (z, x, y) - """ - - # generate compartment mask based on thresholding - thresholdingMask = np.zeros(compartmentImages.shape) - coarseBlockSize = 241 - fineBlockSize = 61 - for z in range(compartmentImages.shape[0]): - coarseThresholdingMask = (compartmentImages[z, :, :] > - filters.threshold_local( - compartmentImages[z, :, :], - coarseBlockSize, - offset=0)) - fineThresholdingMask = (compartmentImages[z, :, :] > - filters.threshold_local( - compartmentImages[z, :, :], - fineBlockSize, - offset=0)) - thresholdingMask[z, :, :] = (coarseThresholdingMask * - fineThresholdingMask) - thresholdingMask[z, :, :] = binary_fill_holes( - thresholdingMask[z, :, :]) - - # generate border mask, necessary to avoid making a single - # connected component when using binary_fill_holes below - borderMask = np.zeros((compartmentImages.shape[1], - compartmentImages.shape[2])) - borderMask[25:(compartmentImages.shape[1]-25), - 25:(compartmentImages.shape[2]-25)] = 1 - - # generate compartment mask from hessian, fine - fineHessianMask = np.zeros(compartmentImages.shape) - for z in range(compartmentImages.shape[0]): - fineHessian = filters.hessian(compartmentImages[z, :, :]) - fineHessianMask[z, :, :] = fineHessian == fineHessian.max() - fineHessianMask[z, :, :] = morphology.binary_closing( - fineHessianMask[z, :, :], - morphology.selem.disk(5)) - fineHessianMask[z, :, :] = fineHessianMask[z, :, :] * borderMask - fineHessianMask[z, :, :] = binary_fill_holes( - fineHessianMask[z, :, :]) - - # generate compartment mask from hessian, coarse - coarseHessianMask = np.zeros(compartmentImages.shape) - for z in range(compartmentImages.shape[0]): - coarseHessian = filters.hessian(compartmentImages[z, :, :] - - morphology.white_tophat( - compartmentImages[z, :, :], - morphology.selem.disk(20))) - coarseHessianMask[z, :, :] = coarseHessian == coarseHessian.max() - coarseHessianMask[z, :, :] = morphology.binary_closing( - coarseHessianMask[z, :, :], morphology.selem.disk(5)) - coarseHessianMask[z, :, :] = (coarseHessianMask[z, :, :] * - borderMask) - coarseHessianMask[z, :, :] = binary_fill_holes( - coarseHessianMask[z, :, :]) - - # combine masks - compartmentMask = thresholdingMask + fineHessianMask + coarseHessianMask - return binary_fill_holes(compartmentMask) - -def get_cv2_watershed_markers(compartmentImages: np.ndarray, - membraneImages: np.ndarray, - membraneFlag: int) -> np.ndarray: - """Combine membrane and compartment markers into a single multilabel mask - for CV2 watershed - - Args: - compartmentImages: a 3 dimensional numpy array containing the images - arranged as (z, x, y). - membraneImages: a 3 dimensional numpy array containing the images - arranged as (z, x, y). - membraneFlag: 0 if compartment and membrane images are the same, 1 - otherwise - Returns: - ndarray containing a 3 dimensional mask arranged as (z, x, y) of - cv2-compatible watershed markers - """ - - compartmentMask = get_compartment_mask(compartmentImages) - membraneMask = get_membrane_mask(membraneImages, membraneFlag) - - watershedMarker = np.zeros(compartmentMask.shape) - - for z in range(compartmentImages.shape[0]): - # generate areas of sure bg and fg, as well as the area of - # unknown classification - background = morphology.dilation(compartmentMask[z, :, :], - morphology.selem.disk(15)) - membraneDilated = morphology.dilation( - membraneMask[z, :, :].astype('bool'), - morphology.selem.disk(10)) - foreground = morphology.erosion(compartmentMask[z, :, :] * ~ - membraneDilated, - morphology.selem.disk(5)) - unknown = background * ~ foreground - - background = np.uint8(background) * 255 - foreground = np.uint8(foreground) * 255 - unknown = np.uint8(unknown) * 255 - - # Marker labelling - ret, markers = cv2.connectedComponents(foreground) - - # Add one to all labels so that sure background is not 0, but 1 - markers = markers + 100 - - # Now, mark the region of unknown with zero - markers[unknown == 255] = 0 - - watershedMarker[z, :, :] = markers - - return watershedMarker - -def convert_grayscale_to_rgb(uint16Image: np.ndarray) -> np.ndarray: - """Convert a 16 bit 2D grayscale image into a 3D 8-bit RGB image. - cv2 only works in 8-bit. Based on https://stackoverflow.com/questions/ - 25485886/how-to-convert-a-16-bit-to-an-8-bit-image-in-opencv3D - - Args: - uint16Image: a 2 dimensional numpy array containing the 16-bit - image - Returns: - ndarray containing a 3 dimensional 8-bit image stack - """ - - # invert image - uint16Image = 2**16 - uint16Image - - # convert to uint8 - ratio = np.amax(uint16Image) / 256 - uint8Image = (uint16Image / ratio).astype('uint8') - - rgbImage = np.zeros((2048, 2048, 3)) - rgbImage[:, :, 0] = uint8Image - rgbImage[:, :, 1] = uint8Image - rgbImage[:, :, 2] = uint8Image - rgbImage = rgbImage.astype('uint8') - - return rgbImage - -def apply_cv2_watershed(compartmentImages: np.ndarray, - watershedMarkers: np.ndarray) -> np.ndarray: - """Perform watershed using cv2 - - Args: - compartmentImages: a 3 dimensional numpy array containing the images - arranged as (z, x, y). - watershedMarkers: a 3 dimensional numpy array containing the cv2 - markers arranged as (z, x, y). - Returns: - ndarray containing a 3 dimensional mask arranged as (z, x, y) of - segmented cells. masks in different z positions are - independent - """ - - watershedOutput = np.zeros(watershedMarkers.shape) - for z in range(nucleiImages.shape[0]): - rgbImage = convert_grayscale_to_rgb(compartmentImages[z, :, :]) - watershedOutput[z, :, :] = cv2.watershed(rgbImage, - watershedMarkers[z, :, :]. - astype('int32')) - watershedOutput[z, :, :][watershedOutput[z, :, :] <= 100] = 0 - - return watershedOutput - -def get_overlapping_objects(watershedZ0: np.ndarray, - watershedZ1: np.ndarray, n0: int): - """Perform watershed using cv2 - - Args: - watershedZ0: a 2 dimensional numpy array containing a - segmentation mask - watershedZ1: a 2 dimensional numpy array containing a - segmentation mask adjacent to watershedZ1 - n0: an integer with the index of the object (cell/nuclei) - to be compared between the provided watershed - segmentation masks - Returns: - a tuple (n1, f0, f1) containing the label of the cell in Z1 - overlapping n0 (n1), the fraction of n0 overlaping n1 (f0) and - the fraction of n1 overlapping n0 (f1) - """ - - z1Indexes = np.unique(watershedZ1[watershedZ0 == n0]) - z1Indexes = z1Indexes[z1Indexes > 100] - - if z1Indexes.shape[0] > 0: - - # calculate overlap fraction - n0Area = np.count_nonzero(watershedZ0 == n0) - n1Area = np.zeros(len(z1Indexes)) - overlapArea = np.zeros(len(z1Indexes)) - - for ii in range(len(z1Indexes)): - n1 = z1Indexes[ii] - n1Area[ii] = np.count_nonzero(watershedZ1 == n1) - overlapArea[ii] = np.count_nonzero((watershedZ0 == n0) * - (watershedZ1 == n1)) - - n0OverlapFraction = np.asarray(overlapArea / n0Area) - n1OverlapFraction = np.asarray(overlapArea / n1Area) - index = list(range(len(n0OverlapFraction))) - - # select the nuclei that has the highest fraction in n0 and n1 - r1, r2, indexSorted = zip(*sorted(zip(n0OverlapFraction, - n1OverlapFraction, - index), - reverse=True)) - - if (n0OverlapFraction[indexSorted[0]] > 0.2 and - n1OverlapFraction[indexSorted[0]] > 0.5): - return z1Indexes[indexSorted[0]], - n0OverlapFraction[indexSorted[0]], - n1OverlapFraction[indexSorted[0]] - else: - return False, False, False - else: - return False, False, False - -def combine_2d_segmentation_masks_into_3d(watershedOutput: - np.ndarray) -> np.ndarray: - """Take a 3 dimensional watershed masks and relabel them so that - nuclei in adjacent sections have the same label if the area their - overlap surpases certain threshold - - Args: - watershedOutput: a 3 dimensional numpy array containing the - segmentation masks arranged as (z, x, y). - Returns: - ndarray containing a 3 dimensional mask arranged as (z, x, y) of - relabeled segmented cells - """ - - # Initialize empty array with size as watershedOutput array - watershedCombinedZ = np.zeros(watershedOutput.shape) - - # copy the mask of the section farthest to the coverslip - watershedCombinedZ[-1, :, :] = watershedOutput[-1, :, :] - - # starting far from coverslip - for z in range(watershedOutput.shape[0]-1, 0, -1): - zIndex = np.unique(watershedOutput[z, :, :])[ - np.unique(watershedOutput[z, :, :]) > 100] - - for n0 in zIndex: - n1, f0, f1 = get_overlapping_objects(watershedCombinedZ[z, :, :], - watershedOutput[z-1, :, :], - n0) - if n1: - watershedCombinedZ[z-1, :, :][(watershedOutput[z-1, :, :] == - n1)] = n0 - return watershedCombinedZ + return normalizedWatershed, watershedMask \ No newline at end of file From e38eb7cc800f5598843a0d7cc53a3f4747afb311 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 7 Jul 2020 12:51:44 -0400 Subject: [PATCH 396/419] remove redundant tasks --- .../test_analysis_parameters.json | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/test/auxiliary_files/test_analysis_parameters.json b/test/auxiliary_files/test_analysis_parameters.json index 9302e1d7..e426e860 100755 --- a/test/auxiliary_files/test_analysis_parameters.json +++ b/test/auxiliary_files/test_analysis_parameters.json @@ -134,25 +134,6 @@ "compartment_channel_name": "DAPI" } }, - { - "task": "WatershedSegmentNucleiCV2", - "module": "merlin.analysis.segment", - "parameters": { - "warp_task": "FiducialCorrelationWarp", - "global_align_task": "SimpleGlobalAlignment" - } - }, - { - "task": "MachineLearningSegment", - "module": "merlin.analysis.segment", - "parameters": { - "warp_task": "FiducialCorrelationWarp", - "global_align_task": "SimpleGlobalAlignment", - "diameter": 50, - "method": "cellpose", - "compartment_channel_name": "DAPI" - } - }, { "task": "CleanCellBoundaries", "module": "merlin.analysis.segment", From 9a59d35bdcd3267ddcd6c630bddf22ac055cd0df Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 7 Jul 2020 13:35:11 -0400 Subject: [PATCH 397/419] add snakemakeParameters --- merlin/merlin.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/merlin/merlin.py b/merlin/merlin.py index c892baa3..c41c0c98 100755 --- a/merlin/merlin.py +++ b/merlin/merlin.py @@ -162,6 +162,12 @@ def run_with_snakemake( dataSet: dataset.MERFISHDataSet, snakefilePath: str, coreCount: int, snakemakeParameters: Dict = {}, report: bool = True): print('Running MERlin pipeline through snakemake') + + if 'restart_times' not in snakemakeParameters: + snakemakeParameters['restart_times'] = 3 + if 'latency_wait' not in snakemakeParameters: + snakemakeParameters['latency_wait'] = 60 + snakemake.snakemake(snakefilePath, cores=coreCount, workdir=dataSet.get_snakemake_path(), stats=snakefilePath + '.stats', lock=False, From 673edaa87252abba32c6c0c0fb1d2fa8ce77ea1e Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 7 Jul 2020 16:22:59 -0400 Subject: [PATCH 398/419] increase latency_wait --- merlin/merlin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/merlin.py b/merlin/merlin.py index c41c0c98..70bbd542 100755 --- a/merlin/merlin.py +++ b/merlin/merlin.py @@ -166,7 +166,7 @@ def run_with_snakemake( if 'restart_times' not in snakemakeParameters: snakemakeParameters['restart_times'] = 3 if 'latency_wait' not in snakemakeParameters: - snakemakeParameters['latency_wait'] = 60 + snakemakeParameters['latency_wait'] = 600 snakemake.snakemake(snakefilePath, cores=coreCount, workdir=dataSet.get_snakemake_path(), From 8c663b8ab4757ac7e660ec866cb784fe8efb591b Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 7 Jul 2020 16:55:40 -0400 Subject: [PATCH 399/419] remove MLS and CV2 from merfish test --- .../test_analysis_parameters.json | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/test/auxiliary_files/test_analysis_parameters.json b/test/auxiliary_files/test_analysis_parameters.json index e426e860..2e9f9212 100755 --- a/test/auxiliary_files/test_analysis_parameters.json +++ b/test/auxiliary_files/test_analysis_parameters.json @@ -115,25 +115,6 @@ "global_align_task": "SimpleGlobalAlignment" } }, - { - "task": "WatershedSegmentNucleiCV2", - "module": "merlin.analysis.segment", - "parameters": { - "warp_task": "FiducialCorrelationWarp", - "global_align_task": "SimpleGlobalAlignment" - } - }, - { - "task": "MachineLearningSegment", - "module": "merlin.analysis.segment", - "parameters": { - "warp_task": "FiducialCorrelationWarp", - "global_align_task": "SimpleGlobalAlignment", - "diameter": 50, - "method": "cellpose", - "compartment_channel_name": "DAPI" - } - }, { "task": "CleanCellBoundaries", "module": "merlin.analysis.segment", From 323e8809fc85a114a3ff2d3c17f87bcc62e3ad66 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Tue, 7 Jul 2020 18:52:17 -0400 Subject: [PATCH 400/419] add test for new segmentation routines --- .../test_analysis_segmentation_cellpose.json | 57 +++++++++++++++++++ .../test_analysis_segmentation_cv2.json | 55 ++++++++++++++++++ test/test_segmentation_cellpose.py | 15 +++++ test/test_segmentation_cv2.py | 15 +++++ 4 files changed, 142 insertions(+) create mode 100755 test/auxiliary_files/test_analysis_segmentation_cellpose.json create mode 100755 test/auxiliary_files/test_analysis_segmentation_cv2.json create mode 100755 test/test_segmentation_cellpose.py create mode 100755 test/test_segmentation_cv2.py diff --git a/test/auxiliary_files/test_analysis_segmentation_cellpose.json b/test/auxiliary_files/test_analysis_segmentation_cellpose.json new file mode 100755 index 00000000..632e2efa --- /dev/null +++ b/test/auxiliary_files/test_analysis_segmentation_cellpose.json @@ -0,0 +1,57 @@ +{ + "analysis_tasks": [ + { + "task": "FiducialCorrelationWarp", + "module": "merlin.analysis.warp", + "parameters": { + "write_aligned_images": true + } + }, + { + "task": "SimpleGlobalAlignment", + "module": "merlin.analysis.globalalign" + }, + { + "task": "MachineLearningSegment", + "module": "merlin.analysis.segment", + "analysis_name": "CellposeSegment", + "parameters": { + "warp_task": "FiducialCorrelationWarp", + "global_align_task": "SimpleGlobalAlignment", + "diameter": 50, + "method": "cellpose", + "compartment_channel_name": "DAPI" + } + }, + { + "task": "CleanCellBoundaries", + "module": "merlin.analysis.segment", + "parameters": { + "segment_task": "CellposeSegment", + "global_align_task": "SimpleGlobalAlignment" + } + }, + { + "task": "CombineCleanedBoundaries", + "module": "merlin.analysis.segment", + "parameters": { + "cleaning_task": "CleanCellBoundaries" + } + }, + { + "task": "RefineCellDatabases", + "module": "merlin.analysis.segment", + "parameters": { + "segment_task": "CellposeSegment", + "combine_cleaning_task": "CombineCleanedBoundaries" + } + }, + { + "task": "ExportCellMetadata", + "module": "merlin.analysis.segment", + "parameters": { + "segment_task": "RefineCellDatabases" + } + } + ] +} diff --git a/test/auxiliary_files/test_analysis_segmentation_cv2.json b/test/auxiliary_files/test_analysis_segmentation_cv2.json new file mode 100755 index 00000000..4fd55a31 --- /dev/null +++ b/test/auxiliary_files/test_analysis_segmentation_cv2.json @@ -0,0 +1,55 @@ +{ + "analysis_tasks": [ + { + "task": "FiducialCorrelationWarp", + "module": "merlin.analysis.warp", + "parameters": { + "write_aligned_images": true + } + }, + { + "task": "SimpleGlobalAlignment", + "module": "merlin.analysis.globalalign" + }, + { + "task": "WatershedSegmentNucleiCV2", + "module": "merlin.analysis.segment", + "analysis_name": "CV2Segment", + "parameters": { + "warp_task": "FiducialCorrelationWarp", + "global_align_task": "SimpleGlobalAlignment", + "" + } + }, + { + "task": "CleanCellBoundaries", + "module": "merlin.analysis.segment", + "parameters": { + "segment_task": "CV2Segment", + "global_align_task": "SimpleGlobalAlignment" + } + }, + { + "task": "CombineCleanedBoundaries", + "module": "merlin.analysis.segment", + "parameters": { + "cleaning_task": "CleanCellBoundaries" + } + }, + { + "task": "RefineCellDatabases", + "module": "merlin.analysis.segment", + "parameters": { + "segment_task": "CV2Segment", + "combine_cleaning_task": "CombineCleanedBoundaries" + } + }, + { + "task": "ExportCellMetadata", + "module": "merlin.analysis.segment", + "parameters": { + "segment_task": "RefineCellDatabases" + } + } + ] +} diff --git a/test/test_segmentation_cellpose.py b/test/test_segmentation_cellpose.py new file mode 100755 index 00000000..4814cbe2 --- /dev/null +++ b/test/test_segmentation_cellpose.py @@ -0,0 +1,15 @@ +import os +import pytest + +import merlin +from merlin import merlin as m + + +@pytest.mark.fullrun +@pytest.mark.slowtest +def test_merfish_2d_full_local(simple_merfish_data): + with open(os.sep.join([merlin.ANALYSIS_PARAMETERS_HOME, + 'test_analysis_segmentation_cellpose.json']), 'r') as f: + snakefilePath = m.generate_analysis_tasks_and_snakefile( + simple_merfish_data, f) + m.run_with_snakemake(simple_merfish_data, snakefilePath, 5) diff --git a/test/test_segmentation_cv2.py b/test/test_segmentation_cv2.py new file mode 100755 index 00000000..ff156f2a --- /dev/null +++ b/test/test_segmentation_cv2.py @@ -0,0 +1,15 @@ +import os +import pytest + +import merlin +from merlin import merlin as m + + +@pytest.mark.fullrun +@pytest.mark.slowtest +def test_merfish_2d_full_local(simple_merfish_data): + with open(os.sep.join([merlin.ANALYSIS_PARAMETERS_HOME, + 'test_analysis_segmentation_cv2.json']), 'r') as f: + snakefilePath = m.generate_analysis_tasks_and_snakefile( + simple_merfish_data, f) + m.run_with_snakemake(simple_merfish_data, snakefilePath, 5) From a1055c04d34559be317f0344621f877b1f62f0eb Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 8 Jul 2020 12:06:21 -0400 Subject: [PATCH 401/419] add segmentation jsons to conftest --- test/conftest.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/conftest.py b/test/conftest.py index 83714b35..6a00262e 100755 --- a/test/conftest.py +++ b/test/conftest.py @@ -57,6 +57,16 @@ def base_files(): [root, 'auxiliary_files', 'test_analysis_parameters.json']), os.sep.join( [merlin.ANALYSIS_PARAMETERS_HOME, 'test_analysis_parameters.json'])) + shutil.copyfile( + os.sep.join( + [root, 'auxiliary_files', 'test_analysis_segmentation_cellpose.json']), + os.sep.join( + [merlin.ANALYSIS_PARAMETERS_HOME, 'test_analysis_segmentation_cellpose.json'])) + shutil.copyfile( + os.sep.join( + [root, 'auxiliary_files', 'test_analysis_segmentation_cv2.json']), + os.sep.join( + [merlin.ANALYSIS_PARAMETERS_HOME, 'test_analysis_segmentation_cv2.json'])) shutil.copyfile( os.sep.join( [root, 'auxiliary_files', 'test_microscope_parameters.json']), From ef8ed16e7133120fc90d0837b65576e11c412dfb Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 8 Jul 2020 12:50:21 -0400 Subject: [PATCH 402/419] added unique task analysis_name for cellpose and cv2 tests --- .../test_analysis_segmentation_cellpose.json | 66 ++++++++++++++----- .../test_analysis_segmentation_cv2.json | 65 +++++++++++++----- 2 files changed, 100 insertions(+), 31 deletions(-) diff --git a/test/auxiliary_files/test_analysis_segmentation_cellpose.json b/test/auxiliary_files/test_analysis_segmentation_cellpose.json index 632e2efa..8b887797 100755 --- a/test/auxiliary_files/test_analysis_segmentation_cellpose.json +++ b/test/auxiliary_files/test_analysis_segmentation_cellpose.json @@ -1,16 +1,5 @@ { "analysis_tasks": [ - { - "task": "FiducialCorrelationWarp", - "module": "merlin.analysis.warp", - "parameters": { - "write_aligned_images": true - } - }, - { - "task": "SimpleGlobalAlignment", - "module": "merlin.analysis.globalalign" - }, { "task": "MachineLearningSegment", "module": "merlin.analysis.segment", @@ -18,14 +7,15 @@ "parameters": { "warp_task": "FiducialCorrelationWarp", "global_align_task": "SimpleGlobalAlignment", - "diameter": 50, "method": "cellpose", + "diameter": 50, "compartment_channel_name": "DAPI" } - }, + }, { "task": "CleanCellBoundaries", "module": "merlin.analysis.segment", + "analysis_name": "CellposeCleanCellBoundaries", "parameters": { "segment_task": "CellposeSegment", "global_align_task": "SimpleGlobalAlignment" @@ -34,24 +24,68 @@ { "task": "CombineCleanedBoundaries", "module": "merlin.analysis.segment", + "analysis_name": "CellposeCombineCleanedBoundaries", "parameters": { - "cleaning_task": "CleanCellBoundaries" + "cleaning_task": "CellposeCleanCellBoundaries" } }, { "task": "RefineCellDatabases", "module": "merlin.analysis.segment", + "analysis_name": "CellposeRefineCellDatabases", "parameters": { "segment_task": "CellposeSegment", - "combine_cleaning_task": "CombineCleanedBoundaries" + "combine_cleaning_task": "CellposeCombineCleanedBoundaries" + } + }, + { + "task": "PartitionBarcodes", + "module": "merlin.analysis.partition", + "analysis_name": "CellposePartitionBarcodes", + "parameters": { + "filter_task": "AdaptiveFilterBarcodes", + "assignment_task": "CellposeRefineCellDatabases", + "alignment_task": "SimpleGlobalAlignment" + } + }, + { + "task": "ExportPartitionedBarcodes", + "module": "merlin.analysis.partition", + "analysis_name": "CellposeExportPartitionedBarcodes", + "parameters": { + "partition_task": "CellposePartitionBarcodes" } }, { "task": "ExportCellMetadata", "module": "merlin.analysis.segment", + "analysis_name": "CellposeExportCellMetadata", + "parameters": { + "segment_task": "CellposeRefineCellDatabases" + } + }, + { + "task": "SumSignal", + "module": "merlin.analysis.sequential", + "analysis_name": "CellposeSumSignal", + "parameters": { + "z_index": 0, + "apply_highpass": true, + "warp_task": "FiducialCorrelationWarp", + "highpass_sigma": 5, + "segment_task": "CellposeRefineCellDatabases", + "global_align_task": "SimpleGlobalAlignment" + } + }, + { + "task": "ExportSumSignals", + "module": "merlin.analysis.sequential", + "analysis_name": "CellposeExportSumSignals", "parameters": { - "segment_task": "RefineCellDatabases" + "sequential_task": "CellposeSumSignal" } } + ] + } diff --git a/test/auxiliary_files/test_analysis_segmentation_cv2.json b/test/auxiliary_files/test_analysis_segmentation_cv2.json index 4fd55a31..195c23d8 100755 --- a/test/auxiliary_files/test_analysis_segmentation_cv2.json +++ b/test/auxiliary_files/test_analysis_segmentation_cv2.json @@ -1,16 +1,5 @@ { "analysis_tasks": [ - { - "task": "FiducialCorrelationWarp", - "module": "merlin.analysis.warp", - "parameters": { - "write_aligned_images": true - } - }, - { - "task": "SimpleGlobalAlignment", - "module": "merlin.analysis.globalalign" - }, { "task": "WatershedSegmentNucleiCV2", "module": "merlin.analysis.segment", @@ -18,12 +7,14 @@ "parameters": { "warp_task": "FiducialCorrelationWarp", "global_align_task": "SimpleGlobalAlignment", - "" + "membrane_channel_name": "DAPI", + "compartment_channel_name": "DAPI" } }, { "task": "CleanCellBoundaries", "module": "merlin.analysis.segment", + "analysis_name": "CV2CleanCellBoundaries", "parameters": { "segment_task": "CV2Segment", "global_align_task": "SimpleGlobalAlignment" @@ -32,24 +23,68 @@ { "task": "CombineCleanedBoundaries", "module": "merlin.analysis.segment", + "analysis_name": "CV2CombineCleanedBoundaries", "parameters": { - "cleaning_task": "CleanCellBoundaries" + "cleaning_task": "CV2CleanCellBoundaries" } }, { "task": "RefineCellDatabases", "module": "merlin.analysis.segment", + "analysis_name": "CV2RefineCellDatabases", "parameters": { "segment_task": "CV2Segment", - "combine_cleaning_task": "CombineCleanedBoundaries" + "combine_cleaning_task": "CV2CombineCleanedBoundaries" + } + }, + { + "task": "PartitionBarcodes", + "module": "merlin.analysis.partition", + "analysis_name": "CV2PartitionBarcodes", + "parameters": { + "filter_task": "AdaptiveFilterBarcodes", + "assignment_task": "CV2RefineCellDatabases", + "alignment_task": "SimpleGlobalAlignment" + } + }, + { + "task": "ExportPartitionedBarcodes", + "module": "merlin.analysis.partition", + "analysis_name": "CV2ExportPartitionedBarcodes", + "parameters": { + "partition_task": "CV2PartitionBarcodes" } }, { "task": "ExportCellMetadata", "module": "merlin.analysis.segment", + "analysis_name": "CV2ExportCellMetadata", + "parameters": { + "segment_task": "CV2RefineCellDatabases" + } + }, + { + "task": "SumSignal", + "module": "merlin.analysis.sequential", + "analysis_name": "CV2SumSignal", + "parameters": { + "z_index": 0, + "apply_highpass": true, + "warp_task": "FiducialCorrelationWarp", + "highpass_sigma": 5, + "segment_task": "CV2RefineCellDatabases", + "global_align_task": "SimpleGlobalAlignment" + } + }, + { + "task": "ExportSumSignals", + "module": "merlin.analysis.sequential", + "analysis_name": "CV2ExportSumSignals", "parameters": { - "segment_task": "RefineCellDatabases" + "sequential_task": "CV2SumSignal" } } + ] + } From 20c659ae73db9debcd35fd8c0331ac6fb29578d6 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 8 Jul 2020 16:24:57 -0400 Subject: [PATCH 403/419] hard-coded to variable array size in convert_grayscale_to_rgb --- merlin/util/segmentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index e7291a05..5bad9c3d 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -360,7 +360,7 @@ def convert_grayscale_to_rgb(uint16Image: np.ndarray) -> np.ndarray: ratio = np.amax(uint16Image) / 256 uint8Image = (uint16Image / ratio).astype('uint8') - rgbImage = np.zeros((2048, 2048, 3)) + rgbImage = np.zeros((uint16Image.shape[0], uint16Image.shape[1], 3)) rgbImage[:, :, 0] = uint8Image rgbImage[:, :, 1] = uint8Image rgbImage[:, :, 2] = uint8Image From 0bbf2c7fd6c8648b1eca52176830202b56a3d160 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 8 Jul 2020 16:34:56 -0400 Subject: [PATCH 404/419] added print statement for debogging --- merlin/util/segmentation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 5bad9c3d..7bd244f5 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -360,6 +360,8 @@ def convert_grayscale_to_rgb(uint16Image: np.ndarray) -> np.ndarray: ratio = np.amax(uint16Image) / 256 uint8Image = (uint16Image / ratio).astype('uint8') + print('size = [' + str(uint16Image.shape[0]) + ', ' + str(uint16Image.shape[1]) + ']') + rgbImage = np.zeros((uint16Image.shape[0], uint16Image.shape[1], 3)) rgbImage[:, :, 0] = uint8Image rgbImage[:, :, 1] = uint8Image From 297919686b7b2b61ca71fdeb479078806973a268 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 8 Jul 2020 16:59:09 -0400 Subject: [PATCH 405/419] change test function names; added fiducial warp and globalign for seg tests --- .../test_analysis_segmentation_cellpose.json | 25 ++++++++++++++----- .../test_analysis_segmentation_cv2.json | 25 ++++++++++++++----- test/test_segmentation_cellpose.py | 2 +- test/test_segmentation_cv2.py | 2 +- 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/test/auxiliary_files/test_analysis_segmentation_cellpose.json b/test/auxiliary_files/test_analysis_segmentation_cellpose.json index 8b887797..9a9086a4 100755 --- a/test/auxiliary_files/test_analysis_segmentation_cellpose.json +++ b/test/auxiliary_files/test_analysis_segmentation_cellpose.json @@ -1,12 +1,25 @@ { "analysis_tasks": [ + { + "task": "FiducialCorrelationWarp", + "module": "merlin.analysis.warp", + "analysis_name": "CellposeFiducialCorrelationWarp", + "parameters": { + "write_aligned_images": true + } + }, + { + "task": "SimpleGlobalAlignment", + "module": "merlin.analysis.globalalign", + "analysis_name": "CellposeSimpleGlobalAlignment", + }, { "task": "MachineLearningSegment", "module": "merlin.analysis.segment", "analysis_name": "CellposeSegment", "parameters": { - "warp_task": "FiducialCorrelationWarp", - "global_align_task": "SimpleGlobalAlignment", + "warp_task": "CellposeFiducialCorrelationWarp", + "global_align_task": "CellposeSimpleGlobalAlignment", "method": "cellpose", "diameter": 50, "compartment_channel_name": "DAPI" @@ -18,7 +31,7 @@ "analysis_name": "CellposeCleanCellBoundaries", "parameters": { "segment_task": "CellposeSegment", - "global_align_task": "SimpleGlobalAlignment" + "global_align_task": "CellposeSimpleGlobalAlignment" } }, { @@ -45,7 +58,7 @@ "parameters": { "filter_task": "AdaptiveFilterBarcodes", "assignment_task": "CellposeRefineCellDatabases", - "alignment_task": "SimpleGlobalAlignment" + "alignment_task": "CellposeSimpleGlobalAlignment" } }, { @@ -71,10 +84,10 @@ "parameters": { "z_index": 0, "apply_highpass": true, - "warp_task": "FiducialCorrelationWarp", + "warp_task": "CellposeFiducialCorrelationWarp", "highpass_sigma": 5, "segment_task": "CellposeRefineCellDatabases", - "global_align_task": "SimpleGlobalAlignment" + "global_align_task": "CellposeSimpleGlobalAlignment" } }, { diff --git a/test/auxiliary_files/test_analysis_segmentation_cv2.json b/test/auxiliary_files/test_analysis_segmentation_cv2.json index 195c23d8..a2a538b3 100755 --- a/test/auxiliary_files/test_analysis_segmentation_cv2.json +++ b/test/auxiliary_files/test_analysis_segmentation_cv2.json @@ -1,12 +1,25 @@ { "analysis_tasks": [ + { + "task": "FiducialCorrelationWarp", + "module": "merlin.analysis.warp", + "analysis_name": "CV2FiducialCorrelationWarp", + "parameters": { + "write_aligned_images": true + } + }, + { + "task": "SimpleGlobalAlignment", + "module": "merlin.analysis.globalalign", + "analysis_name": "CV2SimpleGlobalAlignment", + }, { "task": "WatershedSegmentNucleiCV2", "module": "merlin.analysis.segment", "analysis_name": "CV2Segment", "parameters": { - "warp_task": "FiducialCorrelationWarp", - "global_align_task": "SimpleGlobalAlignment", + "warp_task": "CV2FiducialCorrelationWarp", + "global_align_task": "CV2SimpleGlobalAlignment", "membrane_channel_name": "DAPI", "compartment_channel_name": "DAPI" } @@ -17,7 +30,7 @@ "analysis_name": "CV2CleanCellBoundaries", "parameters": { "segment_task": "CV2Segment", - "global_align_task": "SimpleGlobalAlignment" + "global_align_task": "CV2SimpleGlobalAlignment" } }, { @@ -44,7 +57,7 @@ "parameters": { "filter_task": "AdaptiveFilterBarcodes", "assignment_task": "CV2RefineCellDatabases", - "alignment_task": "SimpleGlobalAlignment" + "alignment_task": "CV2SimpleGlobalAlignment" } }, { @@ -70,10 +83,10 @@ "parameters": { "z_index": 0, "apply_highpass": true, - "warp_task": "FiducialCorrelationWarp", + "warp_task": "CV2FiducialCorrelationWarp", "highpass_sigma": 5, "segment_task": "CV2RefineCellDatabases", - "global_align_task": "SimpleGlobalAlignment" + "global_align_task": "CV2SimpleGlobalAlignment" } }, { diff --git a/test/test_segmentation_cellpose.py b/test/test_segmentation_cellpose.py index 4814cbe2..81989cf7 100755 --- a/test/test_segmentation_cellpose.py +++ b/test/test_segmentation_cellpose.py @@ -7,7 +7,7 @@ @pytest.mark.fullrun @pytest.mark.slowtest -def test_merfish_2d_full_local(simple_merfish_data): +def test_cellpose_2d_local(simple_merfish_data): with open(os.sep.join([merlin.ANALYSIS_PARAMETERS_HOME, 'test_analysis_segmentation_cellpose.json']), 'r') as f: snakefilePath = m.generate_analysis_tasks_and_snakefile( diff --git a/test/test_segmentation_cv2.py b/test/test_segmentation_cv2.py index ff156f2a..ab41c8fd 100755 --- a/test/test_segmentation_cv2.py +++ b/test/test_segmentation_cv2.py @@ -7,7 +7,7 @@ @pytest.mark.fullrun @pytest.mark.slowtest -def test_merfish_2d_full_local(simple_merfish_data): +def test_cv2_2d_local(simple_merfish_data): with open(os.sep.join([merlin.ANALYSIS_PARAMETERS_HOME, 'test_analysis_segmentation_cv2.json']), 'r') as f: snakefilePath = m.generate_analysis_tasks_and_snakefile( From fff22c090a3c0bca22543ca466e86b86da93e8a6 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 8 Jul 2020 17:13:04 -0400 Subject: [PATCH 406/419] add missing commas to json file --- test/auxiliary_files/test_analysis_segmentation_cellpose.json | 2 +- test/auxiliary_files/test_analysis_segmentation_cv2.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/auxiliary_files/test_analysis_segmentation_cellpose.json b/test/auxiliary_files/test_analysis_segmentation_cellpose.json index 9a9086a4..0efe6407 100755 --- a/test/auxiliary_files/test_analysis_segmentation_cellpose.json +++ b/test/auxiliary_files/test_analysis_segmentation_cellpose.json @@ -11,7 +11,7 @@ { "task": "SimpleGlobalAlignment", "module": "merlin.analysis.globalalign", - "analysis_name": "CellposeSimpleGlobalAlignment", + "analysis_name": "CellposeSimpleGlobalAlignment" }, { "task": "MachineLearningSegment", diff --git a/test/auxiliary_files/test_analysis_segmentation_cv2.json b/test/auxiliary_files/test_analysis_segmentation_cv2.json index a2a538b3..0be4520e 100755 --- a/test/auxiliary_files/test_analysis_segmentation_cv2.json +++ b/test/auxiliary_files/test_analysis_segmentation_cv2.json @@ -11,7 +11,7 @@ { "task": "SimpleGlobalAlignment", "module": "merlin.analysis.globalalign", - "analysis_name": "CV2SimpleGlobalAlignment", + "analysis_name": "CV2SimpleGlobalAlignment" }, { "task": "WatershedSegmentNucleiCV2", From 959a104b61565134df0fd3e2ea64da047f8b50ec Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Wed, 8 Jul 2020 17:29:39 -0400 Subject: [PATCH 407/419] decrease snakemake latency wait --- merlin/merlin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/merlin.py b/merlin/merlin.py index 70bbd542..c41c0c98 100755 --- a/merlin/merlin.py +++ b/merlin/merlin.py @@ -166,7 +166,7 @@ def run_with_snakemake( if 'restart_times' not in snakemakeParameters: snakemakeParameters['restart_times'] = 3 if 'latency_wait' not in snakemakeParameters: - snakemakeParameters['latency_wait'] = 600 + snakemakeParameters['latency_wait'] = 60 snakemake.snakemake(snakefilePath, cores=coreCount, workdir=dataSet.get_snakemake_path(), From de2313db9e4eea8da32136aff174d32cf9252039 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 9 Jul 2020 12:19:34 -0400 Subject: [PATCH 408/419] comment snakemake params --- merlin/merlin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/merlin/merlin.py b/merlin/merlin.py index c41c0c98..af494c7e 100755 --- a/merlin/merlin.py +++ b/merlin/merlin.py @@ -162,12 +162,12 @@ def run_with_snakemake( dataSet: dataset.MERFISHDataSet, snakefilePath: str, coreCount: int, snakemakeParameters: Dict = {}, report: bool = True): print('Running MERlin pipeline through snakemake') - +''' if 'restart_times' not in snakemakeParameters: snakemakeParameters['restart_times'] = 3 if 'latency_wait' not in snakemakeParameters: snakemakeParameters['latency_wait'] = 60 - +''' snakemake.snakemake(snakefilePath, cores=coreCount, workdir=dataSet.get_snakemake_path(), stats=snakefilePath + '.stats', lock=False, From 06e9149c983be1241fc9c83314d8a62fb862e7a7 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 9 Jul 2020 12:26:03 -0400 Subject: [PATCH 409/419] fix comment indentation --- merlin/merlin.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/merlin/merlin.py b/merlin/merlin.py index af494c7e..4cae293e 100755 --- a/merlin/merlin.py +++ b/merlin/merlin.py @@ -162,12 +162,12 @@ def run_with_snakemake( dataSet: dataset.MERFISHDataSet, snakefilePath: str, coreCount: int, snakemakeParameters: Dict = {}, report: bool = True): print('Running MERlin pipeline through snakemake') -''' - if 'restart_times' not in snakemakeParameters: - snakemakeParameters['restart_times'] = 3 - if 'latency_wait' not in snakemakeParameters: - snakemakeParameters['latency_wait'] = 60 -''' + ''' + if 'restart_times' not in snakemakeParameters: + snakemakeParameters['restart_times'] = 3 + if 'latency_wait' not in snakemakeParameters: + snakemakeParameters['latency_wait'] = 60 + ''' snakemake.snakemake(snakefilePath, cores=coreCount, workdir=dataSet.get_snakemake_path(), stats=snakefilePath + '.stats', lock=False, From 039eaf6a69289e8b8f2b3cf4438465220dbf7fee Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 9 Jul 2020 13:23:49 -0400 Subject: [PATCH 410/419] change json files for test segmentation --- ...ysis_parameters_segmentation_cellpose.json | 208 ++++++++++++++++++ ..._analysis_parameters_segmentation_cv2.json | 207 +++++++++++++++++ test/test_segmentation_cellpose.py | 2 +- test/test_segmentation_cv2.py | 2 +- 4 files changed, 417 insertions(+), 2 deletions(-) create mode 100755 test/auxiliary_files/test_analysis_parameters_segmentation_cellpose.json create mode 100755 test/auxiliary_files/test_analysis_parameters_segmentation_cv2.json diff --git a/test/auxiliary_files/test_analysis_parameters_segmentation_cellpose.json b/test/auxiliary_files/test_analysis_parameters_segmentation_cellpose.json new file mode 100755 index 00000000..4b917347 --- /dev/null +++ b/test/auxiliary_files/test_analysis_parameters_segmentation_cellpose.json @@ -0,0 +1,208 @@ +{ + "analysis_tasks": [ + { + "task": "FiducialCorrelationWarp", + "module": "merlin.analysis.warp", + "analysis_name": "CellposeFiducialCorrelationWarp", + "parameters": { + "write_aligned_images": true + } + }, + { + "task": "DeconvolutionPreprocess", + "module": "merlin.analysis.preprocess", + "analysis_name": "CellposeDeconvolutionPreprocess", + "parameters": { + "warp_task": "CellposeFiducialCorrelationWarp" + } + }, + { + "task": "OptimizeIteration", + "module": "merlin.analysis.optimize", + "analysis_name": "CellposeOptimize1", + "parameters": { + "preprocess_task": "CellposeDeconvolutionPreprocess", + "warp_task": "CellposeFiducialCorrelationWarp", + "fov_per_iteration": 2, + "iteration_count": 2, + "optimize_chromatic_correction": false + } + }, + { + "task": "OptimizeIteration", + "module": "merlin.analysis.optimize", + "analysis_name": "CellposeOptimize2", + "parameters": { + "preprocess_task": "CellposeDeconvolutionPreprocess", + "warp_task": "CellposeFiducialCorrelationWarp", + "fov_per_iteration": 2, + "iteration_count": 2, + "optimize_chromatic_correction": false, + "previous_iteration": "CellposeOptimize1" + } + }, + { + "task": "Decode", + "module": "merlin.analysis.decode", + "analysis_name": "CellposeDecode", + "parameters": { + "preprocess_task": "CellposeDeconvolutionPreprocess", + "optimize_task": "CellposeOptimize2", + "global_align_task": "CellposeSimpleGlobalAlignment", + "crop_width": 10, + "remove_z_duplicated_barcodes": true, + "z_duplicate_zPlane_threshold": 1, + "z_duplicate_xy_pixel_threshold": 1.414 + } + }, + { + "task": "SimpleGlobalAlignment", + "module": "merlin.analysis.globalalign", + "analysis_name": "CellposeSimpleGlobalAlignment" + }, + { + "task": "GenerateMosaic", + "module": "merlin.analysis.generatemosaic", + "analysis_name": "CellposeGenerateMosaic", + "parameters": { + "global_align_task": "CellposeSimpleGlobalAlignment", + "warp_task": "CellposeFiducialCorrelationWarp" + } + }, + { + "task": "FilterBarcodes", + "module": "merlin.analysis.filterbarcodes", + "analysis_name": "CellposeFilterBarcodes", + "parameters": { + "decode_task": "CellposeDecode", + "area_threshold": 5, + "intensity_threshold": 1 + } + }, + { + "task": "GenerateAdaptiveThreshold", + "module": "merlin.analysis.filterbarcodes", + "analysis_name": "CellposeGenerateAdaptiveThreshold", + "parameters": { + "decode_task": "CellposeDecode", + "run_after_task": "CellposeDecode" + } + }, + { + "task": "AdaptiveFilterBarcodes", + "module": "merlin.analysis.filterbarcodes", + "analysis_name": "CellposeAdaptiveFilterBarcodes", + "parameters": { + "decode_task": "CellposeDecode", + "adaptive_task": "CellposeGenerateAdaptiveThreshold" + } + }, + { + "task": "ExportBarcodes", + "module": "merlin.analysis.exportbarcodes", + "analysis_tasks": "CellposeExportBarcodes", + "parameters": { + "filter_task": "CellposeFilterBarcodes" + } + }, + { + "task": "PlotPerformance", + "module": "merlin.analysis.plotperformance", + "analysis_name": "CellposePlotPerformance", + "parameters": { + "preprocess_task": "CellposeDeconvolutionPreprocess", + "optimize_task": "CellposeOptimize2", + "decode_task": "CellposeDecode", + "filter_task": "CellposeAdaptiveFilterBarcodes", + "global_align_task": "CellposeSimpleGlobalAlignment" + } + }, + { + "task": "MachineLearningSegment", + "module": "merlin.analysis.segment", + "analysis_name": "CellposeSegment", + "parameters": { + "warp_task": "CellposeFiducialCorrelationWarp", + "global_align_task": "CellposeSimpleGlobalAlignment", + "method": "cellpose", + "diameter": 50, + "compartment_channel_name": "DAPI" + } + }, + { + "task": "CleanCellBoundaries", + "module": "merlin.analysis.segment", + "analysis_name": "CellposeCleanCellBoundaries", + "parameters": { + "segment_task": "CellposeSegment", + "global_align_task": "CellposeSimpleGlobalAlignment" + } + }, + { + "task": "CombineCleanedBoundaries", + "module": "merlin.analysis.segment", + "analysis_name": "CellposeCombineCleanedBoundaries", + "parameters": { + "cleaning_task": "CellposeCleanCellBoundaries" + } + }, + { + "task": "RefineCellDatabases", + "module": "merlin.analysis.segment", + "analysis_name": "CellposeRefineCellDatabases", + "parameters": { + "segment_task": "CellposeSegment", + "combine_cleaning_task": "CellposeCombineCleanedBoundaries" + } + }, + { + "task": "PartitionBarcodes", + "module": "merlin.analysis.partition", + "analysis_name": "CellposePartitionBarcodes", + "parameters": { + "filter_task": "AdaptiveFilterBarcodes", + "assignment_task": "CellposeRefineCellDatabases", + "alignment_task": "CellposeSimpleGlobalAlignment" + } + }, + { + "task": "ExportPartitionedBarcodes", + "module": "merlin.analysis.partition", + "analysis_name": "CellposeExportPartitionedBarcodes", + "parameters": { + "partition_task": "CellposePartitionBarcodes" + } + }, + { + "task": "ExportCellMetadata", + "module": "merlin.analysis.segment", + "analysis_name": "CellposeExportCellMetadata", + "parameters": { + "segment_task": "CellposeRefineCellDatabases" + } + }, + { + "task": "SumSignal", + "module": "merlin.analysis.sequential", + "analysis_name": "CellposeSumSignal", + "parameters": { + "z_index": 0, + "apply_highpass": true, + "warp_task": "CellposeFiducialCorrelationWarp", + "highpass_sigma": 5, + "segment_task": "CellposeRefineCellDatabases", + "global_align_task": "CellposeSimpleGlobalAlignment" + } + }, + { + "task": "ExportSumSignals", + "module": "merlin.analysis.sequential", + "analysis_name": "CellposeExportSumSignals", + "parameters": { + "sequential_task": "CellposeSumSignal" + } + } + + ] + +} diff --git a/test/auxiliary_files/test_analysis_parameters_segmentation_cv2.json b/test/auxiliary_files/test_analysis_parameters_segmentation_cv2.json new file mode 100755 index 00000000..d8912594 --- /dev/null +++ b/test/auxiliary_files/test_analysis_parameters_segmentation_cv2.json @@ -0,0 +1,207 @@ +{ + "analysis_tasks": [ + { + "task": "FiducialCorrelationWarp", + "module": "merlin.analysis.warp", + "analysis_name": "CV2FiducialCorrelationWarp", + "parameters": { + "write_aligned_images": true + } + }, + { + "task": "DeconvolutionPreprocess", + "module": "merlin.analysis.preprocess", + "analysis_name": "CV2DeconvolutionPreprocess", + "parameters": { + "warp_task": "CV2FiducialCorrelationWarp" + } + }, + { + "task": "OptimizeIteration", + "module": "merlin.analysis.optimize", + "analysis_name": "CV2Optimize1", + "parameters": { + "preprocess_task": "CV2DeconvolutionPreprocess", + "warp_task": "CV2FiducialCorrelationWarp", + "fov_per_iteration": 2, + "iteration_count": 2, + "optimize_chromatic_correction": false + } + }, + { + "task": "OptimizeIteration", + "module": "merlin.analysis.optimize", + "analysis_name": "CV2Optimize2", + "parameters": { + "preprocess_task": "CV2DeconvolutionPreprocess", + "warp_task": "CV2FiducialCorrelationWarp", + "fov_per_iteration": 2, + "iteration_count": 2, + "optimize_chromatic_correction": false, + "previous_iteration": "CV2Optimize1" + } + }, + { + "task": "Decode", + "module": "merlin.analysis.decode", + "analysis_name": "CV2Decode", + "parameters": { + "preprocess_task": "CV2DeconvolutionPreprocess", + "optimize_task": "CV2Optimize2", + "global_align_task": "CV2SimpleGlobalAlignment", + "crop_width": 10, + "remove_z_duplicated_barcodes": true, + "z_duplicate_zPlane_threshold": 1, + "z_duplicate_xy_pixel_threshold": 1.414 + } + }, + { + "task": "SimpleGlobalAlignment", + "module": "merlin.analysis.globalalign", + "analysis_name": "CV2SimpleGlobalAlignment" + }, + { + "task": "GenerateMosaic", + "module": "merlin.analysis.generatemosaic", + "analysis_name": "CV2GenerateMosaic", + "parameters": { + "global_align_task": "CV2SimpleGlobalAlignment", + "warp_task": "CV2FiducialCorrelationWarp" + } + }, + { + "task": "FilterBarcodes", + "module": "merlin.analysis.filterbarcodes", + "analysis_name": "CV2FilterBarcodes", + "parameters": { + "decode_task": "CV2Decode", + "area_threshold": 5, + "intensity_threshold": 1 + } + }, + { + "task": "GenerateAdaptiveThreshold", + "module": "merlin.analysis.filterbarcodes", + "analysis_name": "CV2GenerateAdaptiveThreshold", + "parameters": { + "decode_task": "CV2Decode", + "run_after_task": "CV2Decode" + } + }, + { + "task": "AdaptiveFilterBarcodes", + "module": "merlin.analysis.filterbarcodes", + "analysis_name": "CV2AdaptiveFilterBarcodes", + "parameters": { + "decode_task": "CV2Decode", + "adaptive_task": "CV2GenerateAdaptiveThreshold" + } + }, + { + "task": "ExportBarcodes", + "module": "merlin.analysis.exportbarcodes", + "analysis_tasks": "CV2ExportBarcodes", + "parameters": { + "filter_task": "CV2FilterBarcodes" + } + }, + { + "task": "PlotPerformance", + "module": "merlin.analysis.plotperformance", + "analysis_name": "CV2PlotPerformance", + "parameters": { + "preprocess_task": "CV2DeconvolutionPreprocess", + "optimize_task": "CV2Optimize2", + "decode_task": "CV2Decode", + "filter_task": "CV2AdaptiveFilterBarcodes", + "global_align_task": "CV2SimpleGlobalAlignment" + } + }, + { + "task": "WatershedSegmentNucleiCV2", + "module": "merlin.analysis.segment", + "analysis_name": "CV2Segment", + "parameters": { + "warp_task": "CV2FiducialCorrelationWarp", + "global_align_task": "CV2SimpleGlobalAlignment", + "membrane_channel_name": "DAPI", + "compartment_channel_name": "DAPI" + } + }, + { + "task": "CleanCellBoundaries", + "module": "merlin.analysis.segment", + "analysis_name": "CV2CleanCellBoundaries", + "parameters": { + "segment_task": "CV2Segment", + "global_align_task": "CV2SimpleGlobalAlignment" + } + }, + { + "task": "CombineCleanedBoundaries", + "module": "merlin.analysis.segment", + "analysis_name": "CV2CombineCleanedBoundaries", + "parameters": { + "cleaning_task": "CV2CleanCellBoundaries" + } + }, + { + "task": "RefineCellDatabases", + "module": "merlin.analysis.segment", + "analysis_name": "CV2RefineCellDatabases", + "parameters": { + "segment_task": "CV2Segment", + "combine_cleaning_task": "CV2CombineCleanedBoundaries" + } + }, + { + "task": "PartitionBarcodes", + "module": "merlin.analysis.partition", + "analysis_name": "CV2PartitionBarcodes", + "parameters": { + "filter_task": "CV2AdaptiveFilterBarcodes", + "assignment_task": "CV2RefineCellDatabases", + "alignment_task": "CV2SimpleGlobalAlignment" + } + }, + { + "task": "ExportPartitionedBarcodes", + "module": "merlin.analysis.partition", + "analysis_name": "CV2ExportPartitionedBarcodes", + "parameters": { + "partition_task": "CV2PartitionBarcodes" + } + }, + { + "task": "ExportCellMetadata", + "module": "merlin.analysis.segment", + "analysis_name": "CV2ExportCellMetadata", + "parameters": { + "segment_task": "CV2RefineCellDatabases" + } + }, + { + "task": "SumSignal", + "module": "merlin.analysis.sequential", + "analysis_name": "CV2SumSignal", + "parameters": { + "z_index": 0, + "apply_highpass": true, + "warp_task": "CV2FiducialCorrelationWarp", + "highpass_sigma": 5, + "segment_task": "CV2RefineCellDatabases", + "global_align_task": "CV2SimpleGlobalAlignment" + } + }, + { + "task": "ExportSumSignals", + "module": "merlin.analysis.sequential", + "analysis_name": "CV2ExportSumSignals", + "parameters": { + "sequential_task": "CV2SumSignal" + } + } + + ] + +} diff --git a/test/test_segmentation_cellpose.py b/test/test_segmentation_cellpose.py index 81989cf7..ff6c5ef2 100755 --- a/test/test_segmentation_cellpose.py +++ b/test/test_segmentation_cellpose.py @@ -9,7 +9,7 @@ @pytest.mark.slowtest def test_cellpose_2d_local(simple_merfish_data): with open(os.sep.join([merlin.ANALYSIS_PARAMETERS_HOME, - 'test_analysis_segmentation_cellpose.json']), 'r') as f: + 'test_analysis_parameters_segmentation_cellpose.json']), 'r') as f: snakefilePath = m.generate_analysis_tasks_and_snakefile( simple_merfish_data, f) m.run_with_snakemake(simple_merfish_data, snakefilePath, 5) diff --git a/test/test_segmentation_cv2.py b/test/test_segmentation_cv2.py index ab41c8fd..805d6888 100755 --- a/test/test_segmentation_cv2.py +++ b/test/test_segmentation_cv2.py @@ -9,7 +9,7 @@ @pytest.mark.slowtest def test_cv2_2d_local(simple_merfish_data): with open(os.sep.join([merlin.ANALYSIS_PARAMETERS_HOME, - 'test_analysis_segmentation_cv2.json']), 'r') as f: + 'test_analysis_parameters_segmentation_cv2.json']), 'r') as f: snakefilePath = m.generate_analysis_tasks_and_snakefile( simple_merfish_data, f) m.run_with_snakemake(simple_merfish_data, snakefilePath, 5) From 1940353d55c7220c90c07cfbfe1e0f9a91e2a1d9 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 9 Jul 2020 13:25:04 -0400 Subject: [PATCH 411/419] update conftest with new json names --- test/conftest.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index 6a00262e..b9578719 100755 --- a/test/conftest.py +++ b/test/conftest.py @@ -59,14 +59,14 @@ def base_files(): [merlin.ANALYSIS_PARAMETERS_HOME, 'test_analysis_parameters.json'])) shutil.copyfile( os.sep.join( - [root, 'auxiliary_files', 'test_analysis_segmentation_cellpose.json']), + [root, 'auxiliary_files', 'test_analysis_parameters_segmentation_cellpose.json']), os.sep.join( - [merlin.ANALYSIS_PARAMETERS_HOME, 'test_analysis_segmentation_cellpose.json'])) + [merlin.ANALYSIS_PARAMETERS_HOME, 'test_analysis_parameters_segmentation_cellpose.json'])) shutil.copyfile( os.sep.join( - [root, 'auxiliary_files', 'test_analysis_segmentation_cv2.json']), + [root, 'auxiliary_files', 'test_analysis_parameters_segmentation_cv2.json']), os.sep.join( - [merlin.ANALYSIS_PARAMETERS_HOME, 'test_analysis_segmentation_cv2.json'])) + [merlin.ANALYSIS_PARAMETERS_HOME, 'test_analysis_parameters_segmentation_cv2.json'])) shutil.copyfile( os.sep.join( [root, 'auxiliary_files', 'test_microscope_parameters.json']), From 1e90523a0d72ca6b4d06d59fde945863ccdeb634 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 9 Jul 2020 13:37:29 -0400 Subject: [PATCH 412/419] corrected variable typo --- .../test_analysis_parameters_segmentation_cellpose.json | 2 +- .../test_analysis_parameters_segmentation_cv2.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/auxiliary_files/test_analysis_parameters_segmentation_cellpose.json b/test/auxiliary_files/test_analysis_parameters_segmentation_cellpose.json index 4b917347..18b7f1ec 100755 --- a/test/auxiliary_files/test_analysis_parameters_segmentation_cellpose.json +++ b/test/auxiliary_files/test_analysis_parameters_segmentation_cellpose.json @@ -100,7 +100,7 @@ { "task": "ExportBarcodes", "module": "merlin.analysis.exportbarcodes", - "analysis_tasks": "CellposeExportBarcodes", + "analysis_name": "CellposeExportBarcodes", "parameters": { "filter_task": "CellposeFilterBarcodes" } diff --git a/test/auxiliary_files/test_analysis_parameters_segmentation_cv2.json b/test/auxiliary_files/test_analysis_parameters_segmentation_cv2.json index d8912594..982129e3 100755 --- a/test/auxiliary_files/test_analysis_parameters_segmentation_cv2.json +++ b/test/auxiliary_files/test_analysis_parameters_segmentation_cv2.json @@ -100,7 +100,7 @@ { "task": "ExportBarcodes", "module": "merlin.analysis.exportbarcodes", - "analysis_tasks": "CV2ExportBarcodes", + "analysis_name": "CV2ExportBarcodes", "parameters": { "filter_task": "CV2FilterBarcodes" } From caf0518f95eca0b9dc3a5f832820f9e30505261c Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 9 Jul 2020 14:05:40 -0400 Subject: [PATCH 413/419] change test name --- ...entation_cellpose.py => test_merfish_segmentation_cellpose.py} | 0 ...{test_segmentation_cv2.py => test_merfish_segmentation_cv2.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename test/{test_segmentation_cellpose.py => test_merfish_segmentation_cellpose.py} (100%) rename test/{test_segmentation_cv2.py => test_merfish_segmentation_cv2.py} (100%) diff --git a/test/test_segmentation_cellpose.py b/test/test_merfish_segmentation_cellpose.py similarity index 100% rename from test/test_segmentation_cellpose.py rename to test/test_merfish_segmentation_cellpose.py diff --git a/test/test_segmentation_cv2.py b/test/test_merfish_segmentation_cv2.py similarity index 100% rename from test/test_segmentation_cv2.py rename to test/test_merfish_segmentation_cv2.py From b9f70888cafacc4fad8d635d6b692bde82124512 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 9 Jul 2020 14:40:12 -0400 Subject: [PATCH 414/419] simplify test json --- test/conftest.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index b9578719..6a00262e 100755 --- a/test/conftest.py +++ b/test/conftest.py @@ -59,14 +59,14 @@ def base_files(): [merlin.ANALYSIS_PARAMETERS_HOME, 'test_analysis_parameters.json'])) shutil.copyfile( os.sep.join( - [root, 'auxiliary_files', 'test_analysis_parameters_segmentation_cellpose.json']), + [root, 'auxiliary_files', 'test_analysis_segmentation_cellpose.json']), os.sep.join( - [merlin.ANALYSIS_PARAMETERS_HOME, 'test_analysis_parameters_segmentation_cellpose.json'])) + [merlin.ANALYSIS_PARAMETERS_HOME, 'test_analysis_segmentation_cellpose.json'])) shutil.copyfile( os.sep.join( - [root, 'auxiliary_files', 'test_analysis_parameters_segmentation_cv2.json']), + [root, 'auxiliary_files', 'test_analysis_segmentation_cv2.json']), os.sep.join( - [merlin.ANALYSIS_PARAMETERS_HOME, 'test_analysis_parameters_segmentation_cv2.json'])) + [merlin.ANALYSIS_PARAMETERS_HOME, 'test_analysis_segmentation_cv2.json'])) shutil.copyfile( os.sep.join( [root, 'auxiliary_files', 'test_microscope_parameters.json']), From 5bbd4f680cee4c2c0ed6fba9a096b0445ba712b3 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 9 Jul 2020 14:46:34 -0400 Subject: [PATCH 415/419] change json name in test py file --- test/test_merfish_segmentation_cellpose.py | 2 +- test/test_merfish_segmentation_cv2.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_merfish_segmentation_cellpose.py b/test/test_merfish_segmentation_cellpose.py index ff6c5ef2..81989cf7 100755 --- a/test/test_merfish_segmentation_cellpose.py +++ b/test/test_merfish_segmentation_cellpose.py @@ -9,7 +9,7 @@ @pytest.mark.slowtest def test_cellpose_2d_local(simple_merfish_data): with open(os.sep.join([merlin.ANALYSIS_PARAMETERS_HOME, - 'test_analysis_parameters_segmentation_cellpose.json']), 'r') as f: + 'test_analysis_segmentation_cellpose.json']), 'r') as f: snakefilePath = m.generate_analysis_tasks_and_snakefile( simple_merfish_data, f) m.run_with_snakemake(simple_merfish_data, snakefilePath, 5) diff --git a/test/test_merfish_segmentation_cv2.py b/test/test_merfish_segmentation_cv2.py index 805d6888..ab41c8fd 100755 --- a/test/test_merfish_segmentation_cv2.py +++ b/test/test_merfish_segmentation_cv2.py @@ -9,7 +9,7 @@ @pytest.mark.slowtest def test_cv2_2d_local(simple_merfish_data): with open(os.sep.join([merlin.ANALYSIS_PARAMETERS_HOME, - 'test_analysis_parameters_segmentation_cv2.json']), 'r') as f: + 'test_analysis_segmentation_cv2.json']), 'r') as f: snakefilePath = m.generate_analysis_tasks_and_snakefile( simple_merfish_data, f) m.run_with_snakemake(simple_merfish_data, snakefilePath, 5) From 9f2e68afcea7ea7ad4ba2f2717b5f405d6248540 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Thu, 9 Jul 2020 18:33:42 -0400 Subject: [PATCH 416/419] simplify json to increase codecov --- ...ysis_parameters_segmentation_cellpose.json | 208 ------------------ ..._analysis_parameters_segmentation_cv2.json | 207 ----------------- .../test_analysis_segmentation_cellpose.json | 5 +- .../test_analysis_segmentation_cv2.json | 4 +- 4 files changed, 2 insertions(+), 422 deletions(-) delete mode 100755 test/auxiliary_files/test_analysis_parameters_segmentation_cellpose.json delete mode 100755 test/auxiliary_files/test_analysis_parameters_segmentation_cv2.json diff --git a/test/auxiliary_files/test_analysis_parameters_segmentation_cellpose.json b/test/auxiliary_files/test_analysis_parameters_segmentation_cellpose.json deleted file mode 100755 index 18b7f1ec..00000000 --- a/test/auxiliary_files/test_analysis_parameters_segmentation_cellpose.json +++ /dev/null @@ -1,208 +0,0 @@ -{ - "analysis_tasks": [ - { - "task": "FiducialCorrelationWarp", - "module": "merlin.analysis.warp", - "analysis_name": "CellposeFiducialCorrelationWarp", - "parameters": { - "write_aligned_images": true - } - }, - { - "task": "DeconvolutionPreprocess", - "module": "merlin.analysis.preprocess", - "analysis_name": "CellposeDeconvolutionPreprocess", - "parameters": { - "warp_task": "CellposeFiducialCorrelationWarp" - } - }, - { - "task": "OptimizeIteration", - "module": "merlin.analysis.optimize", - "analysis_name": "CellposeOptimize1", - "parameters": { - "preprocess_task": "CellposeDeconvolutionPreprocess", - "warp_task": "CellposeFiducialCorrelationWarp", - "fov_per_iteration": 2, - "iteration_count": 2, - "optimize_chromatic_correction": false - } - }, - { - "task": "OptimizeIteration", - "module": "merlin.analysis.optimize", - "analysis_name": "CellposeOptimize2", - "parameters": { - "preprocess_task": "CellposeDeconvolutionPreprocess", - "warp_task": "CellposeFiducialCorrelationWarp", - "fov_per_iteration": 2, - "iteration_count": 2, - "optimize_chromatic_correction": false, - "previous_iteration": "CellposeOptimize1" - } - }, - { - "task": "Decode", - "module": "merlin.analysis.decode", - "analysis_name": "CellposeDecode", - "parameters": { - "preprocess_task": "CellposeDeconvolutionPreprocess", - "optimize_task": "CellposeOptimize2", - "global_align_task": "CellposeSimpleGlobalAlignment", - "crop_width": 10, - "remove_z_duplicated_barcodes": true, - "z_duplicate_zPlane_threshold": 1, - "z_duplicate_xy_pixel_threshold": 1.414 - } - }, - { - "task": "SimpleGlobalAlignment", - "module": "merlin.analysis.globalalign", - "analysis_name": "CellposeSimpleGlobalAlignment" - }, - { - "task": "GenerateMosaic", - "module": "merlin.analysis.generatemosaic", - "analysis_name": "CellposeGenerateMosaic", - "parameters": { - "global_align_task": "CellposeSimpleGlobalAlignment", - "warp_task": "CellposeFiducialCorrelationWarp" - } - }, - { - "task": "FilterBarcodes", - "module": "merlin.analysis.filterbarcodes", - "analysis_name": "CellposeFilterBarcodes", - "parameters": { - "decode_task": "CellposeDecode", - "area_threshold": 5, - "intensity_threshold": 1 - } - }, - { - "task": "GenerateAdaptiveThreshold", - "module": "merlin.analysis.filterbarcodes", - "analysis_name": "CellposeGenerateAdaptiveThreshold", - "parameters": { - "decode_task": "CellposeDecode", - "run_after_task": "CellposeDecode" - } - }, - { - "task": "AdaptiveFilterBarcodes", - "module": "merlin.analysis.filterbarcodes", - "analysis_name": "CellposeAdaptiveFilterBarcodes", - "parameters": { - "decode_task": "CellposeDecode", - "adaptive_task": "CellposeGenerateAdaptiveThreshold" - } - }, - { - "task": "ExportBarcodes", - "module": "merlin.analysis.exportbarcodes", - "analysis_name": "CellposeExportBarcodes", - "parameters": { - "filter_task": "CellposeFilterBarcodes" - } - }, - { - "task": "PlotPerformance", - "module": "merlin.analysis.plotperformance", - "analysis_name": "CellposePlotPerformance", - "parameters": { - "preprocess_task": "CellposeDeconvolutionPreprocess", - "optimize_task": "CellposeOptimize2", - "decode_task": "CellposeDecode", - "filter_task": "CellposeAdaptiveFilterBarcodes", - "global_align_task": "CellposeSimpleGlobalAlignment" - } - }, - { - "task": "MachineLearningSegment", - "module": "merlin.analysis.segment", - "analysis_name": "CellposeSegment", - "parameters": { - "warp_task": "CellposeFiducialCorrelationWarp", - "global_align_task": "CellposeSimpleGlobalAlignment", - "method": "cellpose", - "diameter": 50, - "compartment_channel_name": "DAPI" - } - }, - { - "task": "CleanCellBoundaries", - "module": "merlin.analysis.segment", - "analysis_name": "CellposeCleanCellBoundaries", - "parameters": { - "segment_task": "CellposeSegment", - "global_align_task": "CellposeSimpleGlobalAlignment" - } - }, - { - "task": "CombineCleanedBoundaries", - "module": "merlin.analysis.segment", - "analysis_name": "CellposeCombineCleanedBoundaries", - "parameters": { - "cleaning_task": "CellposeCleanCellBoundaries" - } - }, - { - "task": "RefineCellDatabases", - "module": "merlin.analysis.segment", - "analysis_name": "CellposeRefineCellDatabases", - "parameters": { - "segment_task": "CellposeSegment", - "combine_cleaning_task": "CellposeCombineCleanedBoundaries" - } - }, - { - "task": "PartitionBarcodes", - "module": "merlin.analysis.partition", - "analysis_name": "CellposePartitionBarcodes", - "parameters": { - "filter_task": "AdaptiveFilterBarcodes", - "assignment_task": "CellposeRefineCellDatabases", - "alignment_task": "CellposeSimpleGlobalAlignment" - } - }, - { - "task": "ExportPartitionedBarcodes", - "module": "merlin.analysis.partition", - "analysis_name": "CellposeExportPartitionedBarcodes", - "parameters": { - "partition_task": "CellposePartitionBarcodes" - } - }, - { - "task": "ExportCellMetadata", - "module": "merlin.analysis.segment", - "analysis_name": "CellposeExportCellMetadata", - "parameters": { - "segment_task": "CellposeRefineCellDatabases" - } - }, - { - "task": "SumSignal", - "module": "merlin.analysis.sequential", - "analysis_name": "CellposeSumSignal", - "parameters": { - "z_index": 0, - "apply_highpass": true, - "warp_task": "CellposeFiducialCorrelationWarp", - "highpass_sigma": 5, - "segment_task": "CellposeRefineCellDatabases", - "global_align_task": "CellposeSimpleGlobalAlignment" - } - }, - { - "task": "ExportSumSignals", - "module": "merlin.analysis.sequential", - "analysis_name": "CellposeExportSumSignals", - "parameters": { - "sequential_task": "CellposeSumSignal" - } - } - - ] - -} diff --git a/test/auxiliary_files/test_analysis_parameters_segmentation_cv2.json b/test/auxiliary_files/test_analysis_parameters_segmentation_cv2.json deleted file mode 100755 index 982129e3..00000000 --- a/test/auxiliary_files/test_analysis_parameters_segmentation_cv2.json +++ /dev/null @@ -1,207 +0,0 @@ -{ - "analysis_tasks": [ - { - "task": "FiducialCorrelationWarp", - "module": "merlin.analysis.warp", - "analysis_name": "CV2FiducialCorrelationWarp", - "parameters": { - "write_aligned_images": true - } - }, - { - "task": "DeconvolutionPreprocess", - "module": "merlin.analysis.preprocess", - "analysis_name": "CV2DeconvolutionPreprocess", - "parameters": { - "warp_task": "CV2FiducialCorrelationWarp" - } - }, - { - "task": "OptimizeIteration", - "module": "merlin.analysis.optimize", - "analysis_name": "CV2Optimize1", - "parameters": { - "preprocess_task": "CV2DeconvolutionPreprocess", - "warp_task": "CV2FiducialCorrelationWarp", - "fov_per_iteration": 2, - "iteration_count": 2, - "optimize_chromatic_correction": false - } - }, - { - "task": "OptimizeIteration", - "module": "merlin.analysis.optimize", - "analysis_name": "CV2Optimize2", - "parameters": { - "preprocess_task": "CV2DeconvolutionPreprocess", - "warp_task": "CV2FiducialCorrelationWarp", - "fov_per_iteration": 2, - "iteration_count": 2, - "optimize_chromatic_correction": false, - "previous_iteration": "CV2Optimize1" - } - }, - { - "task": "Decode", - "module": "merlin.analysis.decode", - "analysis_name": "CV2Decode", - "parameters": { - "preprocess_task": "CV2DeconvolutionPreprocess", - "optimize_task": "CV2Optimize2", - "global_align_task": "CV2SimpleGlobalAlignment", - "crop_width": 10, - "remove_z_duplicated_barcodes": true, - "z_duplicate_zPlane_threshold": 1, - "z_duplicate_xy_pixel_threshold": 1.414 - } - }, - { - "task": "SimpleGlobalAlignment", - "module": "merlin.analysis.globalalign", - "analysis_name": "CV2SimpleGlobalAlignment" - }, - { - "task": "GenerateMosaic", - "module": "merlin.analysis.generatemosaic", - "analysis_name": "CV2GenerateMosaic", - "parameters": { - "global_align_task": "CV2SimpleGlobalAlignment", - "warp_task": "CV2FiducialCorrelationWarp" - } - }, - { - "task": "FilterBarcodes", - "module": "merlin.analysis.filterbarcodes", - "analysis_name": "CV2FilterBarcodes", - "parameters": { - "decode_task": "CV2Decode", - "area_threshold": 5, - "intensity_threshold": 1 - } - }, - { - "task": "GenerateAdaptiveThreshold", - "module": "merlin.analysis.filterbarcodes", - "analysis_name": "CV2GenerateAdaptiveThreshold", - "parameters": { - "decode_task": "CV2Decode", - "run_after_task": "CV2Decode" - } - }, - { - "task": "AdaptiveFilterBarcodes", - "module": "merlin.analysis.filterbarcodes", - "analysis_name": "CV2AdaptiveFilterBarcodes", - "parameters": { - "decode_task": "CV2Decode", - "adaptive_task": "CV2GenerateAdaptiveThreshold" - } - }, - { - "task": "ExportBarcodes", - "module": "merlin.analysis.exportbarcodes", - "analysis_name": "CV2ExportBarcodes", - "parameters": { - "filter_task": "CV2FilterBarcodes" - } - }, - { - "task": "PlotPerformance", - "module": "merlin.analysis.plotperformance", - "analysis_name": "CV2PlotPerformance", - "parameters": { - "preprocess_task": "CV2DeconvolutionPreprocess", - "optimize_task": "CV2Optimize2", - "decode_task": "CV2Decode", - "filter_task": "CV2AdaptiveFilterBarcodes", - "global_align_task": "CV2SimpleGlobalAlignment" - } - }, - { - "task": "WatershedSegmentNucleiCV2", - "module": "merlin.analysis.segment", - "analysis_name": "CV2Segment", - "parameters": { - "warp_task": "CV2FiducialCorrelationWarp", - "global_align_task": "CV2SimpleGlobalAlignment", - "membrane_channel_name": "DAPI", - "compartment_channel_name": "DAPI" - } - }, - { - "task": "CleanCellBoundaries", - "module": "merlin.analysis.segment", - "analysis_name": "CV2CleanCellBoundaries", - "parameters": { - "segment_task": "CV2Segment", - "global_align_task": "CV2SimpleGlobalAlignment" - } - }, - { - "task": "CombineCleanedBoundaries", - "module": "merlin.analysis.segment", - "analysis_name": "CV2CombineCleanedBoundaries", - "parameters": { - "cleaning_task": "CV2CleanCellBoundaries" - } - }, - { - "task": "RefineCellDatabases", - "module": "merlin.analysis.segment", - "analysis_name": "CV2RefineCellDatabases", - "parameters": { - "segment_task": "CV2Segment", - "combine_cleaning_task": "CV2CombineCleanedBoundaries" - } - }, - { - "task": "PartitionBarcodes", - "module": "merlin.analysis.partition", - "analysis_name": "CV2PartitionBarcodes", - "parameters": { - "filter_task": "CV2AdaptiveFilterBarcodes", - "assignment_task": "CV2RefineCellDatabases", - "alignment_task": "CV2SimpleGlobalAlignment" - } - }, - { - "task": "ExportPartitionedBarcodes", - "module": "merlin.analysis.partition", - "analysis_name": "CV2ExportPartitionedBarcodes", - "parameters": { - "partition_task": "CV2PartitionBarcodes" - } - }, - { - "task": "ExportCellMetadata", - "module": "merlin.analysis.segment", - "analysis_name": "CV2ExportCellMetadata", - "parameters": { - "segment_task": "CV2RefineCellDatabases" - } - }, - { - "task": "SumSignal", - "module": "merlin.analysis.sequential", - "analysis_name": "CV2SumSignal", - "parameters": { - "z_index": 0, - "apply_highpass": true, - "warp_task": "CV2FiducialCorrelationWarp", - "highpass_sigma": 5, - "segment_task": "CV2RefineCellDatabases", - "global_align_task": "CV2SimpleGlobalAlignment" - } - }, - { - "task": "ExportSumSignals", - "module": "merlin.analysis.sequential", - "analysis_name": "CV2ExportSumSignals", - "parameters": { - "sequential_task": "CV2SumSignal" - } - } - - ] - -} diff --git a/test/auxiliary_files/test_analysis_segmentation_cellpose.json b/test/auxiliary_files/test_analysis_segmentation_cellpose.json index 0efe6407..15ac7dcc 100755 --- a/test/auxiliary_files/test_analysis_segmentation_cellpose.json +++ b/test/auxiliary_files/test_analysis_segmentation_cellpose.json @@ -19,10 +19,7 @@ "analysis_name": "CellposeSegment", "parameters": { "warp_task": "CellposeFiducialCorrelationWarp", - "global_align_task": "CellposeSimpleGlobalAlignment", - "method": "cellpose", - "diameter": 50, - "compartment_channel_name": "DAPI" + "global_align_task": "CellposeSimpleGlobalAlignment" } }, { diff --git a/test/auxiliary_files/test_analysis_segmentation_cv2.json b/test/auxiliary_files/test_analysis_segmentation_cv2.json index 0be4520e..fb2e24c3 100755 --- a/test/auxiliary_files/test_analysis_segmentation_cv2.json +++ b/test/auxiliary_files/test_analysis_segmentation_cv2.json @@ -19,9 +19,7 @@ "analysis_name": "CV2Segment", "parameters": { "warp_task": "CV2FiducialCorrelationWarp", - "global_align_task": "CV2SimpleGlobalAlignment", - "membrane_channel_name": "DAPI", - "compartment_channel_name": "DAPI" + "global_align_task": "CV2SimpleGlobalAlignment" } }, { From 0ac2e2e5954194cfec80f9618d8c2861151f1a44 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Mon, 3 Aug 2020 12:21:34 -0400 Subject: [PATCH 417/419] 100>1 in the cv2 segmentation --- merlin/util/segmentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 7bd244f5..0db6609e 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -392,7 +392,7 @@ def apply_cv2_watershed(compartmentImages: np.ndarray, watershedOutput[z, :, :] = cv2.watershed(rgbImage, watershedMarkers[z, :, :]. astype('int32')) - watershedOutput[z, :, :][watershedOutput[z, :, :] <= 100] = 0 + watershedOutput[z, :, :][watershedOutput[z, :, :] <= 0] = 0 return watershedOutput From 1b35306daa8834e58cc1471fe7b8107b9c4cad31 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 8 Jan 2021 15:40:00 -0500 Subject: [PATCH 418/419] add flow_threshold and cellpob_threshold parameters --- merlin/util/segmentation.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/merlin/util/segmentation.py b/merlin/util/segmentation.py index 0db6609e..f4a2f1cc 100755 --- a/merlin/util/segmentation.py +++ b/merlin/util/segmentation.py @@ -392,7 +392,7 @@ def apply_cv2_watershed(compartmentImages: np.ndarray, watershedOutput[z, :, :] = cv2.watershed(rgbImage, watershedMarkers[z, :, :]. astype('int32')) - watershedOutput[z, :, :][watershedOutput[z, :, :] <= 0] = 0 + watershedOutput[z, :, :][watershedOutput[z, :, :] <= 1] = 0 return watershedOutput @@ -510,7 +510,13 @@ def segment_using_cellpose(imageStackIn: np.ndarray, Args: imageStackIn: a 3 dimensional numpy array containing the images arranged as (z, x, y). - params: a dictionary with the parameters for segmentation + params: a dictionary with the parameters for segmentation. + Available parameters: + channel + diameter + flow_threshold + cellprob_threshold + Returns: ndarray containing a 3 dimensional mask arranged as (z, x, y) """ @@ -550,7 +556,11 @@ def segment_using_cellpose(imageStackIn: np.ndarray, masks, flows, styles, diams = model.eval(imageList, diameter=params['diameter'], - channels=channels) + channels=channels, + flow_threshold= + params['flow_threshold'], + cellprob_threshold= + params['cellprob_threshold']) # combine masks into array masksArray = np.stack(masks) From 8d112f6b82ca027d495174eed2d799934f72e9c4 Mon Sep 17 00:00:00 2001 From: leonardosepulveda Date: Fri, 8 Jan 2021 16:09:14 -0500 Subject: [PATCH 419/419] adding flow_thresh and cellprob to machineLearningSegment class --- merlin/analysis/segment.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/merlin/analysis/segment.py b/merlin/analysis/segment.py index e89399dc..8ce22e89 100755 --- a/merlin/analysis/segment.py +++ b/merlin/analysis/segment.py @@ -247,6 +247,10 @@ def __init__(self, dataSet, parameters=None, analysisName=None): self.parameters['diameter'] = 50 if 'compartment_channel_name' not in self.parameters: self.parameters['compartment_channel_name'] = 'DAPI' + if 'flow_threshold' not in self.parameters: + self.parameters['flow_threshold'] = 0.5 + if 'cellprob_threshold' not in self.parameters: + self.parameters['cellprob_threshold'] = 1 def fragment_count(self): return len(self.dataSet.get_fovs()) @@ -286,7 +290,9 @@ def _run_analysis(self, fragmentIndex): segParameters = dict({ 'method': 'cellpose', 'diameter': self.parameters['diameter'], - 'channel': self.parameters['compartment_channel_name'] + 'channel': self.parameters['compartment_channel_name'], + 'flow_threshold': self.parameters['flow_threshold'], + 'cellprob_threshold': self.parameters['cellprob_threshold'] }) segmentationOutput = segmentation.apply_machine_learning_segmentation(