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

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-314t-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-314t-darwin.so +0 -0
  32. dclab/external/skimage/_find_contours_cy.pyx +188 -0
  33. dclab/external/skimage/_pnpoly.cpython-314t-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-314t-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
@@ -0,0 +1,95 @@
1
+ """Crosstalk-correction for fluorescence data"""
2
+
3
+ import numpy as np
4
+
5
+
6
+ def get_compensation_matrix(ct21, ct31, ct12, ct32, ct13, ct23):
7
+ """Compute crosstalk inversion matrix
8
+
9
+ The spillover matrix is
10
+
11
+ | | c11 c12 c13 |
12
+ | | c21 c22 c23 |
13
+ | | c31 c32 c33 |
14
+
15
+ The diagonal elements are set to 1, i.e.
16
+
17
+ ct11 = c22 = c33 = 1
18
+
19
+ Parameters
20
+ ----------
21
+ cij: float
22
+ Spill from channel i to channel j
23
+
24
+ Returns
25
+ -------
26
+ inv: np.ndarray
27
+ Compensation matrix (inverted spillover matrix)
28
+ """
29
+ ct11 = 1
30
+ ct22 = 1
31
+ ct33 = 1
32
+
33
+ if ct21 < 0:
34
+ raise ValueError("ct21 matrix element must not be negative!")
35
+
36
+ if ct31 < 0:
37
+ raise ValueError("ct31 matrix element must not be negative!")
38
+
39
+ if ct12 < 0:
40
+ raise ValueError("ct12 matrix element must not be negative!")
41
+
42
+ if ct32 < 0:
43
+ raise ValueError("ct32 matrix element must not be negative!")
44
+
45
+ if ct13 < 0:
46
+ raise ValueError("ct13 matrix element must not be negative!")
47
+
48
+ if ct23 < 0:
49
+ raise ValueError("ct23 matrix element must not be negative!")
50
+
51
+ crosstalk = np.array([[ct11, ct12, ct13],
52
+ [ct21, ct22, ct23],
53
+ [ct31, ct32, ct33],
54
+ ])
55
+ return np.linalg.inv(crosstalk)
56
+
57
+
58
+ def correct_crosstalk(fl1, fl2, fl3, fl_channel,
59
+ ct21=0, ct31=0, ct12=0, ct32=0, ct13=0, ct23=0):
60
+ """Perform crosstalk correction
61
+
62
+ Parameters
63
+ ----------
64
+ fli: int, float, or np.ndarray
65
+ Measured fluorescence signals
66
+ fl_channel: int (1, 2, or 3)
67
+ The channel number for which the crosstalk-corrected signal
68
+ should be computed
69
+ cij: float
70
+ Spill (crosstalk or bleed-through) from channel i to channel j
71
+ This spill is computed from the fluorescence signal of e.g.
72
+ single-stained positive control cells; It is defined by the
73
+ ratio of the fluorescence signals of the two channels, i.e
74
+ cij = flj / fli.
75
+
76
+ See Also
77
+ --------
78
+ get_compensation_matrix: compute the inverse crosstalk matrix
79
+
80
+ Notes
81
+ -----
82
+ If there are only two channels (e.g. fl1 and fl2), then the
83
+ crosstalk to and from the other channel (ct31, ct32, ct13, ct23)
84
+ should be set to zero.
85
+ """
86
+ fl_channel = int(fl_channel)
87
+ if fl_channel not in [1, 2, 3]:
88
+ raise ValueError("`fl_channel` must be 1, 2, or 3!")
89
+
90
+ minv = get_compensation_matrix(ct21=ct21, ct31=ct31, ct12=ct12,
91
+ ct32=ct32, ct13=ct13, ct23=ct23)
92
+
93
+ col = minv[:, fl_channel - 1].flatten()
94
+ flout = col[0] * fl1 + col[1] * fl2 + col[2] * fl3
95
+ return flout
@@ -0,0 +1,377 @@
1
+ """Computation of inertia ratio from contour data"""
2
+ import numpy as np
3
+ import scipy.spatial as ssp
4
+
5
+
6
+ def cont_moments_cv(cont,
7
+ flt_epsilon=1.19209e-07,
8
+ dbl_epsilon=2.2204460492503131e-16):
9
+ """Compute the moments of a contour
10
+
11
+ The moments are computed in the same way as they are computed
12
+ in OpenCV's `contourMoments` in `moments.cpp`.
13
+
14
+ Parameters
15
+ ----------
16
+ cont: array of shape (N,2)
17
+ The contour for which to compute the moments.
18
+ flt_epsilon: float
19
+ The value of ``FLT_EPSILON`` in OpenCV/gcc.
20
+ dbl_epsilon: float
21
+ The value of ``DBL_EPSILON`` in OpenCV/gcc.
22
+
23
+ .. versionchanged:: 0.48.2
24
+
25
+ For long channels, an integer overflow could occur in previous
26
+ versions, leading to false contour moments.
27
+ See https://github.com/DC-analysis/dclab/issues/212
28
+
29
+ Returns
30
+ -------
31
+ moments: dict
32
+ A dictionary of moments. If the moment `m00` is smaller
33
+ than half of `flt_epsilon`, `None` is returned.
34
+ """
35
+ # Make sure we have 64bit integer or floating point values.
36
+ # If the input data was int32, then integer overflows could occur
37
+ # for mu20 and mu02 (https://github.com/DC-analysis/dclab/issues/212).
38
+ if np.issubdtype(cont.dtype, np.integer):
39
+ cont = cont.astype(np.int64)
40
+ elif np.issubdtype(cont.dtype, np.floating):
41
+ cont = cont.astype(np.float64)
42
+
43
+ xi = cont[:, 0]
44
+ yi = cont[:, 1]
45
+
46
+ xi_1 = np.roll(xi, -1)
47
+ yi_1 = np.roll(yi, -1)
48
+
49
+ xi_12 = xi_1**2
50
+ yi_12 = yi_1**2
51
+
52
+ xi2 = xi**2
53
+ yi2 = yi**2
54
+
55
+ dxy = xi_1 * yi - xi * yi_1
56
+
57
+ xii_1 = xi_1 + xi
58
+ yii_1 = yi_1 + yi
59
+
60
+ a00 = np.sum(dxy)
61
+ a10 = np.sum(dxy * xii_1)
62
+ a01 = np.sum(dxy * yii_1)
63
+ a20 = np.sum(dxy * (xi_1 * xii_1 + xi2))
64
+ a11 = np.sum(dxy * (xi_1 * (yii_1 + yi_1) + xi * (yii_1 + yi)))
65
+ a02 = np.sum(dxy * (yi_1 * yii_1 + yi2))
66
+ a30 = np.sum(dxy * xii_1 * (xi_12 + xi2))
67
+ a03 = np.sum(dxy * yii_1 * (yi_12 + yi2))
68
+ a21 = np.sum(dxy * (xi_12 * (3 * yi_1 + yi) + 2 *
69
+ xi * xi_1 * yii_1 + xi2 * (yi_1 + 3 * yi)))
70
+ a12 = np.sum(dxy * (yi_12 * (3 * xi_1 + xi) + 2 *
71
+ yi * yi_1 * xii_1 + yi2 * (xi_1 + 3 * xi)))
72
+
73
+ if abs(a00) > flt_epsilon:
74
+ db1_2 = 0.5
75
+ db1_6 = 0.16666666666666666666666666666667
76
+ db1_12 = 0.083333333333333333333333333333333
77
+ db1_24 = 0.041666666666666666666666666666667
78
+ db1_20 = 0.05
79
+ db1_60 = 0.016666666666666666666666666666667
80
+
81
+ if a00 < 0:
82
+ db1_2 *= -1
83
+ db1_6 *= -1
84
+ db1_12 *= -1
85
+ db1_24 *= -1
86
+ db1_20 *= -1
87
+ db1_60 *= -1
88
+
89
+ m = dict(m00=a00 * db1_2,
90
+ m10=a10 * db1_6,
91
+ m01=a01 * db1_6,
92
+ m20=a20 * db1_12,
93
+ m11=a11 * db1_24,
94
+ m02=a02 * db1_12,
95
+ m30=a30 * db1_20,
96
+ m21=a21 * db1_60,
97
+ m12=a12 * db1_60,
98
+ m03=a03 * db1_20,
99
+ )
100
+
101
+ if m["m00"] > dbl_epsilon:
102
+ # Center of gravity
103
+ cx = m["m10"]/m["m00"]
104
+ cy = m["m01"]/m["m00"]
105
+ else:
106
+ cx = 0
107
+ cy = 0
108
+
109
+ # central second order moments
110
+ m["mu20"] = m["m20"] - m["m10"]*cx
111
+ m["mu11"] = m["m11"] - m["m10"]*cy
112
+ m["mu02"] = m["m02"] - m["m01"]*cy
113
+
114
+ m["mu30"] = m["m30"] - cx*(3*m["mu20"] + cx*m["m10"])
115
+ m["mu21"] = m["m21"] - cx*(2*m["mu11"] + cx*m["m01"]) - cy*m["mu20"]
116
+ m["mu12"] = m["m12"] - cy*(2*m["mu11"] + cy*m["m10"]) - cx*m["mu02"]
117
+ m["mu03"] = m["m03"] - cy*(3*m["mu02"] + cy*m["m01"])
118
+ return m
119
+ else:
120
+ return None
121
+
122
+
123
+ def get_inert_ratio_cvx(cont):
124
+ """Compute the inertia ratio of the convex hull of a contour
125
+
126
+ The inertia ratio is computed from the central second order of moments
127
+ along x (mu20) and y (mu02) via `sqrt(mu20/mu02)`.
128
+
129
+ Parameters
130
+ ----------
131
+ cont: ndarray or list of ndarrays of shape (N,2)
132
+ A 2D array that holds the contour of an event (in pixels)
133
+ e.g. obtained using `mm.contour` where `mm` is an instance
134
+ of `RTDCBase`. The first and second columns of `cont`
135
+ correspond to the x- and y-coordinates of the contour.
136
+
137
+ Returns
138
+ -------
139
+ inert_ratio_cvx: float or ndarray of size N
140
+ The inertia ratio of the contour's convex hull
141
+
142
+ .. versionchanged:: 0.48.2
143
+
144
+ For long channels, an integer overflow could occur in previous
145
+ versions, leading invalid or nan values.
146
+ See https://github.com/DC-analysis/dclab/issues/212
147
+
148
+ Notes
149
+ -----
150
+ The contour moments mu20 and mu02 are computed the same way they
151
+ are computed in OpenCV's `moments.cpp`.
152
+
153
+
154
+ See Also
155
+ --------
156
+ get_inert_ratio_raw: Compute inertia ratio of a raw contour
157
+
158
+
159
+ References
160
+ ----------
161
+ - `<https://en.wikipedia.org/wiki/Image_moment#Central_moments>`__
162
+ - `<https://github.com/opencv/opencv/blob/
163
+ f81370232a651bdac5042efe907bcaa50a66c487/modules/imgproc/src/
164
+ moments.cpp#L93>`__
165
+ """
166
+ if isinstance(cont, np.ndarray):
167
+ # If cont is an array, it is not a list of contours,
168
+ # because contours can have different lengths.
169
+ cont = [cont]
170
+ ret_list = False
171
+ else:
172
+ ret_list = True
173
+
174
+ length = len(cont)
175
+
176
+ inert_ratio_cvx = np.zeros(length, dtype=np.float64) * np.nan
177
+
178
+ for ii in range(length):
179
+ try:
180
+ chull = ssp.ConvexHull(cont[ii])
181
+ except ssp.QhullError:
182
+ pass
183
+ else:
184
+ hull = cont[ii][chull.vertices, :]
185
+ inert_ratio_cvx[ii] = get_inert_ratio_raw(hull)
186
+
187
+ if not ret_list:
188
+ inert_ratio_cvx = inert_ratio_cvx[0]
189
+
190
+ return inert_ratio_cvx
191
+
192
+
193
+ def get_inert_ratio_prnc(cont):
194
+ """Compute principal inertia ratio of a contour
195
+
196
+ The principal inertia ratio is rotation-invariant, which
197
+ makes it applicable to reservoir measurements where e.g.
198
+ cells are not aligned with the channel.
199
+
200
+ .. versionchanged:: 0.48.2
201
+
202
+ For long channels, an integer overflow could occur in previous
203
+ versions, leading to a principal inertia ratio smaller than one.
204
+ See https://github.com/DC-analysis/dclab/issues/212
205
+
206
+ Parameters
207
+ ----------
208
+ cont: ndarray or list of ndarrays of shape (N,2)
209
+ A 2D array that holds the contour of an event (in pixels)
210
+ e.g. obtained using `mm.contour` where `mm` is an instance
211
+ of `RTDCBase`. The first and second columns of `cont`
212
+ correspond to the x- and y-coordinates of the contour.
213
+
214
+ Returns
215
+ -------
216
+ inert_ratio_prnc: float or ndarray of size N
217
+ The principal inertia ratio of the contour
218
+ """
219
+ if isinstance(cont, np.ndarray):
220
+ # If cont is an array, it is not a list of contours,
221
+ # because contours can have different lengths.
222
+ cont = [cont]
223
+ ret_list = False
224
+ else:
225
+ ret_list = True
226
+
227
+ length = len(cont)
228
+ # np.float32 for compatibility with opencv
229
+ inert_ratio_prnc = np.zeros(length, dtype=np.float32) * np.nan
230
+
231
+ for ii in range(length):
232
+ # As discussed in https://github.com/DC-analysis/dclab/issues/212,
233
+ # `cont_moments_cv` now already properly casts everything. But since
234
+ # we have to create a floating point contour anyway, we can just
235
+ # create a copy here and be safe.
236
+ cc = np.array(cont[ii], dtype=np.float64, copy=True)
237
+ moments = cont_moments_cv(cc)
238
+
239
+ if moments is not None:
240
+ # orientation of the contour
241
+ orient = 0.5 * np.arctan2(2 * moments['mu11'],
242
+ moments['mu02'] - moments['mu20'])
243
+ # rotate contour
244
+ rho = np.sqrt(cc[:, 0]**2 + cc[:, 1]**2)
245
+ phi = np.arctan2(cc[:, 1], cc[:, 0]) + orient + np.pi / 2
246
+ # change contour data in-place (we already created a copy)
247
+ cc[:, 0] = rho * np.cos(phi)
248
+ cc[:, 1] = rho * np.sin(phi)
249
+ # compute inertia ratio of rotated contour
250
+ mprnc = cont_moments_cv(cc)
251
+ inert_ratio_prnc[ii] = np.sqrt(mprnc["mu20"] / mprnc["mu02"])
252
+
253
+ if not ret_list:
254
+ inert_ratio_prnc = inert_ratio_prnc[0]
255
+
256
+ return inert_ratio_prnc
257
+
258
+
259
+ def get_inert_ratio_raw(cont):
260
+ """Compute the inertia ratio of a contour
261
+
262
+ The inertia ratio is computed from the central second order of moments
263
+ along x (mu20) and y (mu02) via `sqrt(mu20/mu02)`.
264
+
265
+ Parameters
266
+ ----------
267
+ cont: ndarray or list of ndarrays of shape (N,2)
268
+ A 2D array that holds the contour of an event (in pixels)
269
+ e.g. obtained using `mm.contour` where `mm` is an instance
270
+ of `RTDCBase`. The first and second columns of `cont`
271
+ correspond to the x- and y-coordinates of the contour.
272
+
273
+ Returns
274
+ -------
275
+ inert_ratio_raw: float or ndarray of size N
276
+ The inertia ratio of the contour
277
+
278
+ .. versionchanged:: 0.48.2
279
+
280
+ For long channels, an integer overflow could occur in previous
281
+ versions, leading invalid or nan values.
282
+ See https://github.com/DC-analysis/dclab/issues/212
283
+
284
+ Notes
285
+ -----
286
+ The contour moments mu20 and mu02 are computed the same way they
287
+ are computed in OpenCV's `moments.cpp`.
288
+
289
+ See Also
290
+ --------
291
+ get_inert_ratio_cvx: Compute inertia ratio of the convex hull of
292
+ a contour
293
+
294
+ References
295
+ ----------
296
+ - `<https://en.wikipedia.org/wiki/Image_moment#Central_moments>`__
297
+ - `<https://github.com/opencv/opencv/blob/
298
+ f81370232a651bdac5042efe907bcaa50a66c487/modules/imgproc/src/
299
+ moments.cpp#L93>`__
300
+ """
301
+ if isinstance(cont, np.ndarray):
302
+ # If cont is an array, it is not a list of contours,
303
+ # because contours can have different lengths.
304
+ cont = [cont]
305
+ ret_list = False
306
+ else:
307
+ ret_list = True
308
+
309
+ length = len(cont)
310
+
311
+ inert_ratio_raw = np.zeros(length, dtype=np.float64) * np.nan
312
+
313
+ for ii in range(length):
314
+ moments = cont_moments_cv(cont[ii])
315
+ if moments is not None:
316
+ inert_ratio_raw[ii] = np.sqrt(moments["mu20"]/moments["mu02"])
317
+
318
+ if not ret_list:
319
+ inert_ratio_raw = inert_ratio_raw[0]
320
+
321
+ return inert_ratio_raw
322
+
323
+
324
+ def get_tilt(cont):
325
+ """Compute tilt of raw contour relative to channel axis
326
+
327
+ Parameters
328
+ ----------
329
+ cont: ndarray or list of ndarrays of shape (N,2)
330
+ A 2D array that holds the contour of an event (in pixels)
331
+ e.g. obtained using `mm.contour` where `mm` is an instance
332
+ of `RTDCBase`. The first and second columns of `cont`
333
+ correspond to the x- and y-coordinates of the contour.
334
+
335
+ Returns
336
+ -------
337
+ tilt: float or ndarray of size N
338
+ Tilt of the contour in the interval [0, PI/2]
339
+
340
+ .. versionchanged:: 0.48.2
341
+
342
+ For long channels, an integer overflow could occur in previous
343
+ versions, leading to invalid tilt values of PI/2.
344
+ See https://github.com/DC-analysis/dclab/issues/212
345
+
346
+ References
347
+ ----------
348
+ - `<https://en.wikipedia.org/wiki/Image_moment#Examples_2>`__
349
+ """
350
+ if isinstance(cont, np.ndarray):
351
+ # If cont is an array, it is not a list of contours,
352
+ # because contours can have different lengths.
353
+ cont = [cont]
354
+ ret_list = False
355
+ else:
356
+ ret_list = True
357
+
358
+ length = len(cont)
359
+
360
+ tilt = np.zeros(length, dtype=np.float64) * np.nan
361
+
362
+ for ii in range(length):
363
+ moments = cont_moments_cv(cont[ii])
364
+ if moments is not None:
365
+ # orientation of the contour
366
+ tilt[ii] = 0.5 * np.arctan2(-2 * moments['mu11'],
367
+ moments['mu20'] - moments['mu02'])
368
+
369
+ tilt = np.abs(tilt)
370
+
371
+ # sanity check
372
+ assert np.all(tilt) <= np.pi/2
373
+
374
+ if not ret_list:
375
+ tilt = tilt[0]
376
+
377
+ return tilt