dclab 0.67.0__cp314-cp314-macosx_11_0_arm64.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.

Files changed (142) hide show
  1. dclab/__init__.py +41 -0
  2. dclab/_version.py +34 -0
  3. dclab/cached.py +97 -0
  4. dclab/cli/__init__.py +10 -0
  5. dclab/cli/common.py +237 -0
  6. dclab/cli/task_compress.py +126 -0
  7. dclab/cli/task_condense.py +223 -0
  8. dclab/cli/task_join.py +229 -0
  9. dclab/cli/task_repack.py +98 -0
  10. dclab/cli/task_split.py +154 -0
  11. dclab/cli/task_tdms2rtdc.py +186 -0
  12. dclab/cli/task_verify_dataset.py +75 -0
  13. dclab/definitions/__init__.py +79 -0
  14. dclab/definitions/feat_const.py +202 -0
  15. dclab/definitions/feat_logic.py +182 -0
  16. dclab/definitions/meta_const.py +252 -0
  17. dclab/definitions/meta_logic.py +111 -0
  18. dclab/definitions/meta_parse.py +94 -0
  19. dclab/downsampling.cpython-314-darwin.so +0 -0
  20. dclab/downsampling.pyx +230 -0
  21. dclab/external/__init__.py +4 -0
  22. dclab/external/packaging/LICENSE +3 -0
  23. dclab/external/packaging/LICENSE.APACHE +177 -0
  24. dclab/external/packaging/LICENSE.BSD +23 -0
  25. dclab/external/packaging/__init__.py +6 -0
  26. dclab/external/packaging/_structures.py +61 -0
  27. dclab/external/packaging/version.py +505 -0
  28. dclab/external/skimage/LICENSE +28 -0
  29. dclab/external/skimage/__init__.py +2 -0
  30. dclab/external/skimage/_find_contours.py +216 -0
  31. dclab/external/skimage/_find_contours_cy.cpython-314-darwin.so +0 -0
  32. dclab/external/skimage/_find_contours_cy.pyx +188 -0
  33. dclab/external/skimage/_pnpoly.cpython-314-darwin.so +0 -0
  34. dclab/external/skimage/_pnpoly.pyx +99 -0
  35. dclab/external/skimage/_shared/__init__.py +1 -0
  36. dclab/external/skimage/_shared/geometry.cpython-314-darwin.so +0 -0
  37. dclab/external/skimage/_shared/geometry.pxd +6 -0
  38. dclab/external/skimage/_shared/geometry.pyx +55 -0
  39. dclab/external/skimage/measure.py +7 -0
  40. dclab/external/skimage/pnpoly.py +53 -0
  41. dclab/external/statsmodels/LICENSE +35 -0
  42. dclab/external/statsmodels/__init__.py +6 -0
  43. dclab/external/statsmodels/nonparametric/__init__.py +1 -0
  44. dclab/external/statsmodels/nonparametric/_kernel_base.py +203 -0
  45. dclab/external/statsmodels/nonparametric/kernel_density.py +165 -0
  46. dclab/external/statsmodels/nonparametric/kernels.py +36 -0
  47. dclab/features/__init__.py +9 -0
  48. dclab/features/bright.py +81 -0
  49. dclab/features/bright_bc.py +93 -0
  50. dclab/features/bright_perc.py +63 -0
  51. dclab/features/contour.py +161 -0
  52. dclab/features/emodulus/__init__.py +339 -0
  53. dclab/features/emodulus/load.py +252 -0
  54. dclab/features/emodulus/lut_HE-2D-FEM-22.txt +16432 -0
  55. dclab/features/emodulus/lut_HE-3D-FEM-22.txt +1276 -0
  56. dclab/features/emodulus/lut_LE-2D-FEM-19.txt +13082 -0
  57. dclab/features/emodulus/pxcorr.py +135 -0
  58. dclab/features/emodulus/scale_linear.py +247 -0
  59. dclab/features/emodulus/viscosity.py +260 -0
  60. dclab/features/fl_crosstalk.py +95 -0
  61. dclab/features/inert_ratio.py +377 -0
  62. dclab/features/volume.py +242 -0
  63. dclab/http_utils.py +322 -0
  64. dclab/isoelastics/__init__.py +468 -0
  65. dclab/isoelastics/iso_HE-2D-FEM-22-area_um-deform.txt +2440 -0
  66. dclab/isoelastics/iso_HE-2D-FEM-22-volume-deform.txt +2635 -0
  67. dclab/isoelastics/iso_HE-3D-FEM-22-area_um-deform.txt +1930 -0
  68. dclab/isoelastics/iso_HE-3D-FEM-22-volume-deform.txt +2221 -0
  69. dclab/isoelastics/iso_LE-2D-FEM-19-area_um-deform.txt +2151 -0
  70. dclab/isoelastics/iso_LE-2D-FEM-19-volume-deform.txt +2250 -0
  71. dclab/isoelastics/iso_LE-2D-ana-18-area_um-deform.txt +1266 -0
  72. dclab/kde/__init__.py +1 -0
  73. dclab/kde/base.py +459 -0
  74. dclab/kde/contours.py +222 -0
  75. dclab/kde/methods.py +313 -0
  76. dclab/kde_contours.py +10 -0
  77. dclab/kde_methods.py +11 -0
  78. dclab/lme4/__init__.py +5 -0
  79. dclab/lme4/lme4_template.R +94 -0
  80. dclab/lme4/rsetup.py +204 -0
  81. dclab/lme4/wrapr.py +386 -0
  82. dclab/polygon_filter.py +398 -0
  83. dclab/rtdc_dataset/__init__.py +15 -0
  84. dclab/rtdc_dataset/check.py +902 -0
  85. dclab/rtdc_dataset/config.py +533 -0
  86. dclab/rtdc_dataset/copier.py +353 -0
  87. dclab/rtdc_dataset/core.py +896 -0
  88. dclab/rtdc_dataset/export.py +867 -0
  89. dclab/rtdc_dataset/feat_anc_core/__init__.py +24 -0
  90. dclab/rtdc_dataset/feat_anc_core/af_basic.py +75 -0
  91. dclab/rtdc_dataset/feat_anc_core/af_emodulus.py +160 -0
  92. dclab/rtdc_dataset/feat_anc_core/af_fl_max_ctc.py +133 -0
  93. dclab/rtdc_dataset/feat_anc_core/af_image_contour.py +113 -0
  94. dclab/rtdc_dataset/feat_anc_core/af_ml_class.py +102 -0
  95. dclab/rtdc_dataset/feat_anc_core/ancillary_feature.py +320 -0
  96. dclab/rtdc_dataset/feat_anc_ml/__init__.py +32 -0
  97. dclab/rtdc_dataset/feat_anc_plugin/__init__.py +3 -0
  98. dclab/rtdc_dataset/feat_anc_plugin/plugin_feature.py +329 -0
  99. dclab/rtdc_dataset/feat_basin.py +762 -0
  100. dclab/rtdc_dataset/feat_temp.py +102 -0
  101. dclab/rtdc_dataset/filter.py +263 -0
  102. dclab/rtdc_dataset/fmt_dcor/__init__.py +7 -0
  103. dclab/rtdc_dataset/fmt_dcor/access_token.py +52 -0
  104. dclab/rtdc_dataset/fmt_dcor/api.py +173 -0
  105. dclab/rtdc_dataset/fmt_dcor/base.py +299 -0
  106. dclab/rtdc_dataset/fmt_dcor/basin.py +73 -0
  107. dclab/rtdc_dataset/fmt_dcor/logs.py +26 -0
  108. dclab/rtdc_dataset/fmt_dcor/tables.py +66 -0
  109. dclab/rtdc_dataset/fmt_dict.py +103 -0
  110. dclab/rtdc_dataset/fmt_hdf5/__init__.py +6 -0
  111. dclab/rtdc_dataset/fmt_hdf5/base.py +192 -0
  112. dclab/rtdc_dataset/fmt_hdf5/basin.py +30 -0
  113. dclab/rtdc_dataset/fmt_hdf5/events.py +276 -0
  114. dclab/rtdc_dataset/fmt_hdf5/feat_defect.py +164 -0
  115. dclab/rtdc_dataset/fmt_hdf5/logs.py +33 -0
  116. dclab/rtdc_dataset/fmt_hdf5/tables.py +60 -0
  117. dclab/rtdc_dataset/fmt_hierarchy/__init__.py +11 -0
  118. dclab/rtdc_dataset/fmt_hierarchy/base.py +278 -0
  119. dclab/rtdc_dataset/fmt_hierarchy/events.py +146 -0
  120. dclab/rtdc_dataset/fmt_hierarchy/hfilter.py +140 -0
  121. dclab/rtdc_dataset/fmt_hierarchy/mapper.py +134 -0
  122. dclab/rtdc_dataset/fmt_http.py +102 -0
  123. dclab/rtdc_dataset/fmt_s3.py +354 -0
  124. dclab/rtdc_dataset/fmt_tdms/__init__.py +476 -0
  125. dclab/rtdc_dataset/fmt_tdms/event_contour.py +264 -0
  126. dclab/rtdc_dataset/fmt_tdms/event_image.py +220 -0
  127. dclab/rtdc_dataset/fmt_tdms/event_mask.py +62 -0
  128. dclab/rtdc_dataset/fmt_tdms/event_trace.py +146 -0
  129. dclab/rtdc_dataset/fmt_tdms/exc.py +37 -0
  130. dclab/rtdc_dataset/fmt_tdms/naming.py +151 -0
  131. dclab/rtdc_dataset/load.py +77 -0
  132. dclab/rtdc_dataset/meta_table.py +25 -0
  133. dclab/rtdc_dataset/writer.py +1019 -0
  134. dclab/statistics.py +226 -0
  135. dclab/util.py +176 -0
  136. dclab/warn.py +15 -0
  137. dclab-0.67.0.dist-info/METADATA +153 -0
  138. dclab-0.67.0.dist-info/RECORD +142 -0
  139. dclab-0.67.0.dist-info/WHEEL +6 -0
  140. dclab-0.67.0.dist-info/entry_points.txt +8 -0
  141. dclab-0.67.0.dist-info/licenses/LICENSE +283 -0
  142. dclab-0.67.0.dist-info/top_level.txt +1 -0
