From 0105d4bfb9878f76530d72c963aa63ccd2daf8d4 Mon Sep 17 00:00:00 2001 From: Jeremy Muhlich Date: Sat, 25 Oct 2025 18:18:09 -0400 Subject: [PATCH 1/4] Fix crash in puncta detection on large images skimage 0.16.2 has a bug where images above a certain size cause an out-of-bounds array read in the cython code, leading to a segfault and hard crash. This upgrades us to skimage 0.20.0 which has a fix. Workarounds are required for other API changes and new bugs. --- Dockerfile | 2 +- S3segmenter.py | 38 ++++++++++++++++++++++++------- __pycache__/rowit.cpython-36.pyc | Bin 2784 -> 0 bytes __pycache__/rowit.cpython-38.pyc | Bin 2871 -> 0 bytes 4 files changed, 31 insertions(+), 9 deletions(-) delete mode 100644 __pycache__/rowit.cpython-36.pyc delete mode 100644 __pycache__/rowit.cpython-38.pyc diff --git a/Dockerfile b/Dockerfile index ee7a03e..9dfb72c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM python:3.8.0 -RUN pip install numpy==1.22.3 scikit-learn scikit-image==0.16.2 matplotlib tifffile==2021.6.6 opencv-python==4.3.0.36 ome_types==0.4.5 imagecodecs +RUN pip install numpy==1.22.3 scikit-learn scikit-image==0.20.0 matplotlib tifffile==2021.6.6 opencv-python==4.3.0.36 ome_types==0.5.3 imagecodecs COPY S3segmenter.py ./app/S3segmenter.py COPY save_tifffile_pyramid.py ./app/save_tifffile_pyramid.py diff --git a/S3segmenter.py b/S3segmenter.py index 351eb3a..994f476 100644 --- a/S3segmenter.py +++ b/S3segmenter.py @@ -8,6 +8,7 @@ from skimage.morphology import * from skimage.morphology import extrema from skimage import morphology +import skimage.measure._regionprops from skimage.measure import regionprops from skimage.transform import resize from skimage.filters import gaussian, threshold_otsu, threshold_local @@ -58,6 +59,28 @@ def normI(I): J = J/(p99-p1); return J +def peak_local_max_mask(image, **kwargs): + idx = peak_local_max(image, **kwargs) + mask = np.zeros_like(image, dtype=bool) + mask[tuple(idx.T)] = True + return mask + +# Workaround for skimage bug in unpickling regionprops. +# https://github.com/scikit-image/scikit-image/issues/6465 +class SerializableRegionProperties(skimage.measure._regionprops.RegionProperties): + + def __init__(self, slice=None, label=None, label_image=None, intensity_image=None, cache_active=None): + super().__init__(slice, label, label_image, intensity_image, cache_active) + self._initialized = True + + def __getattr__(self, attr): + if attr == "_initialized" or not self._initialized: + self.__init__() + super().__getattr__(attr) + +def wrap_props(p): + return SerializableRegionProperties(p.slice, p.label, p._label_image, p._intensity_image, p._cache_active) + def contour_pm_watershed( contour_pm, sigma=2, h=0, tissue_mask=None, padding_mask=None, min_area=None, max_area=None @@ -73,12 +96,11 @@ def contour_pm_watershed( tissue_mask, padding_mask ) - maxima = peak_local_max( + maxima = peak_local_max_mask( extrema.h_maxima( ndi.gaussian_filter(np.invert(contour_pm), sigma=sigma), h=h ), - indices=False, footprint=np.ones((3, 3)) ) maxima = label(maxima).astype(np.int32) @@ -152,7 +174,7 @@ def S3NucleiBypass(nucleiPM,nucleiImage,logSigma,TMAmask,nucleiFilter,nucleiRegi def props_of_keys(prop, keys): return [prop[k] for k in keys] - mean_ints, labels, areas = np.array(Parallel(n_jobs=1)(delayed(props_of_keys)(prop, prop_keys) + mean_ints, labels, areas = np.array(Parallel(n_jobs=1)(delayed(props_of_keys)(wrap_props(prop), prop_keys) for prop in P ) ).T @@ -171,7 +193,7 @@ def S3NucleiSegmentationWatershed(nucleiPM,nucleiImage,logSigma,TMAmask,nucleiFi nucleiCenters = nucleiPM[:,:,0] mask = resize(TMAmask,(nucleiImage.shape[0],nucleiImage.shape[1]),order = 0)>0 if nucleiRegion == 'localThreshold' or nucleiRegion == 'localMax': - Imax = peak_local_max(extrema.h_maxima(255-nucleiContours,logSigma[0]),indices=False) + Imax = peak_local_max_mask(extrema.h_maxima(255-nucleiContours,logSigma[0])) Imax = label(Imax).astype(np.int32) foregroundMask = watershed(nucleiContours, Imax, watershed_line=True) P = regionprops(foregroundMask, np.amax(nucleiCenters) - nucleiCenters - nucleiContours) @@ -179,7 +201,7 @@ def S3NucleiSegmentationWatershed(nucleiPM,nucleiImage,logSigma,TMAmask,nucleiFi def props_of_keys(prop, keys): return [prop[k] for k in keys] - mean_ints, labels, areas = np.array(Parallel(n_jobs=6)(delayed(props_of_keys)(prop, prop_keys) + mean_ints, labels, areas = np.array(Parallel(n_jobs=6)(delayed(props_of_keys)(wrap_props(prop), prop_keys) for prop in P ) ).T @@ -236,7 +258,7 @@ def props_of_keys(prop, keys): prop_keys = ['mean_intensity', 'area', 'solidity', 'label'] mean_ints, areas, solidities, labels = np.array( - Parallel(n_jobs=6)(delayed(props_of_keys)(prop, prop_keys) + Parallel(n_jobs=6)(delayed(props_of_keys)(wrap_props(prop), prop_keys) for prop in P ) ).T @@ -349,7 +371,7 @@ def exportMasks(mask,image,outputPath,filePrefix,fileName,commit,metadata_args,s def S3punctaDetection(spotChan,mask,sigma,SD): Ilog = -gaussian_laplace(np.float32(spotChan),sigma) tissueMask = spotChan >0 - fgm=peak_local_max(Ilog, indices=False,footprint=np.ones((3, 3)))*tissueMask + fgm=peak_local_max_mask(Ilog, footprint=np.ones((3, 3)))*tissueMask test=Ilog[fgm==1] med = np.median(test) mad = np.median(np.absolute(test - med)) @@ -606,4 +628,4 @@ def S3punctaDetection(spotChan,mask,sigma,SD): channel_names= spot_channel_names, is_mask=False, **metadata - ) \ No newline at end of file + ) diff --git a/__pycache__/rowit.cpython-36.pyc b/__pycache__/rowit.cpython-36.pyc deleted file mode 100644 index 5a2c4f345e9b4cfe46c6014cdc15e30a215d5fe5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2784 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 From eb01afa00c3a27cbeba09bc379f82870f4c31b91 Mon Sep 17 00:00:00 2001 From: Jeremy Muhlich Date: Sat, 25 Oct 2025 19:10:56 -0400 Subject: [PATCH 2/4] Update CI upload-artifact version to v4 --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b1d09ad..8f87339 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,10 +48,10 @@ jobs: # If the action is successful, the output will be available as a downloadable artifact - name: Upload processed result - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: ex001-s3seg path: | ~/data/exemplar-001-cycle6/** ~/data/large/** - \ No newline at end of file + From 85be608ff3f653ec8d7ea9b9ea8a8fe046931b74 Mon Sep 17 00:00:00 2001 From: Jeremy Muhlich Date: Sat, 25 Oct 2025 19:38:53 -0400 Subject: [PATCH 3/4] Mass update versions of everything to address issues It turns out the code is almost completely compatible with the latest version of all the dependencies. It ended up being a lot simpler to upgrade everything than chase all the issues with the older versions one by one. --- Dockerfile | 8 ++++++-- S3segmenter.py | 28 ++++++---------------------- rowit.py | 4 ++-- 3 files changed, 14 insertions(+), 26 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9dfb72c..76df76a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,10 @@ -FROM python:3.8.0 +FROM python:3.13.9 -RUN pip install numpy==1.22.3 scikit-learn scikit-image==0.20.0 matplotlib tifffile==2021.6.6 opencv-python==4.3.0.36 ome_types==0.5.3 imagecodecs +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y libgl1 \ + && rm -rf /var/lib/apt/lists/* + +RUN pip install --no-cache numpy scikit-learn scikit-image matplotlib tifffile opencv-python ome_types imagecodecs COPY S3segmenter.py ./app/S3segmenter.py COPY save_tifffile_pyramid.py ./app/save_tifffile_pyramid.py diff --git a/S3segmenter.py b/S3segmenter.py index 994f476..df3bf37 100644 --- a/S3segmenter.py +++ b/S3segmenter.py @@ -16,7 +16,7 @@ from skimage.color import label2rgb from skimage.io import imsave,imread from skimage.segmentation import clear_border, watershed, find_boundaries -from scipy.ndimage.filters import uniform_filter +from scipy.ndimage import uniform_filter from os.path import * from os import listdir, makedirs, remove import pickle @@ -65,22 +65,6 @@ def peak_local_max_mask(image, **kwargs): mask[tuple(idx.T)] = True return mask -# Workaround for skimage bug in unpickling regionprops. -# https://github.com/scikit-image/scikit-image/issues/6465 -class SerializableRegionProperties(skimage.measure._regionprops.RegionProperties): - - def __init__(self, slice=None, label=None, label_image=None, intensity_image=None, cache_active=None): - super().__init__(slice, label, label_image, intensity_image, cache_active) - self._initialized = True - - def __getattr__(self, attr): - if attr == "_initialized" or not self._initialized: - self.__init__() - super().__getattr__(attr) - -def wrap_props(p): - return SerializableRegionProperties(p.slice, p.label, p._label_image, p._intensity_image, p._cache_active) - def contour_pm_watershed( contour_pm, sigma=2, h=0, tissue_mask=None, padding_mask=None, min_area=None, max_area=None @@ -121,10 +105,10 @@ def contour_pm_watershed( np.greater(maxima, 0, out=maxima) if padded is None: - return maxima.astype(np.bool) + return maxima.astype(bool) else: padded[padded == 1] = maxima.flatten() - return padded.astype(np.bool) + return padded.astype(bool) def S3AreaSegmenter(nucleiPM, images, TMAmask, threshold,fileprefix,outputPath): nucleiCenters = nucleiPM[:,:,0] @@ -174,7 +158,7 @@ def S3NucleiBypass(nucleiPM,nucleiImage,logSigma,TMAmask,nucleiFilter,nucleiRegi def props_of_keys(prop, keys): return [prop[k] for k in keys] - mean_ints, labels, areas = np.array(Parallel(n_jobs=1)(delayed(props_of_keys)(wrap_props(prop), prop_keys) + mean_ints, labels, areas = np.array(Parallel(n_jobs=1)(delayed(props_of_keys)(prop, prop_keys) for prop in P ) ).T @@ -201,7 +185,7 @@ def S3NucleiSegmentationWatershed(nucleiPM,nucleiImage,logSigma,TMAmask,nucleiFi def props_of_keys(prop, keys): return [prop[k] for k in keys] - mean_ints, labels, areas = np.array(Parallel(n_jobs=6)(delayed(props_of_keys)(wrap_props(prop), prop_keys) + mean_ints, labels, areas = np.array(Parallel(n_jobs=6)(delayed(props_of_keys)(prop, prop_keys) for prop in P ) ).T @@ -258,7 +242,7 @@ def props_of_keys(prop, keys): prop_keys = ['mean_intensity', 'area', 'solidity', 'label'] mean_ints, areas, solidities, labels = np.array( - Parallel(n_jobs=6)(delayed(props_of_keys)(wrap_props(prop), prop_keys) + Parallel(n_jobs=6)(delayed(props_of_keys)(prop, prop_keys) for prop in P ) ).T diff --git a/rowit.py b/rowit.py index 2e9ab1c..14e0482 100644 --- a/rowit.py +++ b/rowit.py @@ -47,7 +47,7 @@ def reconstruct(self, img_window_view_list): def padded_shape(self): padded_shape = np.array(self.img_shape) + self.overlap_size n = np.ceil((padded_shape - self.block_size) / self.step_size) - padded_shape = (self.block_size + (n * self.step_size)).astype(np.int) + padded_shape = (self.block_size + (n * self.step_size)).astype(int) return tuple(padded_shape) @property @@ -73,4 +73,4 @@ def crop_with_padding_mask(img, padding_mask, return_mask=False): padded = np.zeros_like(img) img = img[r_s:r_e, c_s:c_e] padded[r_s:r_e, c_s:c_e] = 1 - return (img, padded) if return_mask else img \ No newline at end of file + return (img, padded) if return_mask else img From d95a99f7a97cb7b202b388aaf1e3c6f9b80e498f Mon Sep 17 00:00:00 2001 From: Jeremy Muhlich Date: Sun, 26 Oct 2025 00:09:31 -0400 Subject: [PATCH 4/4] Add --verbose and --numWorkers arguments to script Not necessary for the puncta detection fix, but this does make the script more ergonomic. --- S3segmenter.py | 41 +++++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/S3segmenter.py b/S3segmenter.py index df3bf37..3d5acac 100644 --- a/S3segmenter.py +++ b/S3segmenter.py @@ -33,6 +33,7 @@ from save_tifffile_pyramid import save_pyramid import subprocess import ome_types +import threadpoolctl def imshowpair(A,B): @@ -158,7 +159,7 @@ def S3NucleiBypass(nucleiPM,nucleiImage,logSigma,TMAmask,nucleiFilter,nucleiRegi def props_of_keys(prop, keys): return [prop[k] for k in keys] - mean_ints, labels, areas = np.array(Parallel(n_jobs=1)(delayed(props_of_keys)(prop, prop_keys) + mean_ints, labels, areas = np.array(Parallel(n_jobs=1,verbose=joblibVerbose)(delayed(props_of_keys)(prop, prop_keys) for prop in P ) ).T @@ -185,7 +186,7 @@ def S3NucleiSegmentationWatershed(nucleiPM,nucleiImage,logSigma,TMAmask,nucleiFi def props_of_keys(prop, keys): return [prop[k] for k in keys] - mean_ints, labels, areas = np.array(Parallel(n_jobs=6)(delayed(props_of_keys)(prop, prop_keys) + mean_ints, labels, areas = np.array(Parallel(n_jobs=numWorkers,verbose=joblibVerbose)(delayed(props_of_keys)(prop, prop_keys) for prop in P ) ).T @@ -216,9 +217,10 @@ def props_of_keys(prop, keys): maxArea = (logSigma[1]**2)*3/4 minArea = (logSigma[0]**2)*3/4 - + + print(datetime.datetime.now(), '==> Performing watershed') foregroundMask = np.array( - Parallel(n_jobs=6)(delayed(contour_pm_watershed)( + Parallel(n_jobs=numWorkers,verbose=joblibVerbose)(delayed(contour_pm_watershed)( img, sigma=logSigma[1]/30, h=logSigma[1]/30, tissue_mask=tm, padding_mask=m, min_area=minArea, max_area=maxArea ) for img, tm, m in zip(nucleiContours, mask, padding_mask)) @@ -234,7 +236,7 @@ def props_of_keys(prop, keys): elif nucleiFilter == 'Int': int_img = nucleiImage - print(' ', datetime.datetime.now(), 'regionprops') + print(datetime.datetime.now(), '==> Filtering out low-confidence events') P = regionprops(foregroundMask, int_img) def props_of_keys(prop, keys): @@ -242,7 +244,7 @@ def props_of_keys(prop, keys): prop_keys = ['mean_intensity', 'area', 'solidity', 'label'] mean_ints, areas, solidities, labels = np.array( - Parallel(n_jobs=6)(delayed(props_of_keys)(prop, prop_keys) + Parallel(n_jobs=numWorkers,verbose=joblibVerbose)(delayed(props_of_keys)(prop, prop_keys) for prop in P ) ).T @@ -391,15 +393,32 @@ def S3punctaDetection(spotChan,mask,sigma,SD): parser.add_argument("--punctaSD", nargs = '+', type=float, default=[4]) parser.add_argument("--saveMask",action='store_false') parser.add_argument("--saveFig",action='store_false') + parser.add_argument("--numWorkers", type=int, default=0, help="Parallel worker processes, recommend <= 4 (default=autodetect)") + parser.add_argument("--verbose", action="store_true", help="Enable for extra logging output") args = parser.parse_args() - + imagePath = args.imagePath outputPath = args.outputPath nucleiClassProbPath = args.nucleiClassProbPath contoursClassProbPath = args.contoursClassProbPath stackProbPath = args.stackProbPath maskPath = args.maskPath - + joblibVerbose = 10 if args.verbose else 0 + + threadpoolctl.threadpool_limits(1) + if args.numWorkers == 0: + if hasattr(os, 'sched_getaffinity'): + numCpus = len(os.sched_getaffinity(0)) + else: + numCpus = multiprocessing.cpu_count() + numWorkers = min(numCpus, 4) + print( + f"Using {numWorkers} worker processes based on available CPU count ({numCpus})." + ) + print() + else: + numWorkers = args.numWorkers + commit = '1.3.11'#subprocess.check_output(['git', 'describe', '--tags']).decode('ascii').strip() metadata = getMetadata(imagePath,commit) @@ -444,6 +463,8 @@ def S3punctaDetection(spotChan,mask,sigma,SD): TissueMaskChan[:] = [number - 1 for number in TissueMaskChan]#convert 1-based indexing to 0-based indexing TissueMaskChan.append(nucMaskChan) + print(datetime.datetime.now(), '==> Preprocessing images') + #crop images if needed if args.crop == 'interactiveCrop': nucleiCrop = tifffile.imread(imagePath,key = nucMaskChan) @@ -519,6 +540,7 @@ def S3punctaDetection(spotChan,mask,sigma,SD): del nucleiPM # cytoplasm segmentation if args.segmentCytoplasm == 'segmentCytoplasm': + print(datetime.datetime.now(), '==> Segmenting cytoplasm') count =0 if args.crop == 'noCrop' or args.crop == 'dearray' or args.crop == 'plate': cyto=np.empty((len(CytoMaskChan),nucleiCrop.shape[0],nucleiCrop.shape[1]),dtype=np.uint16) @@ -556,6 +578,7 @@ def S3punctaDetection(spotChan,mask,sigma,SD): detectPuncta = args.detectPuncta if (np.min(detectPuncta)>0): + print(datetime.datetime.now(), '==> Detecting puncta') detectPuncta[:] = [number - 1 for number in detectPuncta] #convert 1-based indexing to 0-based indexing if len(detectPuncta) != len(args.punctaSigma): args.punctaSigma = args.punctaSigma[0] * np.ones(len(detectPuncta)) @@ -613,3 +636,5 @@ def S3punctaDetection(spotChan,mask,sigma,SD): is_mask=False, **metadata ) + + print(datetime.datetime.now(), '==> Done!')