dclab 0.62.16__cp313-cp313-macosx_10_13_x86_64.whl → 0.63.0__cp313-cp313-macosx_10_13_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 +23 -5
- dclab/_version.py +2 -2
- dclab/downsampling.cpython-313-darwin.so +0 -0
- dclab/external/skimage/_find_contours_cy.cpython-313-darwin.so +0 -0
- dclab/external/skimage/_pnpoly.cpython-313-darwin.so +0 -0
- dclab/external/skimage/_shared/geometry.cpython-313-darwin.so +0 -0
- dclab/kde/__init__.py +1 -0
- dclab/kde/base.py +238 -0
- dclab/kde/contours.py +222 -0
- dclab/kde/methods.py +303 -0
- dclab/kde_contours.py +7 -219
- dclab/kde_methods.py +9 -301
- dclab/rtdc_dataset/core.py +30 -146
- dclab/rtdc_dataset/export.py +129 -37
- dclab/util.py +20 -0
- {dclab-0.62.16.dist-info → dclab-0.63.0.dist-info}/METADATA +4 -4
- {dclab-0.62.16.dist-info → dclab-0.63.0.dist-info}/RECORD +21 -17
- {dclab-0.62.16.dist-info → dclab-0.63.0.dist-info}/WHEEL +1 -1
- {dclab-0.62.16.dist-info → dclab-0.63.0.dist-info}/licenses/LICENSE +1 -61
- {dclab-0.62.16.dist-info → dclab-0.63.0.dist-info}/entry_points.txt +0 -0
- {dclab-0.62.16.dist-info → dclab-0.63.0.dist-info}/top_level.txt +0 -0
dclab/__init__.py
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
|
|
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
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
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
|