dclab/kde/__init__.py ADDED
@@ -0,0 +1 @@
1
+ from .base import KernelDensityEstimator # noqa: F401
dclab/kde/base.py ADDED
@@ -0,0 +1,459 @@
1
+ import warnings
2
+
3
+ import numpy as np
4
+ from scipy.interpolate import RegularGridInterpolator as RGI
5
+
6
+ from .methods import bin_width_doane_div5, get_bad_vals, methods
7
+ from .contours import find_contours_level, get_quantile_levels
8
+
9
+
10
+ class ContourSpacingTooLarge(UserWarning):
11
+ pass
12
+
13
+
14
+ class KernelDensityEstimator:
15
+ def __init__(self, rtdc_ds):
16
+ self.rtdc_ds = rtdc_ds
17
+
18
+ @staticmethod
19
+ def apply_scale(a, scale, feat):
20
+ """Helper function for transforming an aray to log-scale
21
+
22
+ Parameters
23
+ ----------
24
+ a: np.ndarray
25
+ Input array
26
+ scale: str
27
+ If set to "log", take the logarithm of `a`; if set to
28
+ "linear" return `a` unchanged.
29
+ feat: str
30
+ Feature name (required for debugging)
31
+
32
+ Returns
33
+ -------
34
+ b: np.ndarray
35
+ The scaled array
36
+
37
+ Notes
38
+ -----
39
+ If the scale is not "linear", then a new array is returned.
40
+ All warnings are suppressed when computing `np.log(a)`, as
41
+ `a` may have negative or nan values.
42
+ """
43
+ if scale == "linear":
44
+ b = a
45
+ elif scale == "log":
46
+ with warnings.catch_warnings(record=True) as w:
47
+ warnings.simplefilter("always")
48
+ b = np.log(a)
49
+ if len(w):
50
+ # Tell the user that the log-transformation issued
51
+ # a warning.
52
+ warnings.warn(f"Invalid values encounterd in np.log "
53
+ f"while scaling feature '{feat}'!")
54
+ else:
55
+ raise ValueError(f"`scale` must be either 'linear' or 'log', "
56
+ f"got '{scale}'!")
57
+ return b
58
+
59
+ @staticmethod
60
+ def get_spacing(a, method, scale="linear", method_kw=None,
61
+ feat="undefined", ret_scaled=False):
62
+ """Convenience function for computing the contour spacing
63
+
64
+ Parameters
65
+ ----------
66
+ a: ndarray
67
+ feature data
68
+ scale: str
69
+ how the data should be scaled ("log" or "linear")
70
+ method: callable
71
+ KDE spacing method to use
72
+ method_kw: dict
73
+ keyword arguments to `method`
74
+ feat: str
75
+ feature name for debugging
76
+ ret_scaled: bool
77
+ whether to return the scaled array of `a`
78
+ """
79
+ if method_kw is None:
80
+ method_kw = {}
81
+ # Apply scale (no change for linear scale)
82
+ asc = KernelDensityEstimator.apply_scale(a, scale, feat)
83
+ # Apply multiplicator
84
+ acc = method(asc, **method_kw)
85
+ if ret_scaled:
86
+ return acc, asc
87
+ else:
88
+ return acc
89
+
90
+ def get_contour(self, xax="area_um", yax="deform", xacc=None, yacc=None,
91
+ kde_type="histogram", kde_kwargs=None, xscale="linear",
92
+ yscale="linear"):
93
+ """Evaluate the kernel density estimate for contour plots
94
+
95
+ Parameters
96
+ ----------
97
+ xax: str
98
+ Identifier for X axis (e.g. "area_um", "aspect", "deform")
99
+ yax: str
100
+ Identifier for Y axis
101
+ xacc: float
102
+ Contour accuracy in x direction
103
+ yacc: float
104
+ Contour accuracy in y direction
105
+ kde_type: str
106
+ The KDE method to use
107
+ kde_kwargs: dict
108
+ Additional keyword arguments to the KDE method
109
+ xscale: str
110
+ If set to "log", take the logarithm of the x-values before
111
+ computing the KDE. This is useful when data are
112
+ displayed on a log-scale. Defaults to "linear".
113
+ yscale: str
114
+ See `xscale`.
115
+
116
+ Returns
117
+ -------
118
+ X, Y, Z : coordinates
119
+ The kernel density Z evaluated on a rectangular grid (X,Y).
120
+ """
121
+ warnings.warn("`get_contour` is deprecated; please use "
122
+ "`get_raster` instead", DeprecationWarning)
123
+ return self.get_raster(
124
+ xax=xax, yax=yax, xacc=xacc, yacc=yacc,
125
+ kde_type=kde_type, kde_kwargs=kde_kwargs,
126
+ xscale=xscale, yscale=yscale
127
+ )
128
+
129
+ def get_contour_lines(self, quantiles=None, xax="area_um", yax="deform",
130
+ xacc=None, yacc=None, kde_type="histogram",
131
+ kde_kwargs=None, xscale="linear", yscale="linear",
132
+ ret_levels=False):
133
+ """Compute contour lines for a given kernel kensity estimate.
134
+
135
+ Parameters
136
+ ----------
137
+ quantiles: list or array of floats
138
+ KDE Quantiles for which contour levels are computed. The
139
+ values must be between 0 and 1. If set to None, use
140
+ [0.5, 0.95] as default.
141
+ xax: str
142
+ Identifier for X axis (e.g. "area_um", "aspect", "deform")
143
+ yax: str
144
+ Identifier for Y axis
145
+ xacc: float
146
+ Contour accuracy in x direction
147
+ if set to None, will use :func:`bin_width_doane_div5`
148
+ yacc: float
149
+ Contour accuracy in y direction
150
+ if set to None, will use :func:`bin_width_doane_div5`
151
+ kde_type: str
152
+ The KDE method to use
153
+ kde_kwargs: dict
154
+ Additional keyword arguments to the KDE method
155
+ xscale: str
156
+ If set to "log", take the logarithm of the x-values before
157
+ computing the KDE. This is useful when data are
158
+ displayed on a log-scale. Defaults to "linear".
159
+ yscale: str
160
+ See `xscale`
161
+ ret_levels: bool
162
+ If set to True, return the levels of the contours
163
+ (default: False)
164
+
165
+ Returns
166
+ -------
167
+ contour_lines: list of lists (of lists)
168
+ For every number in `quantiles`, this list contains a list of
169
+ corresponding contour lines. Each contour line is a 2D
170
+ array of shape (N, 2), where N is the number of points in the
171
+ contour line.
172
+ levels: list of floats
173
+ The density levels corresponding to each number in `quantiles`.
174
+ Only returned if `ret_levels` is set to True.
175
+ """
176
+ if not quantiles:
177
+ quantiles = [0.5, 0.95]
178
+ try:
179
+ x, y, density = self.get_raster(
180
+ xax=xax,
181
+ yax=yax,
182
+ xacc=xacc,
183
+ yacc=yacc,
184
+ xscale=xscale,
185
+ yscale=yscale,
186
+ kde_type=kde_type,
187
+ kde_kwargs=kde_kwargs,
188
+ )
189
+ except ValueError:
190
+ # most-likely there is nothing to compute a contour for
191
+ return []
192
+ if density.shape[0] < 3 or density.shape[1] < 3:
193
+ warnings.warn("Contour not possible; spacing may be too large!",
194
+ ContourSpacingTooLarge)
195
+ return []
196
+ levels = get_quantile_levels(
197
+ density=density,
198
+ x=x,
199
+ y=y,
200
+ xp=self.rtdc_ds[xax][self.rtdc_ds.filter.all],
201
+ yp=self.rtdc_ds[yax][self.rtdc_ds.filter.all],
202
+ q=np.array(quantiles),
203
+ normalize=False)
204
+ contours = []
205
+ # Normalize levels to [0, 1]
206
+ nlevels = np.array(levels) / density.max()
207
+ for nlev in nlevels:
208
+ # make sure that the contour levels are not at the boundaries
209
+ if not (np.allclose(nlev, 0, atol=1e-12, rtol=0)
210
+ or np.allclose(nlev, 1, atol=1e-12, rtol=0)):
211
+ cc = find_contours_level(
212
+ density, x=x, y=y, level=nlev)
213
+ contours.append(cc)
214
+ else:
215
+ contours.append([])
216
+ if ret_levels:
217
+ return contours, levels
218
+ else:
219
+ return contours
220
+
221
+ def get_raster(self, xax="area_um", yax="deform", xacc=None, yacc=None,
222
+ kde_type="histogram", kde_kwargs=None, xscale="linear",
223
+ yscale="linear"):
224
+ """Evaluate the kernel density estimate on a grid
225
+
226
+ Parameters
227
+ ----------
228
+ xax: str
229
+ Identifier for X axis (e.g. "area_um", "aspect", "deform")
230
+ yax: str
231
+ Identifier for Y axis
232
+ xacc: float
233
+ Contour accuracy in x direction
234
+ if set to None, will use :func:`bin_width_doane_div5`
235
+ yacc: float
236
+ Contour accuracy in y direction
237
+ if set to None, will use :func:`bin_width_doane_div5`
238
+ kde_type: str
239
+ The KDE method to use
240
+ kde_kwargs: dict
241
+ Additional keyword arguments to the KDE method
242
+ xscale: str
243
+ If set to "log", take the logarithm of the x-values before
244
+ computing the KDE. This is useful when data are
245
+ displayed on a log-scale. Defaults to "linear".
246
+ yscale: str
247
+ See `xscale`.
248
+
249
+ Returns
250
+ -------
251
+ X, Y, Z : coordinates
252
+ The kernel density Z evaluated on a rectangular grid (X,Y).
253
+ """
254
+ if kde_kwargs is None:
255
+ kde_kwargs = {}
256
+ xax = xax.lower()
257
+ yax = yax.lower()
258
+ kde_type = kde_type.lower()
259
+ if kde_type not in methods:
260
+ raise ValueError(f"Not a valid kde type: {kde_type}!")
261
+
262
+ # Get data
263
+ x = self.rtdc_ds[xax][self.rtdc_ds.filter.all]
264
+ y = self.rtdc_ds[yax][self.rtdc_ds.filter.all]
265
+
266
+ xacc_sc, xs = self.get_spacing(
267
+ a=x,
268
+ feat=xax,
269
+ scale=xscale,
270
+ method=bin_width_doane_div5,
271
+ ret_scaled=True)
272
+
273
+ yacc_sc, ys = self.get_spacing(
274
+ a=y,
275
+ feat=yax,
276
+ scale=yscale,
277
+ method=bin_width_doane_div5,
278
+ ret_scaled=True)
279
+
280
+ if xacc is None or xacc == 0:
281
+ xacc = xacc_sc
282
+
283
+ if yacc is None or yacc == 0:
284
+ yacc = yacc_sc
285
+
286
+ # Ignore infs and nans
287
+ bad = get_bad_vals(xs, ys)
288
+ xc = xs[~bad]
289
+ yc = ys[~bad]
290
+
291
+ xnum = int(np.ceil((xc.max() - xc.min()) / xacc))
292
+ ynum = int(np.ceil((yc.max() - yc.min()) / yacc))
293
+
294
+ xlin = np.linspace(xc.min(), xc.max(), xnum, endpoint=True)
295
+ ylin = np.linspace(yc.min(), yc.max(), ynum, endpoint=True)
296
+
297
+ xmesh, ymesh = np.meshgrid(xlin, ylin, indexing="ij")
298
+
299
+ kde_fct = methods[kde_type]
300
+ if len(x):
301
+ density = kde_fct(events_x=xs, events_y=ys,
302
+ xout=xmesh, yout=ymesh,
303
+ **kde_kwargs)
304
+ else:
305
+ density = np.array([])
306
+
307
+ # Convert mesh back to linear scale if applicable
308
+ if xscale == "log":
309
+ xmesh = np.exp(xmesh)
310
+ if yscale == "log":
311
+ ymesh = np.exp(ymesh)
312
+
313
+ return xmesh, ymesh, density
314
+
315
+ def get_scatter(self, xax="area_um", yax="deform", positions=None,
316
+ kde_type="histogram", kde_kwargs=None, xscale="linear",
317
+ yscale="linear"):
318
+ """Evaluate the kernel density estimate for scatter plots
319
+
320
+ The KDE is evaluated with the `kde_type` function for every point.
321
+
322
+ Parameters
323
+ ----------
324
+ xax: str
325
+ Identifier for X axis (e.g. "area_um", "aspect", "deform")
326
+ yax: str
327
+ Identifier for Y axis
328
+ positions: list of two 1d ndarrays or ndarray of shape (2, N)
329
+ The positions where the KDE will be computed. Note that
330
+ the KDE estimate is computed from the points that
331
+ are set in `self.rtdc_ds.filter.all`.
332
+ kde_type: str
333
+ The KDE method to use, see :const:`.kde_methods.methods`
334
+ kde_kwargs: dict
335
+ Additional keyword arguments to the KDE method
336
+ xscale: str
337
+ If set to "log", take the logarithm of the x-values before
338
+ computing the KDE. This is useful when data are
339
+ displayed on a log-scale. Defaults to "linear".
340
+ yscale: str
341
+ See `xscale`.
342
+
343
+ Returns
344
+ -------
345
+ density : 1d ndarray
346
+ The kernel density evaluated for the filtered data points.
347
+ """
348
+ if kde_kwargs is None:
349
+ kde_kwargs = {}
350
+ xax = xax.lower()
351
+ yax = yax.lower()
352
+ kde_type = kde_type.lower()
353
+ if kde_type not in methods:
354
+ raise ValueError(f"Not a valid kde type: {kde_type}!")
355
+
356
+ # Get data
357
+ x = self.rtdc_ds[xax][self.rtdc_ds.filter.all]
358
+ y = self.rtdc_ds[yax][self.rtdc_ds.filter.all]
359
+
360
+ # Apply scale (no change for linear scale)
361
+ xs = self.apply_scale(x, xscale, xax)
362
+ ys = self.apply_scale(y, yscale, yax)
363
+
364
+ if positions is None:
365
+ posx = None
366
+ posy = None
367
+ else:
368
+ posx = self.apply_scale(positions[0], xscale, xax)
369
+ posy = self.apply_scale(positions[1], yscale, yax)
370
+
371
+ kde_fct = methods[kde_type]
372
+ if len(x):
373
+ density = kde_fct(events_x=xs, events_y=ys,
374
+ xout=posx, yout=posy,
375
+ **kde_kwargs)
376
+ else:
377
+ density = np.array([])
378
+
379
+ return density
380
+
381
+ def get_at(self, xax="area_um", yax="deform", positions=None,
382
+ kde_type="histogram", kde_kwargs=None, xscale="linear",
383
+ yscale="linear"):
384
+ """Evaluate the kernel density estimate for specific events
385
+
386
+ The KDE is computed via linear interpolation from the output
387
+ of `get_raster`.
388
+
389
+ Parameters
390
+ ----------
391
+ xax: str
392
+ Identifier for X axis (e.g. "area_um", "aspect", "deform")
393
+ yax: str
394
+ Identifier for Y axis
395
+ positions: list of two 1d ndarrays or ndarray of shape (2, N)
396
+ The positions where the KDE will be computed. Note that
397
+ the KDE estimate is computed from the points that
398
+ are set in `self.rtdc_ds.filter.all`.
399
+ kde_type: str
400
+ The KDE method to use, see :const:`.kde_methods.methods`
401
+ kde_kwargs: dict
402
+ Additional keyword arguments to the KDE method
403
+ xscale: str
404
+ If set to "log", take the logarithm of the x-values before
405
+ computing the KDE. This is useful when data are
406
+ displayed on a log-scale. Defaults to "linear".
407
+ yscale: str
408
+ See `xscale`.
409
+
410
+ Returns
411
+ -------
412
+ density : 1d ndarray
413
+ The kernel density evaluated for the filtered events.
414
+ """
415
+ if kde_kwargs is None:
416
+ kde_kwargs = {}
417
+ xax = xax.lower()
418
+ yax = yax.lower()
419
+ kde_type = kde_type.lower()
420
+ if kde_type not in methods:
421
+ raise ValueError(f"Not a valid kde type: {kde_type}!")
422
+
423
+ # Get data
424
+ x = self.rtdc_ds[xax][self.rtdc_ds.filter.all]
425
+ y = self.rtdc_ds[yax][self.rtdc_ds.filter.all]
426
+
427
+ # Apply scale (no change for linear scale)
428
+ xs = self.apply_scale(x, xscale, xax)
429
+ ys = self.apply_scale(y, yscale, yax)
430
+
431
+ if positions:
432
+ xs = self.apply_scale(positions[0], xscale, xax)
433
+ ys = self.apply_scale(positions[1], yscale, yax)
434
+
435
+ if len(x):
436
+ xr, yr, density_grid = self.get_raster(xax=xax,
437
+ yax=yax,
438
+ kde_type=kde_type,
439
+ kde_kwargs=kde_kwargs,
440
+ xscale=xscale,
441
+ yscale=yscale)
442
+
443
+ # Apply scale (no change for linear scale)
444
+ xrs = self.apply_scale(xr, xscale, xax)
445
+ yrs = self.apply_scale(yr, yscale, yax)
446
+
447
+ # 'scipy.interp2d' has been removed in SciPy 1.14.0
448
+ # https://scipy.github.io/devdocs/tutorial/interpolate/interp_transition_guide.html
449
+ interp_func = RGI((xrs[:, 0], yrs[0, :]),
450
+ density_grid,
451
+ method="linear",
452
+ bounds_error=False,
453
+ fill_value=np.nan)
454
+ density = interp_func((xs, ys))
455
+
456
+ else:
457
+ density = np.array([])
458
+
459
+ 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