From 27eda3f2f24f1182c4a1ee5aeebff60a88640e4d Mon Sep 17 00:00:00 2001 From: jules-vanaret Date: Thu, 9 Oct 2025 23:25:04 +0200 Subject: [PATCH 1/3] implemented intensity normalization with value aggreagation at centroids --- .../preprocessing/_intensity_normalization.py | 51 +++++++++++++++++-- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/src/tapenade/preprocessing/_intensity_normalization.py b/src/tapenade/preprocessing/_intensity_normalization.py index 801c8bf..bb64a44 100644 --- a/src/tapenade/preprocessing/_intensity_normalization.py +++ b/src/tapenade/preprocessing/_intensity_normalization.py @@ -1,5 +1,6 @@ import numpy as np from scipy.optimize import minimize_scalar +from skimage.measure import regionprops from tapenade.preprocessing._smoothing import _masked_smooth_gaussian @@ -13,14 +14,19 @@ def _nans_outside_mask(array: np.ndarray, mask: np.ndarray): def _optimize_sigma( - ref_array: np.ndarray, mask: np.ndarray, labels_mask: np.ndarray + ref_array: np.ndarray, mask: np.ndarray, labels_mask: np.ndarray, aggregate_labels: bool, aggregation_func ): def opt_func( sigma, ref_array: np.ndarray, mask: np.ndarray, labels_mask: np.ndarray ): - ref_array_smooth = _masked_smooth_gaussian( - ref_array, sigmas=sigma, mask_for_volume=labels_mask, mask=mask + ref_array_smooth = _compute_smoothed_ref_array( + ref_array, + sigma, + mask, + labels_mask, + aggregate_labels, + aggregation_func, ) if labels_mask is not None: @@ -49,6 +55,29 @@ def _find_reference_plane_from_medians(array: np.ndarray): ref_ind = np.nanargmax(np.nanmedian(array, axis=(1, 2))) return ref_ind +def _compute_smoothed_ref_array( + ref_array: np.ndarray, + sigma: float, + mask: np.ndarray, + labels: np.ndarray, + aggregate_labels: bool, + aggregation_func, +): + if aggregate_labels and labels is not None: + ref_array_smooth = np.empty_like(ref_array) + props = regionprops(labels.astype(int), intensity_image=ref_array) + centroids = [np.round(prop.centroid).astype(int) for prop in props] + ref_array_smooth[tuple(zip(*centroids))] = [ + aggregation_func(prop.intensity_image[prop.image > 0]) for prop in props + ] + ref_array_smooth = _masked_smooth_gaussian( + ref_array_smooth, sigmas=sigma, mask_for_volume=ref_array_smooth.astype(bool), mask=mask + ) + else: + ref_array_smooth = _masked_smooth_gaussian( + ref_array, sigmas=sigma, mask_for_volume=labels, mask=mask + ) + return ref_array_smooth def _normalize_intensity( array: np.ndarray, @@ -58,6 +87,8 @@ def _normalize_intensity( labels: np.ndarray = None, image_wavelength: float = None, width=3, + aggregate_labels: bool = False, + aggregation_func = np.nanmedian, ): """ Normalize the intensity of an array based on a reference array assumed to have @@ -73,6 +104,10 @@ def _normalize_intensity( signal is expressed, e.g nuclei. Default is None. - image_wavelength (float, optional): The wavelength of the image. Default is None. - width (int, optional): The number of neighboring planes to consider for reference plane calculation. Default is 5. + - aggregate_labels (bool, optional): If labels are provided and this is True, the intensity values within each label + will be aggregated at centroids using the specified aggregation_func before smoothing. Default is False. + - aggregation_func (callable, optional): The function to use for aggregating intensity values within each label if aggregate_labels is True. + Default is np.nanmean. Returns: - array_norm (ndarray): The normalized input array. @@ -107,8 +142,14 @@ def _normalize_intensity( if sigma is None: sigma = _optimize_sigma(ref_array, mask, labels_mask) print("sigma = ", sigma) - ref_array_smooth = _masked_smooth_gaussian( - ref_array, sigmas=sigma, mask_for_volume=labels_mask, mask=mask + + ref_array_smooth = _compute_smoothed_ref_array( + ref_array, + sigma, + mask, + labels, + aggregate_labels, + aggregation_func, ) if mask is not None: From bd43ff6417ff0951850edcb5be925b4a8a328af6 Mon Sep 17 00:00:00 2001 From: jules-vanaret Date: Fri, 10 Oct 2025 00:05:02 +0200 Subject: [PATCH 2/3] modified python version --- setup.cfg | 2 +- tox.ini | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index 0261406..125f60f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -42,7 +42,7 @@ install_requires = 3d-registration >= 0.5.2 tqdm -python_requires = >=3.8 +python_requires = >=3.10 include_package_data = True package_dir = =src diff --git a/tox.ini b/tox.ini index 08aedaf..8f89a2c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,10 @@ # For more information about tox, see https://tox.readthedocs.io/en/latest/ [tox] -envlist = py{38,39,310,311}-{linux,macos,windows} +envlist = py{39,310,311}-{linux,macos,windows} isolated_build=true [gh-actions] python = - 3.8: py38 3.9: py39 3.10: py310 3.11: py311 From 216b24e89b93a86bfa9f5895a62f6e510ee8b275 Mon Sep 17 00:00:00 2001 From: jules-vanaret Date: Fri, 10 Oct 2025 00:06:46 +0200 Subject: [PATCH 3/3] updated version --- .github/workflows/test_and_deploy.yml | 2 +- tox.ini | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml index d82f314..e6427bc 100644 --- a/.github/workflows/test_and_deploy.yml +++ b/.github/workflows/test_and_deploy.yml @@ -21,7 +21,7 @@ jobs: strategy: matrix: platform: [ubuntu-latest, windows-latest, macos-latest] - python-version: ['3.8', '3.9', '3.10', '3.11'] + python-version: ['3.10', '3.11'] steps: - uses: actions/checkout@v2 diff --git a/tox.ini b/tox.ini index 8f89a2c..0c57041 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,10 @@ # For more information about tox, see https://tox.readthedocs.io/en/latest/ [tox] -envlist = py{39,310,311}-{linux,macos,windows} +envlist = py{310,311}-{linux,macos,windows} isolated_build=true [gh-actions] python = - 3.9: py39 3.10: py310 3.11: py311