From 525faa2f4709fbd58d11fefca9d0f102142ae597 Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell Date: Tue, 26 Nov 2024 10:49:03 +0100 Subject: [PATCH 01/20] enh: type hinting for bright.py --- dclab/features/bright.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dclab/features/bright.py b/dclab/features/bright.py index f13004c6..cca59eab 100644 --- a/dclab/features/bright.py +++ b/dclab/features/bright.py @@ -3,9 +3,13 @@ RT-DC event image mask. """ import numpy as np +import numpy.typing as npt -def get_bright(mask, image, ret_data="avg,sd"): +def get_bright( + mask: npt.NDArray[np.bool] | list[npt.NDArray[np.bool]], + image: npt.NDArray | list[npt.NDArray], + ret_data: str = "avg,sd"): """Compute avg and/or std of the event brightness The event brightness is defined by the gray-scale values of the From fb0ed0bafa99dad54026830cc8043ea91c682566 Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell Date: Tue, 26 Nov 2024 11:28:42 +0100 Subject: [PATCH 02/20] test, docs: ensure get_bright returns expected types --- dclab/features/bright.py | 7 +++---- tests/test_feat_bright.py | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/dclab/features/bright.py b/dclab/features/bright.py index cca59eab..c5342cfb 100644 --- a/dclab/features/bright.py +++ b/dclab/features/bright.py @@ -6,10 +6,9 @@ import numpy.typing as npt -def get_bright( - mask: npt.NDArray[np.bool] | list[npt.NDArray[np.bool]], - image: npt.NDArray | list[npt.NDArray], - ret_data: str = "avg,sd"): +def get_bright(mask: npt.NDArray[bool] | list[npt.NDArray[bool]], + image: npt.NDArray | list[npt.NDArray], + ret_data: str = "avg,sd") -> float | npt.NDArray: """Compute avg and/or std of the event brightness The event brightness is defined by the gray-scale values of the diff --git a/tests/test_feat_bright.py b/tests/test_feat_bright.py index 0f4a7b4a..79808ba2 100644 --- a/tests/test_feat_bright.py +++ b/tests/test_feat_bright.py @@ -77,3 +77,25 @@ def test_simple_bright(): avg, get_bright(mask=mask, image=image, ret_data="avg")) assert np.allclose( std, get_bright(mask=mask, image=image, ret_data="sd")) + + +def test_list_input_bright(): + """Use the list of np.ndarray as input.""" + pytest.importorskip("nptdms") + ds = new_dataset(retrieve_data("fmt-tdms_fl-image-bright_2017.zip")) + # This stripped dataset has only 7 video frames / contours + image, mask, bright_avg, bright_sd = [], [], [], [] + for ii in range(2, 7): + image.append(ds["image"][ii]) + mask.append(ds["mask"][ii]) + bright_avg.append(ds["bright_avg"][ii]) + bright_sd.append(ds["bright_sd"][ii]) + + avg, std = get_bright(mask=mask, image=image, ret_data="avg,sd") + assert np.allclose(avg, bright_avg) + assert np.allclose(std, bright_sd) + # cover single `ret_data` input + assert np.allclose( + avg, get_bright(mask=mask, image=image, ret_data="avg")) + assert np.allclose( + std, get_bright(mask=mask, image=image, ret_data="sd")) From 1c4d9c18bca1dd8756ff4eb06ab4ed562d84acbe Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell Date: Tue, 26 Nov 2024 12:01:55 +0100 Subject: [PATCH 03/20] test, docs: ensure get_bright_perc returns expected types --- dclab/features/bright_perc.py | 9 +++++++-- tests/test_feat_bright_perc.py | 28 ++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/dclab/features/bright_perc.py b/dclab/features/bright_perc.py index 879ef908..bc5a6b1c 100644 --- a/dclab/features/bright_perc.py +++ b/dclab/features/bright_perc.py @@ -3,9 +3,14 @@ RT-DC event image mask with background-correction taken into account. """ import numpy as np +import numpy.typing as npt -def get_bright_perc(mask, image, image_bg, bg_off=None): +def get_bright_perc(mask: npt.NDArray[bool] | list[npt.NDArray[bool]], + image: npt.NDArray | list[npt.NDArray], + image_bg: npt.NDArray | list[npt.NDArray], + bg_off: float | npt.NDArray = None) -> \ + tuple[float, float] | tuple[npt.NDArray, npt.NDArray]: """Compute 10th and 90th percentile of the bg-corrected event brightness The background-corrected event brightness is defined by the @@ -29,7 +34,7 @@ def get_bright_perc(mask, image, image_bg, bg_off=None): ------- bright_perc_10: float or ndarray of size N 10th percentile of brightness - bright_perc_10: float or ndarray of size N + bright_perc_90: float or ndarray of size N 90th percentile of brightness """ if isinstance(mask, np.ndarray) and len(mask.shape) == 2: diff --git a/tests/test_feat_bright_perc.py b/tests/test_feat_bright_perc.py index 970b428e..dd0668bc 100644 --- a/tests/test_feat_bright_perc.py +++ b/tests/test_feat_bright_perc.py @@ -65,11 +65,35 @@ def test_af_brightness_bc_single(): p10 = np.percentile(image_corr[mask], 10) p90 = np.percentile(image_corr[mask], 90) assert np.max(np.abs(image_corr)) < 50, "sanity check" - assert dclab.features.bright_perc.get_bright_perc( + + p10_calc, p90_calc = dclab.features.bright_perc.get_bright_perc( ds["mask"][ii], ds["image"][ii], ds["image_bg"][ii], - ) == (p10, p90) + ) + assert (p10_calc, p90_calc) == (p10, p90) + + +@pytest.mark.filterwarnings( + "ignore::dclab.rtdc_dataset.config.WrongConfigurationTypeWarning") +def test_af_brightness_bc_list(): + path = retrieve_data("fmt-hdf5_image-bg_2020.zip") + ds = dclab.new_dataset(path) + # sanity checks + assert "bright_perc_10" not in ds.features_innate + assert "bright_perc_90" not in ds.features_innate + # ignore first event (no image data) + mask = list(ds["mask"][1:6]) + image = list(ds["image"][1:6]) + image_bg = list(ds["image_bg"][1:6]) + + p10_calc, p90_calc = dclab.features.bright_perc.get_bright_perc( + mask, image, image_bg, + ) + assert isinstance(p10_calc, np.ndarray) + assert isinstance(p90_calc, np.ndarray) + assert p10_calc.shape == (4,) + assert p90_calc.shape == (4,) @pytest.mark.filterwarnings( From 821b4b3bb39dae02bbee8f971f3c28259435c965 Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell Date: Tue, 26 Nov 2024 12:08:47 +0100 Subject: [PATCH 04/20] docs: add futures annotations import for older py versions --- dclab/features/bright.py | 2 ++ dclab/features/bright_bc.py | 9 ++++++++- dclab/features/bright_perc.py | 2 ++ dclab/features/contour.py | 9 ++++++--- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/dclab/features/bright.py b/dclab/features/bright.py index c5342cfb..a5e40931 100644 --- a/dclab/features/bright.py +++ b/dclab/features/bright.py @@ -2,6 +2,8 @@ Computation of mean and standard deviation of grayscale values inside the RT-DC event image mask. """ +from __future__ import annotations + import numpy as np import numpy.typing as npt diff --git a/dclab/features/bright_bc.py b/dclab/features/bright_bc.py index dba8c578..ec99e048 100644 --- a/dclab/features/bright_bc.py +++ b/dclab/features/bright_bc.py @@ -2,10 +2,17 @@ Computation of mean and standard deviation of grayscale values inside the RT-DC event image mask with background-correction taken into account. """ +from __future__ import annotations + import numpy as np +import numpy.typing as npt -def get_bright_bc(mask, image, image_bg, bg_off=None, ret_data="avg,sd"): +def get_bright_bc(mask: npt.NDArray[bool] | list[npt.NDArray[bool]], + image: npt.NDArray | list[npt.NDArray], + image_bg: npt.NDArray | list[npt.NDArray], + bg_off: float | npt.NDArray = None, + ret_data: str = "avg,sd"): """Compute avg and/or std of the background-corrected event brightness The background-corrected event brightness is defined by the diff --git a/dclab/features/bright_perc.py b/dclab/features/bright_perc.py index bc5a6b1c..6a9ced56 100644 --- a/dclab/features/bright_perc.py +++ b/dclab/features/bright_perc.py @@ -2,6 +2,8 @@ Computation of the 10th and 90th percentile of grayscale values inside the RT-DC event image mask with background-correction taken into account. """ +from __future__ import annotations + import numpy as np import numpy.typing as npt diff --git a/dclab/features/contour.py b/dclab/features/contour.py index 977b13da..c9e13fec 100644 --- a/dclab/features/contour.py +++ b/dclab/features/contour.py @@ -1,8 +1,11 @@ """Computation of event contour from event mask""" +from __future__ import annotations + from collections import deque import numbers import numpy as np +import numpy.typing as npt # equivalent to # from skimage.measure import find_contours @@ -14,15 +17,15 @@ class NoValidContourFoundError(BaseException): class LazyContourList(object): - def __init__(self, masks, max_events=1000): + def __init__(self, masks: npt.ArrayLike, max_events: int = 1000): """A list-like object that computes contours upon indexing Parameters ---------- - masks: array-like + masks 3D array of masks, may be an HDF5 dataset or any other structure that supports indexing - max_events: int + max_events maximum number of contours to keep in the contour list; set to 0/False/None to cache all contours From 6f92494077aea36054ea8419779f1f273058fcc3 Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell Date: Tue, 26 Nov 2024 14:59:33 +0100 Subject: [PATCH 05/20] docs: add type hints for contour --- dclab/features/bright.py | 7 ++++++- dclab/features/bright_bc.py | 7 ++++++- dclab/features/bright_perc.py | 6 ++++-- dclab/features/contour.py | 11 ++++++----- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/dclab/features/bright.py b/dclab/features/bright.py index a5e40931..9845ac2c 100644 --- a/dclab/features/bright.py +++ b/dclab/features/bright.py @@ -10,7 +10,12 @@ def get_bright(mask: npt.NDArray[bool] | list[npt.NDArray[bool]], image: npt.NDArray | list[npt.NDArray], - ret_data: str = "avg,sd") -> float | npt.NDArray: + ret_data: str = "avg,sd") -> ( + float | + npt.NDArray | + tuple[float, float] | + tuple[npt.NDArray, npt.NDArray] +): """Compute avg and/or std of the event brightness The event brightness is defined by the gray-scale values of the diff --git a/dclab/features/bright_bc.py b/dclab/features/bright_bc.py index ec99e048..3feb89fd 100644 --- a/dclab/features/bright_bc.py +++ b/dclab/features/bright_bc.py @@ -12,7 +12,12 @@ def get_bright_bc(mask: npt.NDArray[bool] | list[npt.NDArray[bool]], image: npt.NDArray | list[npt.NDArray], image_bg: npt.NDArray | list[npt.NDArray], bg_off: float | npt.NDArray = None, - ret_data: str = "avg,sd"): + ret_data: str = "avg,sd") -> ( + float | + npt.NDArray | + tuple[float, float] | + tuple[npt.NDArray, npt.NDArray] +): """Compute avg and/or std of the background-corrected event brightness The background-corrected event brightness is defined by the diff --git a/dclab/features/bright_perc.py b/dclab/features/bright_perc.py index 6a9ced56..0fdc2c8e 100644 --- a/dclab/features/bright_perc.py +++ b/dclab/features/bright_perc.py @@ -11,8 +11,10 @@ def get_bright_perc(mask: npt.NDArray[bool] | list[npt.NDArray[bool]], image: npt.NDArray | list[npt.NDArray], image_bg: npt.NDArray | list[npt.NDArray], - bg_off: float | npt.NDArray = None) -> \ - tuple[float, float] | tuple[npt.NDArray, npt.NDArray]: + bg_off: float | npt.NDArray = None) -> ( + tuple[float, float] | + tuple[npt.NDArray, npt.NDArray] +): """Compute 10th and 90th percentile of the bg-corrected event brightness The background-corrected event brightness is defined by the diff --git a/dclab/features/contour.py b/dclab/features/contour.py index c9e13fec..b24a937d 100644 --- a/dclab/features/contour.py +++ b/dclab/features/contour.py @@ -17,7 +17,7 @@ class NoValidContourFoundError(BaseException): class LazyContourList(object): - def __init__(self, masks: npt.ArrayLike, max_events: int = 1000): + def __init__(self, masks: npt.ArrayLike, max_events: int = 1000) -> None: """A list-like object that computes contours upon indexing Parameters @@ -43,7 +43,7 @@ def __init__(self, masks: npt.ArrayLike, max_events: int = 1000): self.identifier = str(masks[0][:].tobytes()) self.shape = len(masks), np.nan, 2 - def __getitem__(self, idx): + def __getitem__(self, idx) -> npt.NDArray: """Compute contour(s) if not already in self.contours""" if not isinstance(idx, numbers.Integral): # slicing! @@ -77,7 +77,7 @@ def __len__(self): return len(self.masks) -def get_contour(mask): +def get_contour(mask: npt.NDArray[bool]) -> npt.NDArray | list[npt.NDArray]: """Compute the image contour from a mask The contour is computed in a very inefficient way using scikit-image @@ -130,7 +130,8 @@ def get_contour(mask): return contours[0] -def get_contour_lazily(mask): +def get_contour_lazily(mask: npt.NDArray[bool]) -> \ + npt.NDArray | LazyContourList: """Like :func:`get_contour`, but computes contours on demand Parameters @@ -156,7 +157,7 @@ def get_contour_lazily(mask): return cont -def remove_duplicates(cont): +def remove_duplicates(cont: npt.NDArray) -> npt.NDArray: """Remove duplicates in a circular contour""" x = np.resize(cont, (len(cont) + 1, 2)) selection = np.ones(len(x), dtype=bool) From 63cdca8fd7ba6b5d955157e451793ba3d9317df4 Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell Date: Tue, 26 Nov 2024 15:37:35 +0100 Subject: [PATCH 06/20] docs type hints for fl_crosstalk --- dclab/features/fl_crosstalk.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/dclab/features/fl_crosstalk.py b/dclab/features/fl_crosstalk.py index 87937c9e..c4db8127 100644 --- a/dclab/features/fl_crosstalk.py +++ b/dclab/features/fl_crosstalk.py @@ -1,9 +1,13 @@ """Crosstalk-correction for fluorescence data""" +from __future__ import annotations import numpy as np +import numpy.typing as npt -def get_compensation_matrix(ct21, ct31, ct12, ct32, ct13, ct23): +def get_compensation_matrix( + ct21: float, ct31: float, ct12: float, + ct32: float, ct13: float, ct23: float) -> npt.NDArray: """Compute crosstalk inversion matrix The spillover matrix is @@ -55,8 +59,13 @@ def get_compensation_matrix(ct21, ct31, ct12, ct32, ct13, ct23): return np.linalg.inv(crosstalk) -def correct_crosstalk(fl1, fl2, fl3, fl_channel, - ct21=0, ct31=0, ct12=0, ct32=0, ct13=0, ct23=0): +def correct_crosstalk( + fl1: int | float | npt.NDArray, + fl2: int | float | npt.NDArray, + fl3: int | float | npt.NDArray, + fl_channel: int, + ct21: float = 0, ct31: float = 0, ct12: float = 0, + ct32: float = 0, ct13: float = 0, ct23: float = 0) -> npt.NDArray: """Perform crosstalk correction Parameters From a2862a302a1917a0e50327dc0498f795f29c94df Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell Date: Tue, 26 Nov 2024 15:52:49 +0100 Subject: [PATCH 07/20] docs: type hints for volume and inert_ratio --- dclab/features/inert_ratio.py | 21 ++++++++++++++------- dclab/features/volume.py | 22 +++++++++++++++------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/dclab/features/inert_ratio.py b/dclab/features/inert_ratio.py index b5a7db81..a70b7b29 100644 --- a/dclab/features/inert_ratio.py +++ b/dclab/features/inert_ratio.py @@ -1,11 +1,14 @@ """Computation of inertia ratio from contour data""" +from __future__ import annotations + import numpy as np +import numpy.typing as npt import scipy.spatial as ssp -def cont_moments_cv(cont, - flt_epsilon=1.19209e-07, - dbl_epsilon=2.2204460492503131e-16): +def cont_moments_cv(cont: npt.NDArray, + flt_epsilon: float = 1.19209e-07, + dbl_epsilon: float = 2.2204460492503131e-16) -> dict: """Compute the moments of a contour The moments are computed in the same way as they are computed @@ -120,7 +123,8 @@ def cont_moments_cv(cont, return None -def get_inert_ratio_cvx(cont): +def get_inert_ratio_cvx( + cont: npt.NDArray | list[npt.NDArray]) -> float | npt.NDArray: """Compute the inertia ratio of the convex hull of a contour The inertia ratio is computed from the central second order of moments @@ -190,7 +194,8 @@ def get_inert_ratio_cvx(cont): return inert_ratio_cvx -def get_inert_ratio_prnc(cont): +def get_inert_ratio_prnc( + cont: npt.NDArray | list[npt.NDArray]) -> float | npt.NDArray: """Compute principal inertia ratio of a contour The principal inertia ratio is rotation-invariant, which @@ -256,7 +261,8 @@ def get_inert_ratio_prnc(cont): return inert_ratio_prnc -def get_inert_ratio_raw(cont): +def get_inert_ratio_raw( + cont: npt.NDArray | list[npt.NDArray]) -> float | npt.NDArray: """Compute the inertia ratio of a contour The inertia ratio is computed from the central second order of moments @@ -321,7 +327,8 @@ def get_inert_ratio_raw(cont): return inert_ratio_raw -def get_tilt(cont): +def get_tilt( + cont: npt.NDArray | list[npt.NDArray]) -> float | npt.NDArray: """Compute tilt of raw contour relative to channel axis Parameters diff --git a/dclab/features/volume.py b/dclab/features/volume.py index b4fa904a..873b859c 100644 --- a/dclab/features/volume.py +++ b/dclab/features/volume.py @@ -1,8 +1,16 @@ """Volume computation based on contour revolution""" +from __future__ import annotations + import numpy as np +import numpy.typing as npt -def get_volume(cont, pos_x, pos_y, pix, fix_orientation=False): +def get_volume( + cont: npt.NDArray | list[npt.NDArray], + pos_x: float | npt.NDArray, + pos_y: float | npt.NDArray, + pix: float, + fix_orientation: bool = False) -> float | npt.NDArray: """Calculate the volume of a polygon revolved around an axis The volume estimation assumes rotational symmetry. @@ -43,7 +51,7 @@ def get_volume(cont, pos_x, pos_y, pix, fix_orientation=False): upper and the lower halves of the contour from which the average is then used. - The volume is computed radially from the the center position + The volume is computed radially from the center position given by (`pos_x`, `pos_y`). For sufficiently smooth contours, such as densely sampled ellipses, the center position does not play an important role. For contours that are given on a coarse @@ -125,7 +133,7 @@ def get_volume(cont, pos_x, pos_y, pix, fix_orientation=False): return v_avg -def counter_clockwise(cx, cy): +def counter_clockwise(cx: npt.NDArray, cy: npt.NDArray) -> float: """Put contour coordinates into counter-clockwise order Parameters @@ -152,7 +160,7 @@ def counter_clockwise(cx, cy): return cx, cy -def vol_revolve(r, z, point_scale=1.): +def vol_revolve(r: npt.NDArray, z: npt.NDArray, point_scale: float = 1.): r"""Calculate the volume of a polygon revolved around the Z-axis This implementation yields the same results as the volRevolve @@ -222,9 +230,9 @@ def vol_revolve(r, z, point_scale=1.): # dr = R - r and dz = h, then we get three terms for the volume # (as opposed to four terms in Olynyk's script). Those three terms # all resemble area slices multiplied by the z-distance dz. - a1 = 3 * rp**2 - a2 = 3 * rp*dr - a3 = dr**2 + a1 = 3 * rp ** 2 + a2 = 3 * rp * dr + a3 = dr ** 2 # Note that the formula for computing the volume is symmetric # with respect to r and R. This means that it does not matter From c695331c5f937702d7928b9891eaaf6689d7b74f Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell Date: Tue, 26 Nov 2024 16:06:17 +0100 Subject: [PATCH 08/20] docs: add type hints to emodulus load --- dclab/features/emodulus/load.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/dclab/features/emodulus/load.py b/dclab/features/emodulus/load.py index 68992cfc..94e5daa9 100644 --- a/dclab/features/emodulus/load.py +++ b/dclab/features/emodulus/load.py @@ -12,6 +12,7 @@ import warnings import numpy as np +import numpy.typing as npt from ... import definitions as dfn @@ -24,7 +25,7 @@ @functools.lru_cache() -def get_internal_lut_names_dict(): +def get_internal_lut_names_dict() -> dict: """Return list of internal lut names""" lutfiles = {} for pp in importlib_resources.files('dclab.features.emodulus').iterdir(): @@ -34,7 +35,7 @@ def get_internal_lut_names_dict(): return lutfiles -def get_lut_path(path_or_id): +def get_lut_path(path_or_id: str | pathlib.Path) -> pathlib.Path: """Find the path to a LUT path_or_id: str or pathlib.Path @@ -63,7 +64,8 @@ def get_lut_path(path_or_id): return lut_path -def load_lut(lut_data: str | pathlib.Path | np.ndarray = "LE-2D-FEM-19"): +def load_lut(lut_data: str | pathlib.Path | tuple[npt.NDArray, dict] = + "LE-2D-FEM-19") -> tuple[npt.NDArray, dict]: """Load LUT data from disk Parameters @@ -98,7 +100,7 @@ def load_lut(lut_data: str | pathlib.Path | np.ndarray = "LE-2D-FEM-19"): return lut, meta -def load_mtext(path): +def load_mtext(path: str | pathlib.Path) -> tuple[npt.NDArray, dict]: """Load column-based data from text files with metadata This file format is used for isoelasticity lines and look-up @@ -217,7 +219,7 @@ def load_mtext(path): return data, meta -def register_lut(path, identifier=None): +def register_lut(path: str | pathlib.Path, identifier: str = None) -> None: """Register an external LUT file in dclab This will add it to :const:`EXTERNAL_LUTS`, which is required From ea1ad64c1fe27aaa53e338f711a224c93de4ddad Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell Date: Tue, 26 Nov 2024 16:15:40 +0100 Subject: [PATCH 09/20] docs: type hints for emod pxcorr --- dclab/features/emodulus/pxcorr.py | 39 ++++++++++++++++++++----------- tests/test_feat_emodulus.py | 9 ++++++- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/dclab/features/emodulus/pxcorr.py b/dclab/features/emodulus/pxcorr.py index e4b33af5..a430ef2f 100644 --- a/dclab/features/emodulus/pxcorr.py +++ b/dclab/features/emodulus/pxcorr.py @@ -1,9 +1,12 @@ """Pixelation correction definitions""" +from __future__ import annotations import numpy as np +import numpy.typing as npt -def corr_deform_with_area_um(area_um, px_um=0.34): +def corr_deform_with_area_um(area_um: float | npt.NDArray, + px_um: float = 0.34) -> float | npt.NDArray: """Deformation correction for area_um-deform data The contour in RT-DC measurements is computed on a @@ -14,14 +17,14 @@ def corr_deform_with_area_um(area_um, px_um=0.34): Parameters ---------- - area_um: float or ndarray + area_um Apparent (2D image) area in µm² of the event(s) - px_um: float + px_um The detector pixel size in µm. Returns ------- - deform_delta: float or ndarray + deform_delta Error of the deformation of the event(s) that must be subtracted from `deform`. deform_corr = deform - deform_delta @@ -46,7 +49,8 @@ def corr_deform_with_area_um(area_um, px_um=0.34): return delta -def corr_deform_with_volume(volume, px_um=0.34): +def corr_deform_with_volume(volume: float | npt.NDArray, + px_um: float = 0.34) -> float | npt.NDArray: """Deformation correction for volume-deform data The contour in RT-DC measurements is computed on a @@ -57,14 +61,14 @@ def corr_deform_with_volume(volume, px_um=0.34): Parameters ---------- - volume: float or ndarray + volume The "volume" feature (rotation of raw contour) [µm³] - px_um: float + px_um The detector pixel size in µm. Returns ------- - deform_delta: float or ndarray + deform_delta Error of the deformation of the event(s) that must be subtracted from `deform`. deform_corr = deform - deform_delta @@ -78,7 +82,13 @@ def corr_deform_with_volume(volume, px_um=0.34): return delta -def get_pixelation_delta_pair(feat1, feat2, data1, data2, px_um=0.34): +def get_pixelation_delta_pair( + feat1: str, + feat2: str, + data1: float | npt.NDArray, + data2: float | npt.NDArray, + px_um: float = 0.34) -> (tuple[float, float] | + tuple[npt.NDArray, npt.NDArray]): """Convenience function that returns pixelation correction pair""" # determine feature that defines abscissa feat_absc = feat1 if feat1 in ["area_um", "volume"] else feat2 @@ -97,17 +107,20 @@ def get_pixelation_delta_pair(feat1, feat2, data1, data2, px_um=0.34): return delt1, delt2 -def get_pixelation_delta(feat_corr, feat_absc, data_absc, px_um=0.34): +def get_pixelation_delta(feat_corr: str, + feat_absc: str, + data_absc: float | npt.NDArray, + px_um: float = 0.34) -> float | npt.NDArray: """Convenience function for obtaining pixelation correction Parameters ---------- - feat_corr: str + feat_corr Feature for which to compute the pixelation correction (e.g. "deform") - feat_absc: str + feat_absc Feature with which to compute the correction (e.g. "area_um"); - data_absc: ndarray or float + data_absc Corresponding data for `feat_absc` px_um: float Detector pixel size [µm] diff --git a/tests/test_feat_emodulus.py b/tests/test_feat_emodulus.py index 922ba0ad..31ee8d08 100644 --- a/tests/test_feat_emodulus.py +++ b/tests/test_feat_emodulus.py @@ -298,7 +298,6 @@ def test_af_emodulus_temp_feat_with_basin(): ) with dclab.new_dataset(path) as ds: - ds.config["calculation"] = {"emodulus lut": "LE-2D-FEM-19", "emodulus viscosity model": "herold-2017", "emodulus medium": "CellCarrier", @@ -585,6 +584,14 @@ def test_pixelation_correction_volume(): assert np.allclose(ddelt, 0.011464479831134636) +def test_pixelation_correction_volume_ndarray_input(): + ddelt = emodulus.get_pixelation_delta(feat_corr="deform", + feat_absc="volume", + data_absc=np.array([100, 100, 90]), + px_um=0.34) + assert np.allclose(ddelt, np.array([0.01146448, 0.01146448, 0.01199665])) + + def test_register_external_lut(): """Load an external LUT and compute YM data""" identifier = "test-test_register_external_lut" From 392b57cd9bf606d850b7817ffb1e7e43cd1303c9 Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell Date: Wed, 27 Nov 2024 11:08:56 +0100 Subject: [PATCH 10/20] docs: add type hints for emod __init__ functions --- dclab/features/emodulus/__init__.py | 58 ++++++++++++++++------------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/dclab/features/emodulus/__init__.py b/dclab/features/emodulus/__init__.py index c4767972..b6cc9a2b 100644 --- a/dclab/features/emodulus/__init__.py +++ b/dclab/features/emodulus/__init__.py @@ -7,6 +7,7 @@ import warnings import numpy as np +import numpy.typing as npt import scipy.interpolate as spint from ...warn import PipelineWarning @@ -18,7 +19,6 @@ from .scale_linear import scale_emodulus, scale_feature from .viscosity import get_viscosity - #: Set this to True to globally enable spline extrapolation when the #: `area_um`/`deform` data are outside the LUT. This is discouraged and #: a :class:`KnowWhatYouAreDoingWarning` warning will be issued. @@ -33,8 +33,13 @@ class YoungsModulusLookupTableExceededWarning(PipelineWarning): pass -def extrapolate_emodulus(lut, datax, deform, emod, deform_norm, - deform_thresh=.05, inplace=True): +def extrapolate_emodulus(lut: npt.NDArray, + datax: npt.NDArray, + deform: npt.NDArray, + emod: npt.NDArray, + deform_norm: float, + deform_thresh: float = .05, + inplace: bool = True) -> npt.NDArray: """Use spline interpolation to fill in nan-values When points (`datax`, `deform`) are outside the convex @@ -56,21 +61,21 @@ def extrapolate_emodulus(lut, datax, deform, emod, deform_norm, points, second axis enumerates datax, deform, and emodulus) datax: ndarray of size N The normalized x data (corresponding to `lut[:, 0]`) - deform: ndarray of size N - The normalized deform (corresponding to `lut[:, 1]`) emod: ndarray of size N The emodulus (corresponding to `lut[:, 2]`); If `emod` does not contain nan-values, there is nothing to do here. - deform_norm: float + deform: ndarray of size N + The normalized deform (corresponding to `lut[:, 1]`) + deform_norm The normalization value used to normalize `lut[:, 1]` and `deform`. - deform_thresh: float + deform_thresh Not the entire LUT is used for bivariate spline interpolation. Only the points where `lut[:, 1] > deform_thresh/deform_norm` are used. This is necessary, because for small deformations, the LUT has an extreme slope that kills any meaningful spline interpolation. - inplace: bool + inplace If True (default), replaces nan values in `emod` in-place. If False, `emod` is not modified. """ @@ -99,48 +104,49 @@ def extrapolate_emodulus(lut, datax, deform, emod, deform_norm, return emod -def get_emodulus(deform: float | np.array, - area_um: float | np.array | None = None, - volume: float | np.array | None = None, +def get_emodulus(deform: float | npt.NDArray, + area_um: float | npt.NDArray | None = None, + volume: float | npt.NDArray | None = None, medium: float | str = "0.49% MC-PBS", channel_width: float = 20.0, flow_rate: float = 0.16, px_um: float = 0.34, - temperature: float | np.ndarray | None = 23.0, - lut_data: str | pathlib.Path | np.ndarray = "LE-2D-FEM-19", + temperature: float | npt.NDArray | None = 23.0, + lut_data: (str | pathlib.Path | + tuple[npt.NDArray, dict]) = "LE-2D-FEM-19", visc_model: Literal['herold-2017', 'herold-2017-fallback', 'buyukurganci-2022', 'kestin-1978', None] = "herold-2017-fallback", extrapolate: bool = INACCURATE_SPLINE_EXTRAPOLATION, - copy: bool = True): + copy: bool = True) -> npt.NDArray: """Compute apparent Young's modulus using a look-up table Parameters ---------- - area_um: float or ndarray + area_um Apparent (2D image) area [µm²] of the event(s) - deform: float or ndarray + deform Deformation (1-circularity) of the event(s) - volume: float or ndarray + volume Apparent volume of the event(s). It is not possible to define `volume` and `area_um` at the same time (makes no sense). .. versionadded:: 0.25.0 - medium: str or float + medium The medium to compute the viscosity for. If a string is given, the viscosity is computed. If a float is given, this value is used as the viscosity in mPa*s (Note that `temperature` and `visc_model` must be set to None in this case). - channel_width: float + channel_width The channel width [µm] - flow_rate: float + flow_rate Flow rate [µL/s] - px_um: float + px_um The detector pixel size [µm] used for pixelation correction. Set to zero to disable. - temperature: float, ndarray, or None + temperature Temperature [°C] of the event(s) lut_data: path, str, or tuple of (np.ndarray of shape (N, 3), dict) The LUT data to use. If it is a built-in identifier, @@ -150,13 +156,13 @@ def get_emodulus(deform: float | np.array, (e.g. `area_um` and `deform`) are valid interpolation choices. .. versionadded:: 0.25.0 - visc_model: str + visc_model The viscosity model to use, see :func:`dclab.features.emodulus.viscosity.get_viscosity` - extrapolate: bool + extrapolate Perform extrapolation using :func:`extrapolate_emodulus`. This is discouraged! - copy: bool + copy Copy input arrays. If set to false, input arrays are overridden. @@ -326,7 +332,7 @@ def get_emodulus(deform: float | np.array, return emod -def normalize(data, dmax): +def normalize(data: npt.NDArray, dmax: float) -> npt.NDArray: """Perform normalization in-place for interpolation Note that :func:`scipy.interpolate.griddata` has a `rescale` From 35bdbd843fe1374f7f509311012b316267090fce Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell Date: Wed, 27 Nov 2024 11:39:03 +0100 Subject: [PATCH 11/20] docs: add type hints for emod scale_linear --- dclab/features/emodulus/scale_linear.py | 129 ++++++++++++++---------- 1 file changed, 78 insertions(+), 51 deletions(-) diff --git a/dclab/features/emodulus/scale_linear.py b/dclab/features/emodulus/scale_linear.py index c0128898..41dee13a 100644 --- a/dclab/features/emodulus/scale_linear.py +++ b/dclab/features/emodulus/scale_linear.py @@ -1,47 +1,60 @@ """Scale conversion applicable to a linear elastic model""" +from __future__ import annotations import warnings import numpy as np - - -def convert(area_um, deform, channel_width_in, channel_width_out, - emodulus=None, flow_rate_in=None, flow_rate_out=None, - viscosity_in=None, viscosity_out=None, inplace=False): +import numpy.typing as npt + + +def convert(area_um: npt.NDArray, + deform: npt.NDArray, + channel_width_in: float, + channel_width_out: float, + emodulus: npt.NDArray = None, + flow_rate_in: float = None, + flow_rate_out: float = None, + viscosity_in: float = None, + viscosity_out: float | npt.NDArray = None, + inplace: bool = False + ) -> ( + tuple[npt.NDArray, npt.NDArray] | + tuple[npt.NDArray, npt.NDArray, npt.NDArray] +): """convert area-deformation-emodulus triplet The conversion formula is described in :cite:`Mietke2015`. Parameters ---------- - area_um: ndarray + area_um Convex cell area [µm²] - deform: ndarray + deform Deformation - channel_width_in: float + channel_width_in Original channel width [µm] - channel_width_out: float + channel_width_out Target channel width [µm] - emodulus: ndarray + emodulus Young's Modulus [kPa] - flow_rate_in: float + flow_rate_in Original flow rate [µL/s] - flow_rate_out: float + flow_rate_out Target flow rate [µL/s] - viscosity_in: float + viscosity_in Original viscosity [mPa*s] - viscosity_out: float or ndarray + viscosity_out Target viscosity [mPa*s]; This can be an array - inplace: bool + inplace If True, override input arrays with corrected data Returns ------- - area_um_corr: ndarray + area_um_corr Corrected cell area [µm²] - deform_corr: ndarray + deform_corr Deformation (a copy if `inplace` is False) - emodulus_corr: ndarray + emodulus_corr Corrected emodulus [kPa]; only returned if `emodulus` is given. Notes @@ -81,8 +94,11 @@ def convert(area_um, deform, channel_width_in, channel_width_out, return area_um_corr, deform_corr, emodulus_corr -def scale_area_um(area_um, channel_width_in, channel_width_out, inplace=False, - **kwargs): +def scale_area_um(area_um: npt.NDArray, + channel_width_in: float, + channel_width_out: float, + inplace: bool = False, + **kwargs) -> npt.NDArray: """Perform scale conversion for area_um (linear elastic model) The area scales with the characteristic length @@ -94,20 +110,20 @@ def scale_area_um(area_um, channel_width_in, channel_width_out, inplace=False, Parameters ---------- - area_um: ndarray + area_um Convex area [µm²] - channel_width_in: float + channel_width_in Original channel width [µm] - channel_width_out: float + channel_width_out Target channel width [µm] - inplace: bool + inplace If True, override input arrays with corrected data - kwargs: + kwargs not used Returns ------- - area_um_corr: ndarray + area_um_corr Scaled area [µm²] """ copy = not inplace @@ -120,9 +136,14 @@ def scale_area_um(area_um, channel_width_in, channel_width_out, inplace=False, return area_um_corr -def scale_emodulus(emodulus, channel_width_in, channel_width_out, - flow_rate_in, flow_rate_out, viscosity_in, - viscosity_out, inplace=False): +def scale_emodulus(emodulus: npt.NDArray, + channel_width_in: float, + channel_width_out: float, + flow_rate_in: float, + flow_rate_out: float, + viscosity_in: float, + viscosity_out: float | npt.NDArray, + inplace: bool = False) -> npt.NDArray: """Perform scale conversion for area_um (linear elastic model) The conversion formula is described in :cite:`Mietke2015`. @@ -131,26 +152,26 @@ def scale_emodulus(emodulus, channel_width_in, channel_width_out, Parameters ---------- - emodulus: ndarray + emodulus Young's Modulus [kPa] - channel_width_in: float + channel_width_in Original channel width [µm] - channel_width_out: float + channel_width_out Target channel width [µm] - flow_rate_in: float + flow_rate_in Original flow rate [µL/s] - flow_rate_out: float + flow_rate_out Target flow rate [µL/s] - viscosity_in: float + viscosity_in Original viscosity [mPa*s] - viscosity_out: float or ndarray + viscosity_out Target viscosity [mPa*s]; This can be an array - inplace: bool + inplace If True, override input arrays with corrected data Returns ------- - emodulus_corr: ndarray + emodulus_corr Scaled emodulus [kPa] """ copy = not inplace @@ -182,7 +203,10 @@ def scale_emodulus(emodulus, channel_width_in, channel_width_out, return emodulus_corr -def scale_feature(feat, data, inplace=False, **scale_kw): +def scale_feature(feat: str, + data: float | npt.NDArray, + inplace: bool = False, + **scale_kw) -> npt.NDArray: """Convenience function for scale conversions (linear elastic model) This method wraps around all the other scale_* methods and also @@ -190,13 +214,13 @@ def scale_feature(feat, data, inplace=False, **scale_kw): Parameters ---------- - feat: str + feat Valid scalar feature name - data: float or ndarray + data Feature data - inplace: bool + inplace If True, override input arrays with corrected data - **scale_kw: + **scale_kw Scale keyword arguments for the wrapped methods """ if feat == "area_um": @@ -212,8 +236,11 @@ def scale_feature(feat, data, inplace=False, **scale_kw): raise KeyError("No recipe to scale feature '{}'!".format(feat)) -def scale_volume(volume, channel_width_in, channel_width_out, inplace=False, - **kwargs): +def scale_volume(volume: npt.NDArray, + channel_width_in: float, + channel_width_out: float, + inplace: bool = False, + **kwargs) -> npt.NDArray: """Perform scale conversion for volume (linear elastic model) The volume scales with the characteristic length @@ -223,20 +250,20 @@ def scale_volume(volume, channel_width_in, channel_width_out, inplace=False, Parameters ---------- - volume: ndarray + volume Volume [µm³] - channel_width_in: float + channel_width_in Original channel width [µm] - channel_width_out: float + channel_width_out Target channel width [µm] - inplace: bool + inplace If True, override input arrays with corrected data - kwargs: + kwargs not used Returns ------- - volume_corr: ndarray + volume_corr Scaled volume [µm³] """ copy = not inplace From 63905bf70ec5eff4b193fd482332bf1c24259472 Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell Date: Wed, 27 Nov 2024 11:52:48 +0100 Subject: [PATCH 12/20] docs: add type hints for emod viscosity --- dclab/features/emodulus/viscosity.py | 50 +++++++++++++++++----------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/dclab/features/emodulus/viscosity.py b/dclab/features/emodulus/viscosity.py index 40353a5a..23e3299b 100644 --- a/dclab/features/emodulus/viscosity.py +++ b/dclab/features/emodulus/viscosity.py @@ -5,6 +5,7 @@ import warnings import numpy as np +import numpy.typing as npt from ...warn import PipelineWarning @@ -45,10 +46,17 @@ class TemperatureOutOfRangeWarning(PipelineWarning): def check_temperature(model: str, - temperature: float | np.array, + temperature: float | npt.NDArray, tmin: float, - tmax: float): - """Raise a TemperatureOutOfRangeWarning if applicable""" + tmax: float) -> None: + """Raise a TemperatureOutOfRangeWarning if applicable + + Raises + ------ + TemperatureOutOfRangeWarning + If the given temperature is out of the given range. + + """ if np.min(temperature) < tmin or np.max(temperature) > tmax: warnings.warn( f"For the {model} model, the temperature should be " @@ -60,11 +68,12 @@ def check_temperature(model: str, def get_viscosity(medium: str = "0.49% MC-PBS", channel_width: float = 20.0, flow_rate: float = 0.16, - temperature: float | np.ndarray = 23.0, + temperature: float | npt.NDArray = 23.0, model: Literal['herold-2017', 'herold-2017-fallback', 'buyukurganci-2022', - 'kestin-1978'] = 'herold-2017-fallback'): + 'kestin-1978'] = 'herold-2017-fallback' + ) -> float | npt.NDArray: """Returns the viscosity for RT-DC-specific media Media that are not pure (e.g. ketchup or polymer solutions) @@ -83,16 +92,16 @@ def get_viscosity(medium: str = "0.49% MC-PBS", Parameters ---------- - medium: str + medium The medium to compute the viscosity for; Valid values are defined in :const:`KNOWN_MEDIA`. - channel_width: float + channel_width The channel width in µm - flow_rate: float + flow_rate Flow rate in µL/s - temperature: float or ndarray + temperature Temperature in °C - model: str + model The model name to use for computing the medium viscosity. For water, this value is ignored, as there is only the 'kestin-1978' model :cite:`Kestin_1978`. For MC-PBS media, @@ -101,7 +110,7 @@ def get_viscosity(medium: str = "0.49% MC-PBS", Returns ------- - viscosity: float or ndarray + viscosity Viscosity in mPa*s Notes @@ -148,21 +157,23 @@ def get_viscosity(medium: str = "0.49% MC-PBS", return eta -def shear_rate_square_channel(flow_rate, channel_width, flow_index): +def shear_rate_square_channel(flow_rate: float, + channel_width: float, + flow_index: float) -> float: """Returns The wall shear rate of a power law liquid in a squared channel. Parameters ---------- - flow_rate: float + flow_rate Flow rate in µL/s - channel_width: float + channel_width The channel width in µm - flow_index: float + flow_index Flow behavior index aka the power law exponent of the shear thinning Returns ------- - shear_rate: float + shear_rate Shear rate in 1/s. """ # convert channel width to mm @@ -176,7 +187,7 @@ def get_viscosity_mc_pbs_buyukurganci_2022( "0.83% MC-PBS"] = "0.49% MC-PBS", channel_width: float = 20.0, flow_rate: float = 0.16, - temperature: float = 23.0): + temperature: float = 23.0) -> float | npt.NDArray: """Compute viscosity of MC-PBS according to :cite:`Buyukurganci2022` This viscosity model was derived in :cite:`Buyukurganci2022` @@ -211,7 +222,7 @@ def get_viscosity_mc_pbs_herold_2017( medium: Literal["0.49% MC-PBS", "0.59% MC-PBS"] = "0.49% MC-PBS", channel_width: float = 20.0, flow_rate: float = 0.16, - temperature: float = 23.0): + temperature: float = 23.0) -> float | npt.NDArray: r"""Compute viscosity of MC-PBS according to :cite:`Herold2017` Note that all the factors in equation 5.2 in :cite:`Herold2017` @@ -242,7 +253,8 @@ def get_viscosity_mc_pbs_herold_2017( return eta -def get_viscosity_water_kestin_1978(temperature: float = 23.0): +def get_viscosity_water_kestin_1978( + temperature: float = 23.0) -> float | npt.NDArray: """Compute the viscosity of water according to :cite:`Kestin_1978`""" # see equation (15) in Kestin et al, J. Phys. Chem. 7(3) 1978 check_temperature("'kestin-1978' water", temperature, 0, 40) From e49eba0366533c620a4a9399413f4c25b38fc230 Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell Date: Wed, 27 Nov 2024 16:36:53 +0100 Subject: [PATCH 13/20] ref: docstrings - remove duplicate info but keep array shape info --- dclab/features/bright.py | 13 ++++++------- dclab/features/bright_bc.py | 13 ++++++------- dclab/features/bright_perc.py | 7 +++---- dclab/features/fl_crosstalk.py | 14 +++++++------- dclab/features/inert_ratio.py | 8 ++++---- dclab/features/volume.py | 22 ++++++++++++---------- 6 files changed, 38 insertions(+), 39 deletions(-) diff --git a/dclab/features/bright.py b/dclab/features/bright.py index 9845ac2c..221af38d 100644 --- a/dclab/features/bright.py +++ b/dclab/features/bright.py @@ -10,12 +10,11 @@ def get_bright(mask: npt.NDArray[bool] | list[npt.NDArray[bool]], image: npt.NDArray | list[npt.NDArray], - ret_data: str = "avg,sd") -> ( - float | - npt.NDArray | - tuple[float, float] | - tuple[npt.NDArray, npt.NDArray] -): + ret_data: str = "avg,sd" + ) -> (float | + npt.NDArray | + tuple[float, float] | + tuple[npt.NDArray, npt.NDArray]): """Compute avg and/or std of the event brightness The event brightness is defined by the gray-scale values of the @@ -28,7 +27,7 @@ def get_bright(mask: npt.NDArray[bool] | list[npt.NDArray[bool]], image: ndarray or list of ndarrays of shape (M,N) A 2D array that holds the image in form of grayscale values of an event. - ret_data: str + ret_data A comma-separated list of metrices to compute - "avg": compute the average - "sd": compute the standard deviation diff --git a/dclab/features/bright_bc.py b/dclab/features/bright_bc.py index 3feb89fd..59b8ef97 100644 --- a/dclab/features/bright_bc.py +++ b/dclab/features/bright_bc.py @@ -12,12 +12,11 @@ def get_bright_bc(mask: npt.NDArray[bool] | list[npt.NDArray[bool]], image: npt.NDArray | list[npt.NDArray], image_bg: npt.NDArray | list[npt.NDArray], bg_off: float | npt.NDArray = None, - ret_data: str = "avg,sd") -> ( - float | - npt.NDArray | - tuple[float, float] | - tuple[npt.NDArray, npt.NDArray] -): + ret_data: str = "avg,sd" + ) -> (float | + npt.NDArray | + tuple[float, float] | + tuple[npt.NDArray, npt.NDArray]): """Compute avg and/or std of the background-corrected event brightness The background-corrected event brightness is defined by the @@ -36,7 +35,7 @@ def get_bright_bc(mask: npt.NDArray[bool] | list[npt.NDArray[bool]], bg_off: float or 1D ndarray Additional offset value that is added to `image_bg` before background correction - ret_data: str + ret_data A comma-separated list of metrices to compute - "avg": compute the average - "sd": compute the standard deviation diff --git a/dclab/features/bright_perc.py b/dclab/features/bright_perc.py index 0fdc2c8e..f7d267cd 100644 --- a/dclab/features/bright_perc.py +++ b/dclab/features/bright_perc.py @@ -11,10 +11,9 @@ def get_bright_perc(mask: npt.NDArray[bool] | list[npt.NDArray[bool]], image: npt.NDArray | list[npt.NDArray], image_bg: npt.NDArray | list[npt.NDArray], - bg_off: float | npt.NDArray = None) -> ( - tuple[float, float] | - tuple[npt.NDArray, npt.NDArray] -): + bg_off: float | npt.NDArray = None + ) -> (tuple[float, float] | + tuple[npt.NDArray, npt.NDArray]): """Compute 10th and 90th percentile of the bg-corrected event brightness The background-corrected event brightness is defined by the diff --git a/dclab/features/fl_crosstalk.py b/dclab/features/fl_crosstalk.py index c4db8127..fa6dee67 100644 --- a/dclab/features/fl_crosstalk.py +++ b/dclab/features/fl_crosstalk.py @@ -22,12 +22,12 @@ def get_compensation_matrix( Parameters ---------- - cij: float + cij Spill from channel i to channel j Returns ------- - inv: np.ndarray + inv Compensation matrix (inverted spillover matrix) """ ct11 = 1 @@ -70,12 +70,12 @@ def correct_crosstalk( Parameters ---------- - fli: int, float, or np.ndarray + fli Measured fluorescence signals - fl_channel: int (1, 2, or 3) - The channel number for which the crosstalk-corrected signal - should be computed - cij: float + fl_channel + The channel number (1, 2, or 3) for which the crosstalk-corrected + signal should be computed + cij Spill (crosstalk or bleed-through) from channel i to channel j This spill is computed from the fluorescence signal of e.g. single-stained positive control cells; It is defined by the diff --git a/dclab/features/inert_ratio.py b/dclab/features/inert_ratio.py index a70b7b29..a895716f 100644 --- a/dclab/features/inert_ratio.py +++ b/dclab/features/inert_ratio.py @@ -16,11 +16,11 @@ def cont_moments_cv(cont: npt.NDArray, Parameters ---------- - cont: array of shape (N,2) + cont: shape (N,2) The contour for which to compute the moments. - flt_epsilon: float + flt_epsilon The value of ``FLT_EPSILON`` in OpenCV/gcc. - dbl_epsilon: float + dbl_epsilon The value of ``DBL_EPSILON`` in OpenCV/gcc. .. versionchanged:: 0.48.2 @@ -31,7 +31,7 @@ def cont_moments_cv(cont: npt.NDArray, Returns ------- - moments: dict + moments A dictionary of moments. If the moment `m00` is smaller than half of `flt_epsilon`, `None` is returned. """ diff --git a/dclab/features/volume.py b/dclab/features/volume.py index 873b859c..b441e641 100644 --- a/dclab/features/volume.py +++ b/dclab/features/volume.py @@ -28,10 +28,10 @@ def get_volume( pos_y: float or ndarray of length N The y coordinate(s) of the centroid of the event(s) [µm] e.g. obtained using `mm.pos_y` - pix: float + pix The detector pixel size in µm. e.g. obtained using: `mm.config["imaging"]["pixel size"]` - fix_orientation: bool + fix_orientation If set to True, make sure that the orientation of the contour is counter-clockwise in the r-z plane (see :func:`vol_revolve`). This is False by default, because @@ -42,7 +42,7 @@ def get_volume( Returns ------- - volume: float or ndarray + volume volume in um^3 Notes @@ -133,17 +133,17 @@ def get_volume( return v_avg -def counter_clockwise(cx: npt.NDArray, cy: npt.NDArray) -> float: +def counter_clockwise(cx: npt.NDArray, cy: npt.NDArray) -> tuple[float, float]: """Put contour coordinates into counter-clockwise order Parameters ---------- - cx, cy: 1d ndarrays + cx, cy The x- and y-coordinates of the contour Returns ------- - cx_cc, cy_cc: + cx_cc, cy_cc The x- and y-coordinates of the contour in counter-clockwise orientation. @@ -160,7 +160,9 @@ def counter_clockwise(cx: npt.NDArray, cy: npt.NDArray) -> float: return cx, cy -def vol_revolve(r: npt.NDArray, z: npt.NDArray, point_scale: float = 1.): +def vol_revolve(r: npt.NDArray, + z: npt.NDArray, + point_scale: float = 1.) -> float | npt.NDArray: r"""Calculate the volume of a polygon revolved around the Z-axis This implementation yields the same results as the volRevolve @@ -191,11 +193,11 @@ def vol_revolve(r: npt.NDArray, z: npt.NDArray, point_scale: float = 1.): Parameters ---------- - r: 1d np.ndarray + r radial coordinates (perpendicular to the z axis) - z: 1d np.ndarray + z coordinate along the axis of rotation - point_scale: float + point_scale point size in your preferred units; The volume is multiplied by a factor of `point_scale**3`. From 4d48eabb8a5d8cc0bf5eccd04eb86afcdb090b89 Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell Date: Thu, 28 Nov 2024 15:45:12 +0100 Subject: [PATCH 14/20] updated CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index c583b896..06dbe365 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,5 @@ 0.62.7 + - docs: type hinted the features sub-module - fix: IntegrityChecker must not load basins - tests: avoid architecture and Python-version dependent test 0.62.6 From 8f71ab71f50a0ed36a6c5b49a423c89ee876093a Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell Date: Wed, 17 Sep 2025 10:25:45 +0200 Subject: [PATCH 15/20] ref: update CHANGELOG --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 8475b6ca..198e3a84 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,5 @@ 0.67.1 + - docs: type hinted the features sub-module - enh: add mass density feature, deprecate some qpi features (#284) 0.67.0 - BREAKING CHANGE: make it difficult to export the "contour" feature @@ -89,7 +90,6 @@ - fix: metadata of tables not copied by `copier.py` - docs: formatting typo (#270) 0.62.7 - - docs: type hinted the features sub-module - fix: IntegrityChecker must not load basins - fix: KeyError in IntegrityChecker when "basin_events" group missing (#268) - fix: warn user about missing internal basin features while still loading From b6b4b6ba376350938181a3e21925e32524bc62b3 Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell <43960046+PinkShnack@users.noreply.github.com> Date: Thu, 25 Sep 2025 08:59:37 +0000 Subject: [PATCH 16/20] docs: update README with basic type hinting style guide --- .gitignore | 1 + README.rst | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/.gitignore b/.gitignore index 3f879d44..5df8ec92 100644 --- a/.gitignore +++ b/.gitignore @@ -81,6 +81,7 @@ venv/ ENV/ env.bak/ venv.bak/ +.vscode/ dclab/_version.py *.bib.bak diff --git a/README.rst b/README.rst index ee1dd7e1..9461dfbe 100644 --- a/README.rst +++ b/README.rst @@ -88,6 +88,20 @@ We use flake8 to enforce coding style:: flake8 tests +Type Hinting +~~~~~~~~~~~~ +For type hinting and docstring styling, try to keep the +Code Reference readable in the documentation website. +If in doubt just ask, or look at the examples in the codebase. +- Simple type combintions such as `float` or `str | pathlib.Path` + should be included as type hints, but do not need to be included + in the docstring parameter description. +- More involved type hints can have extra information in the + docstring. For example for numpy arrays, `npt.NDArray[np.bool]` doesn't + render in a readable way in the Code Reference, and doesn't include shape. + Therefore, you can also keep the docstring parameter description e.g., + `binary ndarray of shape (M, N)`. + Incrementing version ~~~~~~~~~~~~~~~~~~~~ Dclab gets its version from the latest git tag. From f5790d2518cf651633492698c1c14d7de331e142 Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell <43960046+PinkShnack@users.noreply.github.com> Date: Thu, 25 Sep 2025 09:10:42 +0000 Subject: [PATCH 17/20] docs: small type hinting changes, sphinx should display type hints beside param --- README.rst | 6 ++++-- dclab/features/contour.py | 7 ++++--- dclab/features/emodulus/__init__.py | 12 ++++++------ dclab/features/emodulus/load.py | 2 +- dclab/features/emodulus/pxcorr.py | 9 +++++++-- docs/conf.py | 3 +++ 6 files changed, 25 insertions(+), 14 deletions(-) diff --git a/README.rst b/README.rst index 9461dfbe..3b884fbe 100644 --- a/README.rst +++ b/README.rst @@ -93,15 +93,17 @@ Type Hinting For type hinting and docstring styling, try to keep the Code Reference readable in the documentation website. If in doubt just ask, or look at the examples in the codebase. -- Simple type combintions such as `float` or `str | pathlib.Path` + +* Simple type combintions such as `float` or `str | pathlib.Path` should be included as type hints, but do not need to be included in the docstring parameter description. -- More involved type hints can have extra information in the +* More involved type hints can have extra information in the docstring. For example for numpy arrays, `npt.NDArray[np.bool]` doesn't render in a readable way in the Code Reference, and doesn't include shape. Therefore, you can also keep the docstring parameter description e.g., `binary ndarray of shape (M, N)`. + Incrementing version ~~~~~~~~~~~~~~~~~~~~ Dclab gets its version from the latest git tag. diff --git a/dclab/features/contour.py b/dclab/features/contour.py index b24a937d..0f43a887 100644 --- a/dclab/features/contour.py +++ b/dclab/features/contour.py @@ -77,7 +77,8 @@ def __len__(self): return len(self.masks) -def get_contour(mask: npt.NDArray[bool]) -> npt.NDArray | list[npt.NDArray]: +def get_contour(mask: npt.NDArray[np.bool | np.int] + ) -> npt.NDArray | list[npt.NDArray]: """Compute the image contour from a mask The contour is computed in a very inefficient way using scikit-image @@ -130,8 +131,8 @@ def get_contour(mask: npt.NDArray[bool]) -> npt.NDArray | list[npt.NDArray]: return contours[0] -def get_contour_lazily(mask: npt.NDArray[bool]) -> \ - npt.NDArray | LazyContourList: +def get_contour_lazily(mask: npt.NDArray[np.bool | np.int] + ) -> npt.NDArray | LazyContourList: """Like :func:`get_contour`, but computes contours on demand Parameters diff --git a/dclab/features/emodulus/__init__.py b/dclab/features/emodulus/__init__.py index b6cc9a2b..20793a38 100644 --- a/dclab/features/emodulus/__init__.py +++ b/dclab/features/emodulus/__init__.py @@ -61,11 +61,11 @@ def extrapolate_emodulus(lut: npt.NDArray, points, second axis enumerates datax, deform, and emodulus) datax: ndarray of size N The normalized x data (corresponding to `lut[:, 0]`) + deform: ndarray of size N + The normalized deform (corresponding to `lut[:, 1]`) emod: ndarray of size N The emodulus (corresponding to `lut[:, 2]`); If `emod` does not contain nan-values, there is nothing to do here. - deform: ndarray of size N - The normalized deform (corresponding to `lut[:, 1]`) deform_norm The normalization value used to normalize `lut[:, 1]` and `deform`. @@ -125,10 +125,10 @@ def get_emodulus(deform: float | npt.NDArray, Parameters ---------- - area_um - Apparent (2D image) area [µm²] of the event(s) deform Deformation (1-circularity) of the event(s) + area_um + Apparent (2D image) area [µm²] of the event(s) volume Apparent volume of the event(s). It is not possible to define `volume` and `area_um` at the same time (makes no sense). @@ -148,7 +148,7 @@ def get_emodulus(deform: float | npt.NDArray, Set to zero to disable. temperature Temperature [°C] of the event(s) - lut_data: path, str, or tuple of (np.ndarray of shape (N, 3), dict) + lut_data: str, path, or tuple of (np.ndarray of shape (N, 3), dict) The LUT data to use. If it is a built-in identifier, then the respective LUT will be used. Otherwise, a path to a file on disk or a tuple (LUT array, metadata) is possible. @@ -163,7 +163,7 @@ def get_emodulus(deform: float | npt.NDArray, Perform extrapolation using :func:`extrapolate_emodulus`. This is discouraged! copy - Copy input arrays. If set to false, input arrays are + Copy input arrays. If set to False, input arrays are overridden. Returns diff --git a/dclab/features/emodulus/load.py b/dclab/features/emodulus/load.py index 94e5daa9..15b1031d 100644 --- a/dclab/features/emodulus/load.py +++ b/dclab/features/emodulus/load.py @@ -38,7 +38,7 @@ def get_internal_lut_names_dict() -> dict: def get_lut_path(path_or_id: str | pathlib.Path) -> pathlib.Path: """Find the path to a LUT - path_or_id: str or pathlib.Path + path_or_id Identifier of a LUT. This can be either an existing path (checked first), or an internal identifier (see :func:`get_internal_lut_names_dict`). diff --git a/dclab/features/emodulus/pxcorr.py b/dclab/features/emodulus/pxcorr.py index a430ef2f..7a44c1b2 100644 --- a/dclab/features/emodulus/pxcorr.py +++ b/dclab/features/emodulus/pxcorr.py @@ -18,7 +18,7 @@ def corr_deform_with_area_um(area_um: float | npt.NDArray, Parameters ---------- area_um - Apparent (2D image) area in µm² of the event(s) + Area of the event(s) in µm² px_um The detector pixel size in µm. @@ -122,8 +122,13 @@ def get_pixelation_delta(feat_corr: str, Feature with which to compute the correction (e.g. "area_um"); data_absc Corresponding data for `feat_absc` - px_um: float + px_um Detector pixel size [µm] + + Returns + ------- + For details see :func:`corr_deform_with_area_um` and + :func:`corr_deform_with_volume`. """ if feat_corr == "deform" and feat_absc == "area_um": delt = corr_deform_with_area_um(data_absc, px_um=px_um) diff --git a/docs/conf.py b/docs/conf.py index 25ffaae8..d55b080b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -31,6 +31,9 @@ autodoc_member_order = 'groupwise' autoclass_content = 'both' +# type hints should be displayed next to the parameters +autodoc_typehints = "description" + # include source of matplotlib plots plot_include_source = True From f70ac0c9a1c80d8c2de2918dda61359fa20b4f74 Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell <43960046+PinkShnack@users.noreply.github.com> Date: Thu, 25 Sep 2025 09:28:27 +0000 Subject: [PATCH 18/20] docs: reverting some docstring array shape info --- README.rst | 6 +++--- dclab/features/emodulus/scale_linear.py | 2 +- dclab/features/emodulus/viscosity.py | 2 +- dclab/features/inert_ratio.py | 2 +- dclab/features/volume.py | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.rst b/README.rst index 3b884fbe..d9e70531 100644 --- a/README.rst +++ b/README.rst @@ -94,14 +94,14 @@ For type hinting and docstring styling, try to keep the Code Reference readable in the documentation website. If in doubt just ask, or look at the examples in the codebase. -* Simple type combintions such as `float` or `str | pathlib.Path` +* Simple type combintions such as ``float`` or ``str | pathlib.Path`` should be included as type hints, but do not need to be included in the docstring parameter description. * More involved type hints can have extra information in the - docstring. For example for numpy arrays, `npt.NDArray[np.bool]` doesn't + docstring. For example for numpy arrays, ``npt.NDArray[np.bool]`` doesn't render in a readable way in the Code Reference, and doesn't include shape. Therefore, you can also keep the docstring parameter description e.g., - `binary ndarray of shape (M, N)`. + ``binary ndarray of shape (M, N)``. Incrementing version diff --git a/dclab/features/emodulus/scale_linear.py b/dclab/features/emodulus/scale_linear.py index 41dee13a..be7346e7 100644 --- a/dclab/features/emodulus/scale_linear.py +++ b/dclab/features/emodulus/scale_linear.py @@ -21,7 +21,7 @@ def convert(area_um: npt.NDArray, tuple[npt.NDArray, npt.NDArray] | tuple[npt.NDArray, npt.NDArray, npt.NDArray] ): - """convert area-deformation-emodulus triplet + """Convert area-deformation-emodulus triplet The conversion formula is described in :cite:`Mietke2015`. diff --git a/dclab/features/emodulus/viscosity.py b/dclab/features/emodulus/viscosity.py index 0f097cb9..445d9191 100644 --- a/dclab/features/emodulus/viscosity.py +++ b/dclab/features/emodulus/viscosity.py @@ -258,7 +258,7 @@ def get_viscosity_mc_pbs_herold_2017( def get_viscosity_water_kestin_1978( - temperature: float = 23.0) -> float | npt.NDArray: + temperature: float | npt.NDArray = 23.0) -> float | npt.NDArray: """Compute the viscosity of water according to :cite:`Kestin_1978`""" # see equation (15) in Kestin et al, J. Phys. Chem. 7(3) 1978 check_temperature("'kestin-1978' water", temperature, 0, 40) diff --git a/dclab/features/inert_ratio.py b/dclab/features/inert_ratio.py index a895716f..548b7a88 100644 --- a/dclab/features/inert_ratio.py +++ b/dclab/features/inert_ratio.py @@ -16,7 +16,7 @@ def cont_moments_cv(cont: npt.NDArray, Parameters ---------- - cont: shape (N,2) + cont: ndarray of shape (N,2) The contour for which to compute the moments. flt_epsilon The value of ``FLT_EPSILON`` in OpenCV/gcc. diff --git a/dclab/features/volume.py b/dclab/features/volume.py index b441e641..bb267237 100644 --- a/dclab/features/volume.py +++ b/dclab/features/volume.py @@ -138,7 +138,7 @@ def counter_clockwise(cx: npt.NDArray, cy: npt.NDArray) -> tuple[float, float]: Parameters ---------- - cx, cy + cx, cy: 1d ndarrays The x- and y-coordinates of the contour Returns @@ -193,9 +193,9 @@ def vol_revolve(r: npt.NDArray, Parameters ---------- - r + r: 1d ndarray radial coordinates (perpendicular to the z axis) - z + z: 1d ndarray coordinate along the axis of rotation point_scale point size in your preferred units; The volume is multiplied From bd1bc1b683eaa1cd415c8d84de20bc3bd6bc2f21 Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell <43960046+PinkShnack@users.noreply.github.com> Date: Fri, 26 Sep 2025 06:51:51 +0000 Subject: [PATCH 19/20] docs: minor README update --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index d9e70531..15fa128f 100644 --- a/README.rst +++ b/README.rst @@ -100,8 +100,8 @@ If in doubt just ask, or look at the examples in the codebase. * More involved type hints can have extra information in the docstring. For example for numpy arrays, ``npt.NDArray[np.bool]`` doesn't render in a readable way in the Code Reference, and doesn't include shape. - Therefore, you can also keep the docstring parameter description e.g., - ``binary ndarray of shape (M, N)``. + Therefore, you can also keep the docstring parameter description with + the shape and dtype information e.g., ``binary ndarray of shape (M, N)``. Incrementing version From 10d89b6f13de11f2d07f39e31a7de4816f21a936 Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell <43960046+PinkShnack@users.noreply.github.com> Date: Fri, 26 Sep 2025 06:59:14 +0000 Subject: [PATCH 20/20] lint: minor linting --- dclab/features/emodulus/pxcorr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dclab/features/emodulus/pxcorr.py b/dclab/features/emodulus/pxcorr.py index 7a44c1b2..8f337eed 100644 --- a/dclab/features/emodulus/pxcorr.py +++ b/dclab/features/emodulus/pxcorr.py @@ -124,10 +124,10 @@ def get_pixelation_delta(feat_corr: str, Corresponding data for `feat_absc` px_um Detector pixel size [µm] - + Returns ------- - For details see :func:`corr_deform_with_area_um` and + For details see :func:`corr_deform_with_area_um` and :func:`corr_deform_with_volume`. """ if feat_corr == "deform" and feat_absc == "area_um":