Skip to content

Commit

Permalink
appease the formatter
Browse files Browse the repository at this point in the history
  • Loading branch information
vreuter committed Mar 15, 2024
1 parent 536d0ad commit 352e176
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 82 deletions.
6 changes: 4 additions & 2 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,13 @@ def install_groups(
# See: https://python-poetry.org/blog/announcing-poetry-1.7.0/
argv = [
"poetry",
"config",
"config",
"warnings.export",
"false",
]
session.debug(f"Running command to silence warning since in Nix shell we install the patch: {' '.join(argv)}")
session.debug(
f"Running command to silence warning since in Nix shell we install the patch: {' '.join(argv)}"
)
session.run_always(*argv, external=True)
session.log(f"Will generate requirements hashfile: {hashfile}")
requirements_txt.parent.mkdir(parents=True, exist_ok=True)
Expand Down
111 changes: 73 additions & 38 deletions spotfishing/deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@
from skimage.morphology import ball, remove_small_objects, white_tophat
from skimage.segmentation import expand_labels


Numeric = Union[int, float]

CENTROID_COLUMNS_REMAPPING = {'centroid_weighted-0': 'zc', 'centroid_weighted-1': 'yc', 'centroid_weighted-2': 'xc'}
CENTROID_COLUMNS_REMAPPING = {
"centroid_weighted-0": "zc",
"centroid_weighted-1": "yc",
"centroid_weighted-2": "xc",
}


def detect_spots_dog_old(input_img, spot_threshold: Numeric, expand_px: int = 10):
Expand All @@ -24,16 +27,16 @@ def detect_spots_dog_old(input_img, spot_threshold: Numeric, expand_px: int = 10
spot_threshold : NumberLike
Threshold to use for spots
expand_px : int
Number of pixels by which to expand contiguous subregion,
Number of pixels by which to expand contiguous subregion,
up to point of overlap with neighboring subregion of image
Returns
-------
pd.DataFrame, np.ndarray, np.ndarray:
The centroids and roi_IDs of the spots found,
the image used for spot detection, and
numpy array with only sufficiently large regions
retained (bigger than threshold number of pixels),
pd.DataFrame, np.ndarray, np.ndarray:
The centroids and roi_IDs of the spots found,
the image used for spot detection, and
numpy array with only sufficiently large regions
retained (bigger than threshold number of pixels),
and dilated by expansion amount (possibly)
"""
# TODO: Do not use hard-coded sigma values (second parameter to gaussian(...)).
Expand All @@ -45,13 +48,19 @@ def detect_spots_dog_old(input_img, spot_threshold: Numeric, expand_px: int = 10
img = (img - np.mean(img)) / np.std(img)
labels, _ = ndi.label(img > spot_threshold)
labels = expand_labels(labels, expand_px)

# Make a DataFrame with the ROI info.
spot_props = _reindex_to_roi_id(pd.DataFrame(regionprops_table(
label_image=labels,
intensity_image=input_img,
properties=('label', 'centroid_weighted', 'intensity_mean')
)).drop(['label'], axis=1).rename(columns=CENTROID_COLUMNS_REMAPPING))
spot_props = _reindex_to_roi_id(
pd.DataFrame(
regionprops_table(
label_image=labels,
intensity_image=input_img,
properties=("label", "centroid_weighted", "intensity_mean"),
)
)
.drop(["label"], axis=1)
.rename(columns=CENTROID_COLUMNS_REMAPPING)
)

return spot_props, img, labels

Expand All @@ -66,16 +75,16 @@ def detect_spots_int_old(input_img, spot_threshold: Numeric, expand_px: int = 1)
spot_threshold : NumberLike
Threshold to use for spots
expand_px : int
Number of pixels by which to expand contiguous subregion,
Number of pixels by which to expand contiguous subregion,
up to point of overlap with neighboring subregion of image
Returns
-------
pd.DataFrame, np.ndarray, np.ndarray:
The centroids and roi_IDs of the spots found,
the image used for spot detection, and
numpy array with only sufficiently large regions
retained (bigger than threshold number of pixels),
pd.DataFrame, np.ndarray, np.ndarray:
The centroids and roi_IDs of the spots found,
the image used for spot detection, and
numpy array with only sufficiently large regions
retained (bigger than threshold number of pixels),
and dilated by expansion amount (possibly)
"""
# TODO: enforce that output column names don't vary with code path walked.
Expand All @@ -85,33 +94,59 @@ def detect_spots_int_old(input_img, spot_threshold: Numeric, expand_px: int = 1)
binary = ndi.binary_fill_holes(binary)
struct = ndi.generate_binary_structure(input_img.ndim, 2)
labels, n_obj = ndi.label(binary, structure=struct)
if n_obj > 1: # Do not need this with area filtering below
if n_obj > 1: # Do not need this with area filtering below
labels = remove_small_objects(labels, min_size=5)
if expand_px > 0:
labels = expand_labels(labels, expand_px)
if np.all(labels == 0): # No substructures (ROIs) exist after filtering.
spot_props = pd.DataFrame(columns=['label', 'z_min', 'y_min', 'x_min', 'z_max', 'y_max', 'x_max', 'area', 'zc', 'yc', 'xc', 'intensity_mean'])
if np.all(labels == 0): # No substructures (ROIs) exist after filtering.
spot_props = pd.DataFrame(
columns=[
"label",
"z_min",
"y_min",
"x_min",
"z_max",
"y_max",
"x_max",
"area",
"zc",
"yc",
"xc",
"intensity_mean",
]
)
else:
spot_props = _reindex_to_roi_id(
pd.DataFrame(regionprops_table(
labels, input_img, properties=('label', 'bbox', 'area', 'centroid_weighted', 'intensity_mean')
)).rename(
columns={**CENTROID_COLUMNS_REMAPPING,
'bbox-0': 'z_min',
'bbox-1': 'y_min',
'bbox-2': 'x_min',
'bbox-3': 'z_max',
'bbox-4': 'y_max',
'bbox-5': 'x_max'
}
)
pd.DataFrame(
regionprops_table(
labels,
input_img,
properties=(
"label",
"bbox",
"area",
"centroid_weighted",
"intensity_mean",
),
)
).rename(
columns={
**CENTROID_COLUMNS_REMAPPING,
"bbox-0": "z_min",
"bbox-1": "y_min",
"bbox-2": "x_min",
"bbox-3": "z_max",
"bbox-4": "y_max",
"bbox-5": "x_max",
}
)
)

return spot_props, input_img, labels


def _index_as_roi_id(props_table: pd.DataFrame) -> pd.DataFrame:
return props_table.rename(columns={'index': 'roi_id'})
return props_table.rename(columns={"index": "roi_id"})


def _reindex_to_roi_id(props_table: pd.DataFrame) -> pd.DataFrame:
Expand Down
85 changes: 56 additions & 29 deletions spotfishing/detectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
from skimage.measure import regionprops_table
from skimage.morphology import ball, remove_small_objects, white_tophat
from skimage.segmentation import expand_labels
from .exceptions import DimensionalityError

from .exceptions import DimensionalityError

__author__ = "Vince Reuter"
__credits__ = ["Vince Reuter", "Kai Sandoval Beckwith"]
Expand All @@ -19,10 +19,16 @@

Numeric = Union[int, float]

CENTROID_COLUMNS_REMAPPING = {'centroid_weighted-0': 'zc', 'centroid_weighted-1': 'yc', 'centroid_weighted-2': 'xc'}
CENTROID_COLUMNS_REMAPPING = {
"centroid_weighted-0": "zc",
"centroid_weighted-1": "yc",
"centroid_weighted-2": "xc",
}


def detect_spots_dog(*, input_image, spot_threshold: Numeric, expand_px: Optional[Numeric]):
def detect_spots_dog(
*, input_image, spot_threshold: Numeric, expand_px: Optional[Numeric]
):
"""Spot detection by difference of Gaussians filter
Arguments
Expand All @@ -32,26 +38,30 @@ def detect_spots_dog(*, input_image, spot_threshold: Numeric, expand_px: Optiona
spot_threshold : int or float
Minimum peak value after the DoG transformation to call a peak/spot
expand_px : float or int or NoneType
Number of pixels by which to expand contiguous subregion,
Number of pixels by which to expand contiguous subregion,
up to point of overlap with neighboring subregion of image
Returns
-------
pd.DataFrame, np.ndarray, np.ndarray:
The centroids and roi_IDs of the spots found,
the image used for spot detection, and
numpy array with only sufficiently large regions
retained (bigger than threshold number of pixels),
pd.DataFrame, np.ndarray, np.ndarray:
The centroids and roi_IDs of the spots found,
the image used for spot detection, and
numpy array with only sufficiently large regions
retained (bigger than threshold number of pixels),
and dilated by expansion amount (possibly)
"""
_check_input_image(input_image)
img = _preprocess_for_difference_of_gaussians(input_image)
labels, _ = ndi.label(img > spot_threshold)
spot_props, labels = _build_props_table(labels=labels, input_image=input_image, expand_px=expand_px)
spot_props, labels = _build_props_table(
labels=labels, input_image=input_image, expand_px=expand_px
)
return spot_props, img, labels


def detect_spots_int(*, input_image, spot_threshold: Numeric, expand_px: Optional[Numeric]):
def detect_spots_int(
*, input_image, spot_threshold: Numeric, expand_px: Optional[Numeric]
):
"""Spot detection by intensity filter
Arguments
Expand All @@ -61,16 +71,16 @@ def detect_spots_int(*, input_image, spot_threshold: Numeric, expand_px: Optiona
spot_threshold : NumberLike
Minimum intensity value in a pixel to consider it as part of a spot region
expand_px : float or int or NoneType
Number of pixels by which to expand contiguous subregion,
Number of pixels by which to expand contiguous subregion,
up to point of overlap with neighboring subregion of image
Returns
-------
pd.DataFrame, np.ndarray, np.ndarray:
The centroids and roi_IDs of the spots found,
the image used for spot detection, and
numpy array with only sufficiently large regions
retained (bigger than threshold number of pixels),
pd.DataFrame, np.ndarray, np.ndarray:
The centroids and roi_IDs of the spots found,
the image used for spot detection, and
numpy array with only sufficiently large regions
retained (bigger than threshold number of pixels),
and dilated by expansion amount (possibly)
"""
# TODO: enforce that output column names don't vary with code path walked.
Expand All @@ -82,34 +92,51 @@ def detect_spots_int(*, input_image, spot_threshold: Numeric, expand_px: Optiona
labels, num_obj = ndi.label(binary, structure=struct)
if num_obj > 1:
labels = remove_small_objects(labels, min_size=5)
spot_props, labels = _build_props_table(labels=labels, input_image=input_image, expand_px=expand_px)
spot_props, labels = _build_props_table(
labels=labels, input_image=input_image, expand_px=expand_px
)
return spot_props, input_image, labels


def _build_props_table(*, labels: np.ndarray, input_image: np.ndarray, expand_px: Optional[int]) -> Tuple[pd.DataFrame, np.ndarray]:
def _build_props_table(
*, labels: np.ndarray, input_image: np.ndarray, expand_px: Optional[int]
) -> Tuple[pd.DataFrame, np.ndarray]:
if expand_px:
labels = expand_labels(labels, expand_px)
if np.all(labels == 0):
# No substructures (ROIs) exist.
spot_props = pd.DataFrame(columns=['label', 'centroid_weighted-0', 'centroid_weighted-1', 'centroid_weighted-2', 'area', 'intensity_mean'])
spot_props = pd.DataFrame(
columns=[
"label",
"centroid_weighted-0",
"centroid_weighted-1",
"centroid_weighted-2",
"area",
"intensity_mean",
]
)
else:
spot_props = regionprops_table(
label_image=labels,
intensity_image=input_image,
properties=('label', 'centroid_weighted', 'area', 'intensity_mean'),
)
label_image=labels,
intensity_image=input_image,
properties=("label", "centroid_weighted", "area", "intensity_mean"),
)
spot_props = pd.DataFrame(spot_props)
spot_props = spot_props.drop(['label'], axis=1)
spot_props = spot_props.drop(["label"], axis=1)
spot_props = spot_props.rename(columns=CENTROID_COLUMNS_REMAPPING)
spot_props = spot_props.reset_index(drop=True)
return spot_props, labels


def _check_input_image(img: np.ndarray) -> NoReturn:
if not isinstance(img, np.ndarray):
raise TypeError(f"Expected numpy array for input image but got {type(img).__name__}")
raise TypeError(
f"Expected numpy array for input image but got {type(img).__name__}"
)
if img.ndim != 3:
raise DimensionalityError(f"Expected 3D input image but got {img.ndim}-dimensional")
raise DimensionalityError(
f"Expected 3D input image but got {img.ndim}-dimensional"
)


def _preprocess_for_difference_of_gaussians(input_image: np.ndarray) -> np.ndarray:
Expand Down
33 changes: 24 additions & 9 deletions tests/test_old_new_equivalence.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import os
import numpy as np
from pathlib import Path

import numpy as np
import pytest

from spotfishing.deprecated import detect_spots_int_old, detect_spots_dog_old
from spotfishing import detect_spots_int, detect_spots_dog
from spotfishing import detect_spots_dog, detect_spots_int
from spotfishing.deprecated import detect_spots_dog_old, detect_spots_int_old

OLD_PIXEL_EXPANSION_INT = 1
OLD_PIXEL_EXPANSION_DOG = 10
Expand All @@ -14,18 +15,32 @@ def get_img_data_file(fn: str) -> Path:
return Path(os.path.dirname(__file__)) / "data" / fn


@pytest.mark.parametrize("data_path", [get_img_data_file(fn) for fn in ("img__p0_t57_c0__smaller.npy", "img__p13_t57_c0__smaller.npy")])
@pytest.mark.parametrize(
["old_fun", "new_fun", "threshold", "expand_px"],
[(detect_spots_int_old, detect_spots_int, threshold, OLD_PIXEL_EXPANSION_INT) for threshold in (500, 300, 100)] +
[(detect_spots_dog_old, detect_spots_dog, threshold , OLD_PIXEL_EXPANSION_DOG) for threshold in (20, 15, 10)]
"data_path",
[
get_img_data_file(fn)
for fn in ("img__p0_t57_c0__smaller.npy", "img__p13_t57_c0__smaller.npy")
],
)
@pytest.mark.parametrize(
["old_fun", "new_fun", "threshold", "expand_px"],
[
(detect_spots_int_old, detect_spots_int, threshold, OLD_PIXEL_EXPANSION_INT)
for threshold in (500, 300, 100)
]
+ [
(detect_spots_dog_old, detect_spots_dog, threshold, OLD_PIXEL_EXPANSION_DOG)
for threshold in (20, 15, 10)
],
)
def test_eqv(data_path, old_fun, new_fun, threshold, expand_px):
data = np.load(data_path)
old_table, _, _ = old_fun(data, threshold)
new_table, _, _ = new_fun(input_image=data, spot_threshold=threshold, expand_px=expand_px)
new_table, _, _ = new_fun(
input_image=data, spot_threshold=threshold, expand_px=expand_px
)
assert np.all(old_table.index == new_table.index)
cols = ["zc", "yc", "xc", "area", "intensity_mean"]
sub_cols = [c for c in cols if c != "area"] # wasn't in DoG before
sub_cols = [c for c in cols if c != "area"] # wasn't in DoG before
assert list(new_table.columns) == cols
assert np.all(old_table[sub_cols].to_numpy() == new_table[sub_cols].to_numpy())
Loading

0 comments on commit 352e176

Please sign in to comment.