dclab 0.62.16__cp313-cp313-musllinux_1_2_x86_64.whl → 0.63.1__cp313-cp313-musllinux_1_2_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of dclab might be problematic. Click here for more details.

dclab/__init__.py CHANGED
@@ -1,13 +1,25 @@
1
- """
2
- This library contains classes and methods for the analysis
3
- of real-time deformability cytometry (RT-DC) datasets.
1
+ """Core tools for the analysis of deformability cytometry datasets
2
+
3
+ Copyright (C) 2015 Paul Müller
4
+
5
+ This program is free software; you can redistribute it and/or modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation; either version 2 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License along
16
+ with this program; if not, write to the Free Software Foundation, Inc.,
17
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
4
18
  """
5
19
  # flake8: noqa: F401
6
20
  from . import definitions as dfn
7
21
  from . import features
8
22
  from . import isoelastics
9
- from . import kde_contours
10
- from . import kde_methods
11
23
  from . import lme4
12
24
  from .polygon_filter import PolygonFilter
13
25
  from . import rtdc_dataset
@@ -19,5 +31,11 @@ from .rtdc_dataset.feat_anc_ml import (
19
31
  from .rtdc_dataset.feat_anc_plugin.plugin_feature import (
20
32
  PlugInFeature, load_plugin_feature)
21
33
  from . import statistics
34
+ from . import util
22
35
 
23
36
  from ._version import __version__, __version_tuple__
37
+
38
+
39
+ # Lazy-load deprecated kde modules
40
+ kde_contours = util.LazyLoader("dclab.kde_contours")
41
+ kde_methods = util.LazyLoader("dclab.kde_methods")
dclab/_version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.62.16'
21
- __version_tuple__ = version_tuple = (0, 62, 16)
20
+ __version__ = version = '0.63.1'
21
+ __version_tuple__ = version_tuple = (0, 63, 1)
dclab/kde/__init__.py ADDED
@@ -0,0 +1 @@
1
+ from .base import KernelDensityEstimator # noqa: F401
dclab/kde/base.py ADDED
@@ -0,0 +1,238 @@
1
+ import warnings
2
+
3
+ import numpy as np
4
+
5
+ from .methods import bin_width_doane, get_bad_vals, methods
6
+
7
+
8
+ class KernelDensityEstimator:
9
+ def __init__(self, rtdc_ds):
10
+ self.rtdc_ds = rtdc_ds
11
+
12
+ @staticmethod
13
+ def apply_scale(a, scale, feat):
14
+ """Helper function for transforming an aray to log-scale
15
+
16
+ Parameters
17
+ ----------
18
+ a: np.ndarray
19
+ Input array
20
+ scale: str
21
+ If set to "log", take the logarithm of `a`; if set to
22
+ "linear" return `a` unchanged.
23
+ feat: str
24
+ Feature name (required for debugging)
25
+
26
+ Returns
27
+ -------
28
+ b: np.ndarray
29
+ The scaled array
30
+
31
+ Notes
32
+ -----
33
+ If the scale is not "linear", then a new array is returned.
34
+ All warnings are suppressed when computing `np.log(a)`, as
35
+ `a` may have negative or nan values.
36
+ """
37
+ if scale == "linear":
38
+ b = a
39
+ elif scale == "log":
40
+ with warnings.catch_warnings(record=True) as w:
41
+ warnings.simplefilter("always")
42
+ b = np.log(a)
43
+ if len(w):
44
+ # Tell the user that the log-transformation issued
45
+ # a warning.
46
+ warnings.warn(f"Invalid values encounterd in np.log "
47
+ f"while scaling feature '{feat}'!")
48
+ else:
49
+ raise ValueError(f"`scale` must be either 'linear' or 'log', "
50
+ f"got '{scale}'!")
51
+ return b
52
+
53
+ @staticmethod
54
+ def get_spacing(a, method, scale="linear", method_kw=None,
55
+ feat="undefined", ret_scaled=False):
56
+ """Convenience function for computing the contour spacing
57
+
58
+ Parameters
59
+ ----------
60
+ a: ndarray
61
+ feature data
62
+ scale: str
63
+ how the data should be scaled ("log" or "linear")
64
+ method: callable
65
+ KDE spacing method to use
66
+ method_kw: dict
67
+ keyword arguments to `method`
68
+ feat: str
69
+ feature name for debugging
70
+ ret_scaled: bool
71
+ whether to return the scaled array of `a`
72
+ """
73
+ if method_kw is None:
74
+ method_kw = {}
75
+ # Apply scale (no change for linear scale)
76
+ asc = KernelDensityEstimator.apply_scale(a, scale, feat)
77
+ # Apply multiplicator
78
+ acc = method(asc, **method_kw)
79
+ if ret_scaled:
80
+ return acc, asc
81
+ else:
82
+ return acc
83
+
84
+ def get_contour(self, xax="area_um", yax="deform", xacc=None, yacc=None,
85
+ kde_type="histogram", kde_kwargs=None, xscale="linear",
86
+ yscale="linear"):
87
+ """Evaluate the kernel density estimate for contour plots
88
+
89
+ Parameters
90
+ ----------
91
+ xax: str
92
+ Identifier for X axis (e.g. "area_um", "aspect", "deform")
93
+ yax: str
94
+ Identifier for Y axis
95
+ xacc: float
96
+ Contour accuracy in x direction
97
+ yacc: float
98
+ Contour accuracy in y direction
99
+ kde_type: str
100
+ The KDE method to use
101
+ kde_kwargs: dict
102
+ Additional keyword arguments to the KDE method
103
+ xscale: str
104
+ If set to "log", take the logarithm of the x-values before
105
+ computing the KDE. This is useful when data are
106
+ displayed on a log-scale. Defaults to "linear".
107
+ yscale: str
108
+ See `xscale`.
109
+
110
+ Returns
111
+ -------
112
+ X, Y, Z : coordinates
113
+ The kernel density Z evaluated on a rectangular grid (X,Y).
114
+ """
115
+ if kde_kwargs is None:
116
+ kde_kwargs = {}
117
+ xax = xax.lower()
118
+ yax = yax.lower()
119
+ kde_type = kde_type.lower()
120
+ if kde_type not in methods:
121
+ raise ValueError(f"Not a valid kde type: {kde_type}!")
122
+
123
+ # Get data
124
+ x = self.rtdc_ds[xax][self.rtdc_ds.filter.all]
125
+ y = self.rtdc_ds[yax][self.rtdc_ds.filter.all]
126
+
127
+ xacc_sc, xs = self.get_spacing(
128
+ a=x,
129
+ feat=xax,
130
+ scale=xscale,
131
+ method=bin_width_doane,
132
+ ret_scaled=True)
133
+
134
+ yacc_sc, ys = self.get_spacing(
135
+ a=y,
136
+ feat=yax,
137
+ scale=yscale,
138
+ method=bin_width_doane,
139
+ ret_scaled=True)
140
+
141
+ if xacc is None or xacc == 0:
142
+ xacc = xacc_sc / 5
143
+
144
+ if yacc is None or yacc == 0:
145
+ yacc = yacc_sc / 5
146
+
147
+ # Ignore infs and nans
148
+ bad = get_bad_vals(xs, ys)
149
+ xc = xs[~bad]
150
+ yc = ys[~bad]
151
+
152
+ xnum = int(np.ceil((xc.max() - xc.min()) / xacc))
153
+ ynum = int(np.ceil((yc.max() - yc.min()) / yacc))
154
+
155
+ xlin = np.linspace(xc.min(), xc.max(), xnum, endpoint=True)
156
+ ylin = np.linspace(yc.min(), yc.max(), ynum, endpoint=True)
157
+
158
+ xmesh, ymesh = np.meshgrid(xlin, ylin, indexing="ij")
159
+
160
+ kde_fct = methods[kde_type]
161
+ if len(x):
162
+ density = kde_fct(events_x=xs, events_y=ys,
163
+ xout=xmesh, yout=ymesh,
164
+ **kde_kwargs)
165
+ else:
166
+ density = np.array([])
167
+
168
+ # Convert mesh back to linear scale if applicable
169
+ if xscale == "log":
170
+ xmesh = np.exp(xmesh)
171
+ if yscale == "log":
172
+ ymesh = np.exp(ymesh)
173
+
174
+ return xmesh, ymesh, density
175
+
176
+ def get_scatter(self, xax="area_um", yax="deform", positions=None,
177
+ kde_type="histogram", kde_kwargs=None, xscale="linear",
178
+ yscale="linear"):
179
+ """Evaluate the kernel density estimate for scatter plots
180
+
181
+ Parameters
182
+ ----------
183
+ xax: str
184
+ Identifier for X axis (e.g. "area_um", "aspect", "deform")
185
+ yax: str
186
+ Identifier for Y axis
187
+ positions: list of two 1d ndarrays or ndarray of shape (2, N)
188
+ The positions where the KDE will be computed. Note that
189
+ the KDE estimate is computed from the points that
190
+ are set in `self.rtdc_ds.filter.all`.
191
+ kde_type: str
192
+ The KDE method to use, see :const:`.kde_methods.methods`
193
+ kde_kwargs: dict
194
+ Additional keyword arguments to the KDE method
195
+ xscale: str
196
+ If set to "log", take the logarithm of the x-values before
197
+ computing the KDE. This is useful when data are are
198
+ displayed on a log-scale. Defaults to "linear".
199
+ yscale: str
200
+ See `xscale`.
201
+
202
+ Returns
203
+ -------
204
+ density : 1d ndarray
205
+ The kernel density evaluated for the filtered data points.
206
+ """
207
+ if kde_kwargs is None:
208
+ kde_kwargs = {}
209
+ xax = xax.lower()
210
+ yax = yax.lower()
211
+ kde_type = kde_type.lower()
212
+ if kde_type not in methods:
213
+ raise ValueError(f"Not a valid kde type: {kde_type}!")
214
+
215
+ # Get data
216
+ x = self.rtdc_ds[xax][self.rtdc_ds.filter.all]
217
+ y = self.rtdc_ds[yax][self.rtdc_ds.filter.all]
218
+
219
+ # Apply scale (no change for linear scale)
220
+ xs = self.apply_scale(x, xscale, xax)
221
+ ys = self.apply_scale(y, yscale, yax)
222
+
223
+ if positions is None:
224
+ posx = None
225
+ posy = None
226
+ else:
227
+ posx = self.apply_scale(positions[0], xscale, xax)
228
+ posy = self.apply_scale(positions[1], yscale, yax)
229
+
230
+ kde_fct = methods[kde_type]
231
+ if len(x):
232
+ density = kde_fct(events_x=xs, events_y=ys,
233
+ xout=posx, yout=posy,
234
+ **kde_kwargs)
235
+ else:
236
+ density = np.array([])
237
+
238
+ return density
dclab/kde/contours.py ADDED
@@ -0,0 +1,222 @@
1
+
2
+ import numpy as np
3
+
4
+ from ..external.skimage.measure import find_contours, points_in_poly
5
+ import scipy.interpolate as spint
6
+
7
+ from .methods import get_bad_vals
8
+
9
+
10
+ def find_contours_level(density, x, y, level, closed=False):
11
+ """Find iso-valued density contours for a given level value
12
+
13
+ Parameters
14
+ ----------
15
+ density: 2d ndarray of shape (M, N)
16
+ Kernel density estimate (KDE) for which to compute the contours
17
+ x: 2d ndarray of shape (M, N) or 1d ndarray of size M
18
+ X-values corresponding to `density`
19
+ y: 2d ndarray of shape (M, N) or 1d ndarray of size M
20
+ Y-values corresponding to `density`
21
+ level: float between 0 and 1
22
+ Value along which to find contours in `density` relative
23
+ to its maximum
24
+ closed: bool
25
+ Whether to close contours at the KDE support boundaries
26
+
27
+ Returns
28
+ -------
29
+ contours: list of ndarrays of shape (P, 2)
30
+ Contours found for the given level value
31
+
32
+ See Also
33
+ --------
34
+ skimage.measure.find_contours: Contour finding algorithm used
35
+ """
36
+ if level >= 1 or level <= 0:
37
+ raise ValueError("`level` must be in (0,1), got '{}'!".format(level))
38
+ # level relative to maximum
39
+ level = level * density.max()
40
+ # xy coordinates
41
+ if len(x.shape) == 2:
42
+ assert np.all(x[:, 0] == x[:, 1])
43
+ x = x[:, 0]
44
+ if len(y.shape) == 2:
45
+ assert np.all(y[0, :] == y[1, :])
46
+ y = y[0, :]
47
+ if closed:
48
+ # find closed contours
49
+ density = np.pad(density, ((1, 1), (1, 1)), mode="constant")
50
+ offset = 1
51
+ else:
52
+ # leave contours open at kde boundary
53
+ offset = 0
54
+
55
+ conts_idx = find_contours(density, level)
56
+ conts_xy = []
57
+
58
+ for cc in conts_idx:
59
+ cx = np.interp(x=cc[:, 0]-offset,
60
+ xp=range(x.size),
61
+ fp=x)
62
+ cy = np.interp(x=cc[:, 1]-offset,
63
+ xp=range(y.size),
64
+ fp=y)
65
+ conts_xy.append(np.stack((cx, cy), axis=1))
66
+
67
+ return conts_xy
68
+
69
+
70
+ def get_quantile_levels(density, x, y, xp, yp, q, normalize=True):
71
+ """Compute density levels for given quantiles by interpolation
72
+
73
+ For a given 2D density, compute the density levels at which
74
+ the resulting contours contain the fraction `1-q` of all
75
+ data points. E.g. for a measurement of 1000 events, all
76
+ contours at the level corresponding to a quantile of
77
+ `q=0.95` (95th percentile) contain 50 events (5%).
78
+
79
+ Parameters
80
+ ----------
81
+ density: 2d ndarray of shape (M, N)
82
+ Kernel density estimate for which to compute the contours
83
+ x: 2d ndarray of shape (M, N) or 1d ndarray of size M
84
+ X-values corresponding to `density`
85
+ y: 2d ndarray of shape (M, N) or 1d ndarray of size M
86
+ Y-values corresponding to `density`
87
+ xp: 1d ndarray of size D
88
+ Event x-data from which to compute the quantile
89
+ yp: 1d ndarray of size D
90
+ Event y-data from which to compute the quantile
91
+ q: array_like or float between 0 and 1
92
+ Quantile along which to find contours in `density` relative
93
+ to its maximum
94
+ normalize: bool
95
+ Whether output levels should be normalized to the maximum
96
+ of `density`
97
+
98
+ Returns
99
+ -------
100
+ level: np.ndarray or float
101
+ Contours level(s) corresponding to the given quantile
102
+
103
+ Notes
104
+ -----
105
+ NaN-values events in `xp` and `yp` are ignored.
106
+ """
107
+ # xy coordinates
108
+ if len(x.shape) == 2:
109
+ assert np.all(x[:, 0] == x[:, 1])
110
+ x = x[:, 0]
111
+ if len(y.shape) == 2:
112
+ assert np.all(y[0, :] == y[1, :])
113
+ y = y[0, :]
114
+
115
+ # remove bad events
116
+ bad = get_bad_vals(xp, yp)
117
+ xp = xp[~bad]
118
+ yp = yp[~bad]
119
+
120
+ # Normalize interpolation data such that the spacing for
121
+ # x and y is about the same during interpolation.
122
+ x_norm = x.max()
123
+ x = x / x_norm
124
+ xp = xp / x_norm
125
+
126
+ y_norm = y.max()
127
+ y = y / y_norm
128
+ yp = yp / y_norm
129
+
130
+ # Perform interpolation
131
+ dp = spint.interpn((x, y), density,
132
+ (xp, yp),
133
+ method='linear',
134
+ bounds_error=False,
135
+ fill_value=0)
136
+
137
+ if normalize:
138
+ dp /= density.max()
139
+
140
+ if not np.isscalar(q):
141
+ q = np.array(q)
142
+ plev = np.nanpercentile(dp, q=q*100)
143
+ return plev
144
+
145
+
146
+ def _find_quantile_level(density, x, y, xp, yp, quantile, acc=.01,
147
+ ret_err=False):
148
+ """Find density level for a given data quantile by iteration
149
+
150
+ Parameters
151
+ ----------
152
+ density: 2d ndarray of shape (M, N)
153
+ Kernel density estimate for which to compute the contours
154
+ x: 2d ndarray of shape (M, N) or 1d ndarray of size M
155
+ X-values corresponding to `density`
156
+ y: 2d ndarray of shape (M, N) or 1d ndarray of size M
157
+ Y-values corresponding to `density`
158
+ xp: 1d ndarray of size D
159
+ Event x-data from which to compute the quantile
160
+ yp: 1d ndarray of size D
161
+ Event y-data from which to compute the quantile
162
+ quantile: float between 0 and 1
163
+ Quantile along which to find contours in `density` relative
164
+ to its maximum
165
+ acc: float
166
+ Desired absolute accuracy (stopping criterion) of the
167
+ contours
168
+ ret_err: bool
169
+ If True, also return the absolute error
170
+
171
+ Returns
172
+ -------
173
+ level: float
174
+ Contours level corresponding to the given quantile
175
+
176
+ Notes
177
+ -----
178
+ A much more faster method (using interpolation) is implemented in
179
+ :func:`get_quantile_levels`.
180
+ NaN-values events in `xp` and `yp` are ignored.
181
+
182
+ See Also
183
+ --------
184
+ skimage.measure.find_contours: Contour finding algorithm
185
+ """
186
+ if quantile >= 1 or quantile <= 0:
187
+ raise ValueError("Invalid value for `quantile`: {}".format(quantile))
188
+
189
+ # remove bad events
190
+ bad = get_bad_vals(xp, yp)
191
+ xp = xp[~bad]
192
+ yp = yp[~bad]
193
+ points = np.concatenate((xp.reshape(-1, 1), yp.reshape(-1, 1)), axis=1)
194
+
195
+ # initial guess
196
+ level = quantile
197
+ # error of current iteration
198
+ err = 1
199
+ # iteration factor (guarantees convergence)
200
+ itfac = 1
201
+ # total number of events
202
+ nev = xp.size
203
+
204
+ while np.abs(err) > acc:
205
+ # compute contours
206
+ conts = find_contours_level(density, x, y, level, closed=True)
207
+ # compute number of points in contour
208
+ isin = 0
209
+ pi = np.array(points, copy=True)
210
+ for cc in conts:
211
+ pinc = points_in_poly(points=pi, verts=cc)
212
+ isin += np.sum(pinc)
213
+ # ignore these points for the other contours
214
+ pi = pi[~pinc]
215
+ err = quantile - (nev - isin) / nev
216
+ level += err * itfac
217
+ itfac *= .9
218
+
219
+ if ret_err:
220
+ return level, err
221
+ else:
222
+ return level