From 39bca6e6da5d4f2ec0b1af3c411b1df907601793 Mon Sep 17 00:00:00 2001 From: nanli-emory Date: Wed, 8 May 2024 15:51:16 -0400 Subject: [PATCH 1/2] create getMaskReginsStats method to get based statistics for tissue ares --- histoqc/BaseImage.py | 28 ++++++++++-- histoqc/BasicModule.py | 9 +++- histoqc/BlurDetectionModule.py | 25 ++++++----- histoqc/MorphologyModule.py | 82 +++++++++++++++++++--------------- histoqc/config/config_v2.1.ini | 6 ++- 5 files changed, 97 insertions(+), 53 deletions(-) diff --git a/histoqc/BaseImage.py b/histoqc/BaseImage.py index 947f7c7..ec310c4 100644 --- a/histoqc/BaseImage.py +++ b/histoqc/BaseImage.py @@ -8,7 +8,7 @@ from typing import Union, Tuple #os.environ['PATH'] = 'C:\\research\\openslide\\bin' + ';' + os.environ['PATH'] #can either specify openslide bin path in PATH, or add it dynamically from histoqc.import_wrapper.openslide import openslide - +from skimage import measure, morphology # there is no branch reset group in re # compatible with the previous definition of valid input: leading zero and leading decimals are supported _REGEX_MAG = r"^(\d?\.?\d*X?)" @@ -205,6 +205,9 @@ def getImgThumb(self, size: str): self[key] = getBestThumb(self, bx, by, target_dims, target_sampling_factor) return self[key] +''' +the followings are helper functions +''' def getBestThumb(s: BaseImage, x: int, y: int, dims: Tuple[int, int], target_sampling_factor: float): osh = s["os_handle"] @@ -223,9 +226,7 @@ def getBestThumb(s: BaseImage, x: int, y: int, dims: Tuple[int, int], target_sam else: return resizeTileDownward(s, target_sampling_factor, level) -''' -the followings are helper functions -''' + def resizeTileDownward(self, target_downsampling_factor, level): osh = self["os_handle"] (bx, by, bwidth, bheight) = self["img_bbox"] @@ -340,3 +341,22 @@ def getDimensionsByOneDim(s: BaseImage, dim: int) -> Tuple[int, int]: else: w = int(dim * width / height) return w, dim + + +def getMaskReginsStats(img, area_threshold: int = 0)-> dict: + + rps = measure.regionprops(morphology.label(img)) + if rps: + areas = np.asarray([rp.area for rp in rps if rp.area > area_threshold]) + num = len(rps) + area_min = areas.min() + area_max = areas.max() + area_mean = areas.mean() + else: + num = area_min = area_max = area_mean = 0 + return { + 'num': num, + 'area_min': area_min, + 'area_max': area_max, + 'area_mean': area_mean + } \ No newline at end of file diff --git a/histoqc/BasicModule.py b/histoqc/BasicModule.py index 418c715..77b08f0 100644 --- a/histoqc/BasicModule.py +++ b/histoqc/BasicModule.py @@ -1,6 +1,6 @@ import logging import os -from histoqc.BaseImage import printMaskHelper +from histoqc.BaseImage import printMaskHelper, getMaskReginsStats from skimage.morphology import remove_small_objects, binary_opening, disk from skimage import io, color, img_as_ubyte @@ -69,3 +69,10 @@ def finalProcessingArea(s, params): f"{s['filename']} - After BasicModule.finalProcessingArea NO tissue remains detectable! Downstream modules likely to be incorrect/fail") s["warnings"].append( f"After BasicModule.finalProcessingArea NO tissue remains detectable! Downstream modules likely to be incorrect/fail") + + +def countTissuePieces(s, params): + area_thresh = int(params.get("area_threshold", "1000")) + mask = s["img_mask_use"] + stats = getMaskReginsStats(mask, area_threshold=area_thresh) + s.addToPrintList("#pieces_of_tissue", str(stats.get('num', 0))) \ No newline at end of file diff --git a/histoqc/BlurDetectionModule.py b/histoqc/BlurDetectionModule.py index 8c55890..12d5d8d 100644 --- a/histoqc/BlurDetectionModule.py +++ b/histoqc/BlurDetectionModule.py @@ -2,7 +2,7 @@ import os import skimage -from histoqc.BaseImage import printMaskHelper +from histoqc.BaseImage import printMaskHelper, getMaskReginsStats from skimage import io, img_as_ubyte, morphology, measure from skimage.color import rgb2gray from skimage.filters import rank @@ -37,19 +37,20 @@ def identifyBlurryRegions(s, params): prev_mask = s["img_mask_use"] s["img_mask_use"] = s["img_mask_use"] & ~s["img_mask_blurry"] - rps = measure.regionprops(morphology.label(mask)) - if rps: - areas = np.asarray([rp.area for rp in rps]) - nobj = len(rps) - area_max = areas.max() - area_mean = areas.mean() - else: - nobj = area_max = area_mean = 0 + # rps = measure.regionprops(morphology.label(mask)) + # if rps: + # areas = np.asarray([rp.area for rp in rps]) + # nobj = len(rps) + # area_max = areas.max() + # area_mean = areas.mean() + # else: + # nobj = area_max = area_mean = 0 + stat_rs = getMaskReginsStats(mask) - s.addToPrintList("blurry_removed_num_regions", str(nobj)) - s.addToPrintList("blurry_removed_mean_area", str(area_mean)) - s.addToPrintList("blurry_removed_max_area", str(area_max)) + # s.addToPrintList("blurry_removed_num_regions", str(nobj)) + # s.addToPrintList("blurry_removed_mean_area", str(area_mean)) + # s.addToPrintList("blurry_removed_max_area", str(area_max)) s.addToPrintList("blurry_removed_percent", diff --git a/histoqc/MorphologyModule.py b/histoqc/MorphologyModule.py index 68e8437..5f715c2 100644 --- a/histoqc/MorphologyModule.py +++ b/histoqc/MorphologyModule.py @@ -1,7 +1,7 @@ import logging import os import numpy as np -from histoqc.BaseImage import printMaskHelper +from histoqc.BaseImage import printMaskHelper, getMaskReginsStats from skimage import io, morphology, img_as_ubyte, measure from scipy import ndimage as ndi @@ -23,19 +23,23 @@ def removeSmallObjects(s, params): s["img_mask_use"] = img_reduced - rps = measure.regionprops(morphology.label(img_small)) - if rps: - areas = np.asarray([rp.area for rp in rps]) - nobj = len(rps) - area_max = areas.max() - area_mean = areas.mean() - else: - nobj = area_max = area_mean = 0 + # rps = measure.regionprops(morphology.label(img_small)) + # if rps: + # areas = np.asarray([rp.area for rp in rps]) + # nobj = len(rps) + # area_max = areas.max() + # area_mean = areas.mean() + # else: + # nobj = area_max = area_mean = 0 + stats = getMaskReginsStats(img_small) - s.addToPrintList("small_tissue_removed_num_regions", str(nobj)) - s.addToPrintList("small_tissue_removed_mean_area", str(area_mean)) - s.addToPrintList("small_tissue_removed_max_area", str(area_max)) + # s.addToPrintList("small_tissue_removed_num_regions", str(nobj)) + # s.addToPrintList("small_tissue_removed_mean_area", str(area_mean)) + # s.addToPrintList("small_tissue_removed_max_area", str(area_max)) + s.addToPrintList("small_tissue_removed_num_regions", str(stats.get('num',0))) + s.addToPrintList("small_tissue_removed_mean_area", str(stats.get('area_mean',0))) + s.addToPrintList("small_tissue_removed_max_area", str(stats.get('area_max',0))) @@ -87,18 +91,22 @@ def removeFatlikeTissue(s, params): prev_mask = s["img_mask_use"] s["img_mask_use"] = prev_mask & ~mask_fat - rps = measure.regionprops(morphology.label(mask_fat)) - if rps: - areas = np.asarray([rp.area for rp in rps]) - nobj = len(rps) - area_max = areas.max() - area_mean = areas.mean() - else: - nobj = area_max = area_mean = 0 + # rps = measure.regionprops(morphology.label(mask_fat)) + # if rps: + # areas = np.asarray([rp.area for rp in rps]) + # nobj = len(rps) + # area_max = areas.max() + # area_mean = areas.mean() + # else: + # nobj = area_max = area_mean = 0 + stats = getMaskReginsStats(mask_fat) - s.addToPrintList("fatlike_tissue_removed_num_regions", str(nobj)) - s.addToPrintList("fatlike_tissue_removed_mean_area", str(area_mean)) - s.addToPrintList("fatlike_tissue_removed_max_area", str(area_max)) + # s.addToPrintList("fatlike_tissue_removed_num_regions", str(nobj)) + # s.addToPrintList("fatlike_tissue_removed_mean_area", str(area_mean)) + # s.addToPrintList("fatlike_tissue_removed_max_area", str(area_max)) + s.addToPrintList("fatlike_tissue_removed_num_regions", str(stats.get('num',0))) + s.addToPrintList("fatlike_tissue_removed_mean_area", str(stats.get('area_mean',0))) + s.addToPrintList("fatlike_tissue_removed_max_area", str(stats.get('area_max',0))) @@ -125,18 +133,22 @@ def fillSmallHoles(s, params): prev_mask = s["img_mask_use"] s["img_mask_use"] = img_reduced - rps = measure.regionprops(morphology.label(img_small)) - if rps: - areas = np.asarray([rp.area for rp in rps]) - nobj = len(rps) - area_max = areas.max() - area_mean = areas.mean() - else: - nobj = area_max = area_mean = 0 - - s.addToPrintList("small_tissue_filled_num_regions", str(nobj)) - s.addToPrintList("small_tissue_filled_mean_area", str(area_mean)) - s.addToPrintList("small_tissue_filled_max_area", str(area_max)) + # rps = measure.regionprops(morphology.label(img_small)) + # if rps: + # areas = np.asarray([rp.area for rp in rps]) + # nobj = len(rps) + # area_max = areas.max() + # area_mean = areas.mean() + # else: + # nobj = area_max = area_mean = 0 + stats = getMaskReginsStats(img_small) + + # s.addToPrintList("small_tissue_filled_num_regions", str(nobj)) + # s.addToPrintList("small_tissue_filled_mean_area", str(area_mean)) + # s.addToPrintList("small_tissue_filled_max_area", str(area_max)) + s.addToPrintList("small_tissue_filled_num_regions", str(stats.get('num', 0))) + s.addToPrintList("small_tissue_filled_mean_area", str(stats.get('area_mean', 0))) + s.addToPrintList("small_tissue_filled_max_area", str(stats.get('area_max', 0))) s.addToPrintList("small_tissue_filled_percent", printMaskHelper(params.get("mask_statistics", s["mask_statistics"]), prev_mask, s["img_mask_use"])) diff --git a/histoqc/config/config_v2.1.ini b/histoqc/config/config_v2.1.ini index 0aad2dd..98417e4 100644 --- a/histoqc/config/config_v2.1.ini +++ b/histoqc/config/config_v2.1.ini @@ -27,8 +27,9 @@ steps= BasicModule.getBasicStats SaveModule.saveFinalMask SaveModule.saveMacro SaveModule.saveThumbnails + BasicModule.countTissuePieces BasicModule.finalComputations - + [BaseImage.BaseImage] image_work_size = 1.25x @@ -251,3 +252,6 @@ mask_name: img_mask_use disk_size: 5 upper_threshold: 210 invert: True + +[BasicModule.countTissuePieces] +area_threshold: 500 From 222c372e66a2bdc56459e1daaa4a2b740507196e Mon Sep 17 00:00:00 2001 From: nanli-emory Date: Thu, 16 May 2024 10:52:20 -0400 Subject: [PATCH 2/2] remove threshold in countTissuePieces, fix the typo and add stats into the print list --- histoqc/BaseImage.py | 6 +++--- histoqc/BasicModule.py | 7 +++---- histoqc/BlurDetectionModule.py | 12 ++++++------ histoqc/MorphologyModule.py | 8 ++++---- histoqc/config/config_v2.1.ini | 1 - 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/histoqc/BaseImage.py b/histoqc/BaseImage.py index abf3d80..93a51f4 100644 --- a/histoqc/BaseImage.py +++ b/histoqc/BaseImage.py @@ -207,7 +207,7 @@ def getImgThumb(self, size: str): return self[key] ''' -the followings are helper functions +the following are helper functions ''' def getBestThumb(s: BaseImage, x: int, y: int, dims: Tuple[int, int], target_sampling_factor: float): osh = s["os_handle"] @@ -344,11 +344,11 @@ def getDimensionsByOneDim(s: BaseImage, dim: int) -> Tuple[int, int]: return w, dim -def getMaskReginsStats(img, area_threshold: int = 0)-> dict: +def getMaskRegionsStats(img)-> dict: rps = measure.regionprops(morphology.label(img)) if rps: - areas = np.asarray([rp.area for rp in rps if rp.area > area_threshold]) + areas = np.asarray([rp.area for rp in rps]) num = len(rps) area_min = areas.min() area_max = areas.max() diff --git a/histoqc/BasicModule.py b/histoqc/BasicModule.py index 77b08f0..e426a0f 100644 --- a/histoqc/BasicModule.py +++ b/histoqc/BasicModule.py @@ -1,6 +1,6 @@ import logging import os -from histoqc.BaseImage import printMaskHelper, getMaskReginsStats +from histoqc.BaseImage import printMaskHelper, getMaskRegionsStats from skimage.morphology import remove_small_objects, binary_opening, disk from skimage import io, color, img_as_ubyte @@ -71,8 +71,7 @@ def finalProcessingArea(s, params): f"After BasicModule.finalProcessingArea NO tissue remains detectable! Downstream modules likely to be incorrect/fail") -def countTissuePieces(s, params): - area_thresh = int(params.get("area_threshold", "1000")) +def countTissuePieces(s): mask = s["img_mask_use"] - stats = getMaskReginsStats(mask, area_threshold=area_thresh) + stats = getMaskRegionsStats(mask) s.addToPrintList("#pieces_of_tissue", str(stats.get('num', 0))) \ No newline at end of file diff --git a/histoqc/BlurDetectionModule.py b/histoqc/BlurDetectionModule.py index 12d5d8d..afea9f6 100644 --- a/histoqc/BlurDetectionModule.py +++ b/histoqc/BlurDetectionModule.py @@ -2,7 +2,7 @@ import os import skimage -from histoqc.BaseImage import printMaskHelper, getMaskReginsStats +from histoqc.BaseImage import printMaskHelper, getMaskRegionsStats from skimage import io, img_as_ubyte, morphology, measure from skimage.color import rgb2gray from skimage.filters import rank @@ -45,12 +45,12 @@ def identifyBlurryRegions(s, params): # area_mean = areas.mean() # else: # nobj = area_max = area_mean = 0 - stat_rs = getMaskReginsStats(mask) + stat_rs = getMaskRegionsStats(mask) - - # s.addToPrintList("blurry_removed_num_regions", str(nobj)) - # s.addToPrintList("blurry_removed_mean_area", str(area_mean)) - # s.addToPrintList("blurry_removed_max_area", str(area_max)) + + s.addToPrintList("blurry_removed_num_regions", str(stat_rs.get('num', 0))) + s.addToPrintList("blurry_removed_mean_area", str(stat_rs.get('area_mean',0))) + s.addToPrintList("blurry_removed_max_area", str(stat_rs.get('area_max',0))) s.addToPrintList("blurry_removed_percent", diff --git a/histoqc/MorphologyModule.py b/histoqc/MorphologyModule.py index 5f715c2..9bfac7d 100644 --- a/histoqc/MorphologyModule.py +++ b/histoqc/MorphologyModule.py @@ -1,7 +1,7 @@ import logging import os import numpy as np -from histoqc.BaseImage import printMaskHelper, getMaskReginsStats +from histoqc.BaseImage import printMaskHelper, getMaskRegionsStats from skimage import io, morphology, img_as_ubyte, measure from scipy import ndimage as ndi @@ -31,7 +31,7 @@ def removeSmallObjects(s, params): # area_mean = areas.mean() # else: # nobj = area_max = area_mean = 0 - stats = getMaskReginsStats(img_small) + stats = getMaskRegionsStats(img_small) # s.addToPrintList("small_tissue_removed_num_regions", str(nobj)) @@ -99,7 +99,7 @@ def removeFatlikeTissue(s, params): # area_mean = areas.mean() # else: # nobj = area_max = area_mean = 0 - stats = getMaskReginsStats(mask_fat) + stats = getMaskRegionsStats(mask_fat) # s.addToPrintList("fatlike_tissue_removed_num_regions", str(nobj)) # s.addToPrintList("fatlike_tissue_removed_mean_area", str(area_mean)) @@ -141,7 +141,7 @@ def fillSmallHoles(s, params): # area_mean = areas.mean() # else: # nobj = area_max = area_mean = 0 - stats = getMaskReginsStats(img_small) + stats = getMaskRegionsStats(img_small) # s.addToPrintList("small_tissue_filled_num_regions", str(nobj)) # s.addToPrintList("small_tissue_filled_mean_area", str(area_mean)) diff --git a/histoqc/config/config_v2.1.ini b/histoqc/config/config_v2.1.ini index f8d01e8..5e07245 100644 --- a/histoqc/config/config_v2.1.ini +++ b/histoqc/config/config_v2.1.ini @@ -255,4 +255,3 @@ upper_threshold: 210 invert: True [BasicModule.countTissuePieces] -area_threshold: 500