dclab 0.67.0__cp314-cp314t-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-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,135 @@
1
+ """Pixelation correction definitions"""
2
+
3
+ import numpy as np
4
+
5
+
6
+ def corr_deform_with_area_um(area_um, px_um=0.34):
7
+ """Deformation correction for area_um-deform data
8
+
9
+ The contour in RT-DC measurements is computed on a
10
+ pixelated grid. Due to sampling problems, the measured
11
+ deformation is overestimated and must be corrected.
12
+
13
+ The correction formula is described in :cite:`Herold2017`.
14
+
15
+ Parameters
16
+ ----------
17
+ area_um: float or ndarray
18
+ Apparent (2D image) area in µm² of the event(s)
19
+ px_um: float
20
+ The detector pixel size in µm.
21
+
22
+ Returns
23
+ -------
24
+ deform_delta: float or ndarray
25
+ Error of the deformation of the event(s) that must be
26
+ subtracted from `deform`.
27
+ deform_corr = deform - deform_delta
28
+ """
29
+ # A triple-exponential decay can be used to correct for pixelation
30
+ # for apparent cell areas between 10 and 1250µm².
31
+ # For 99 different radii between 0.4 μm and 20 μm circular objects were
32
+ # simulated on a pixel grid with the pixel resolution of 340 nm/pix. At
33
+ # each radius 1000 random starting points were created and the
34
+ # obtained contours were analyzed in the same fashion as RT-DC data.
35
+ # A convex hull on the contour was used to calculate the size (as area)
36
+ # and the deformation.
37
+ # The pixel size correction `pxscale` takes into account the pixel size
38
+ # in the pixelation correction formula.
39
+ pxscale = (.34 / px_um)**2
40
+ offs = 0.0012
41
+ exp1 = 0.020 * np.exp(-area_um * pxscale / 7.1)
42
+ exp2 = 0.010 * np.exp(-area_um * pxscale / 38.6)
43
+ exp3 = 0.005 * np.exp(-area_um * pxscale / 296)
44
+ delta = offs + exp1 + exp2 + exp3
45
+
46
+ return delta
47
+
48
+
49
+ def corr_deform_with_volume(volume, px_um=0.34):
50
+ """Deformation correction for volume-deform data
51
+
52
+ The contour in RT-DC measurements is computed on a
53
+ pixelated grid. Due to sampling problems, the measured
54
+ deformation is overestimated and must be corrected.
55
+
56
+ The correction is derived in scripts/pixelation_correction.py.
57
+
58
+ Parameters
59
+ ----------
60
+ volume: float or ndarray
61
+ The "volume" feature (rotation of raw contour) [µm³]
62
+ px_um: float
63
+ The detector pixel size in µm.
64
+
65
+ Returns
66
+ -------
67
+ deform_delta: float or ndarray
68
+ Error of the deformation of the event(s) that must be
69
+ subtracted from `deform`.
70
+ deform_corr = deform - deform_delta
71
+ """
72
+ pxscalev = (.34 / px_um)**3
73
+ offs = 0.0013
74
+ exp1 = 0.0172 * np.exp(-volume * pxscalev / 40)
75
+ exp2 = 0.0070 * np.exp(-volume * pxscalev / 450)
76
+ exp3 = 0.0032 * np.exp(-volume * pxscalev / 6040)
77
+ delta = offs + exp1 + exp2 + exp3
78
+ return delta
79
+
80
+
81
+ def get_pixelation_delta_pair(feat1, feat2, data1, data2, px_um=0.34):
82
+ """Convenience function that returns pixelation correction pair"""
83
+ # determine feature that defines abscissa
84
+ feat_absc = feat1 if feat1 in ["area_um", "volume"] else feat2
85
+ data_absc = data1 if feat_absc == feat1 else data2
86
+ # first compute all the possible pixelation offsets
87
+ delt1 = get_pixelation_delta(
88
+ feat_corr=feat1,
89
+ feat_absc=feat_absc,
90
+ data_absc=data_absc,
91
+ px_um=px_um)
92
+ delt2 = get_pixelation_delta(
93
+ feat_corr=feat2,
94
+ feat_absc=feat_absc,
95
+ data_absc=data_absc,
96
+ px_um=px_um)
97
+ return delt1, delt2
98
+
99
+
100
+ def get_pixelation_delta(feat_corr, feat_absc, data_absc, px_um=0.34):
101
+ """Convenience function for obtaining pixelation correction
102
+
103
+ Parameters
104
+ ----------
105
+ feat_corr: str
106
+ Feature for which to compute the pixelation correction
107
+ (e.g. "deform")
108
+ feat_absc: str
109
+ Feature with which to compute the correction (e.g. "area_um");
110
+ data_absc: ndarray or float
111
+ Corresponding data for `feat_absc`
112
+ px_um: float
113
+ Detector pixel size [µm]
114
+ """
115
+ if feat_corr == "deform" and feat_absc == "area_um":
116
+ delt = corr_deform_with_area_um(data_absc, px_um=px_um)
117
+ elif feat_corr == "circ" and feat_absc == "area_um":
118
+ delt = -corr_deform_with_area_um(data_absc, px_um=px_um)
119
+ elif feat_corr == "deform" and feat_absc == "volume":
120
+ delt = corr_deform_with_volume(data_absc, px_um=px_um)
121
+ elif feat_corr == "circ" and feat_absc == "volume":
122
+ delt = -corr_deform_with_volume(data_absc, px_um=px_um)
123
+ elif feat_corr == "area_um":
124
+ # no correction for area
125
+ delt = np.zeros_like(data_absc, dtype=float)
126
+ elif feat_corr == "volume":
127
+ # no correction for volume
128
+ delt = np.zeros_like(data_absc, dtype=float)
129
+ elif feat_corr == feat_absc:
130
+ raise ValueError("Input feature names are identical!")
131
+ else:
132
+ raise KeyError(
133
+ "No rule for feature '{}' with abscissa ".format(feat_corr)
134
+ + "'{}'!".format(feat_absc))
135
+ return delt
@@ -0,0 +1,247 @@
1
+ """Scale conversion applicable to a linear elastic model"""
2
+
3
+ import warnings
4
+
5
+ import numpy as np
6
+
7
+
8
+ def convert(area_um, deform, channel_width_in, channel_width_out,
9
+ emodulus=None, flow_rate_in=None, flow_rate_out=None,
10
+ viscosity_in=None, viscosity_out=None, inplace=False):
11
+ """convert area-deformation-emodulus triplet
12
+
13
+ The conversion formula is described in :cite:`Mietke2015`.
14
+
15
+ Parameters
16
+ ----------
17
+ area_um: ndarray
18
+ Convex cell area [µm²]
19
+ deform: ndarray
20
+ Deformation
21
+ channel_width_in: float
22
+ Original channel width [µm]
23
+ channel_width_out: float
24
+ Target channel width [µm]
25
+ emodulus: ndarray
26
+ Young's Modulus [kPa]
27
+ flow_rate_in: float
28
+ Original flow rate [µL/s]
29
+ flow_rate_out: float
30
+ Target flow rate [µL/s]
31
+ viscosity_in: float
32
+ Original viscosity [mPa*s]
33
+ viscosity_out: float or ndarray
34
+ Target viscosity [mPa*s]; This can be an array
35
+ inplace: bool
36
+ If True, override input arrays with corrected data
37
+
38
+ Returns
39
+ -------
40
+ area_um_corr: ndarray
41
+ Corrected cell area [µm²]
42
+ deform_corr: ndarray
43
+ Deformation (a copy if `inplace` is False)
44
+ emodulus_corr: ndarray
45
+ Corrected emodulus [kPa]; only returned if `emodulus` is given.
46
+
47
+ Notes
48
+ -----
49
+ If only `area_um`, `deform`, `channel_width_in` and
50
+ `channel_width_out` are given, then only the area is
51
+ corrected and returned together with the original deform.
52
+ If all other arguments are not set to None, the emodulus
53
+ is corrected and returned as well.
54
+ """
55
+ warnings.warn("Usage of the `convert` method is deprecated! Please use "
56
+ + "the scale_feature method instead.",
57
+ DeprecationWarning)
58
+ copy = not inplace
59
+
60
+ deform_corr = np.array(deform, copy=copy)
61
+
62
+ if channel_width_in != channel_width_out:
63
+ area_um_corr = scale_area_um(area_um, channel_width_in,
64
+ channel_width_out, inplace)
65
+ else:
66
+ area_um_corr = np.array(area_um, copy=copy)
67
+
68
+ if (emodulus is not None
69
+ and flow_rate_in is not None
70
+ and flow_rate_out is not None
71
+ and viscosity_in is not None
72
+ and viscosity_out is not None):
73
+ emodulus_corr = scale_emodulus(emodulus, channel_width_in,
74
+ channel_width_out, flow_rate_in,
75
+ flow_rate_out, viscosity_in,
76
+ viscosity_out, inplace)
77
+
78
+ if emodulus is None:
79
+ return area_um_corr, deform_corr
80
+ else:
81
+ return area_um_corr, deform_corr, emodulus_corr
82
+
83
+
84
+ def scale_area_um(area_um, channel_width_in, channel_width_out, inplace=False,
85
+ **kwargs):
86
+ """Perform scale conversion for area_um (linear elastic model)
87
+
88
+ The area scales with the characteristic length
89
+ "channel radius" L according to (L'/L)².
90
+
91
+ The conversion formula is described in :cite:`Mietke2015`.
92
+
93
+ .. versionadded: 0.25.0
94
+
95
+ Parameters
96
+ ----------
97
+ area_um: ndarray
98
+ Convex area [µm²]
99
+ channel_width_in: float
100
+ Original channel width [µm]
101
+ channel_width_out: float
102
+ Target channel width [µm]
103
+ inplace: bool
104
+ If True, override input arrays with corrected data
105
+ kwargs:
106
+ not used
107
+
108
+ Returns
109
+ -------
110
+ area_um_corr: ndarray
111
+ Scaled area [µm²]
112
+ """
113
+ copy = not inplace
114
+ if issubclass(area_um.dtype.type, np.integer) and inplace:
115
+ raise ValueError("Cannot correct integer `area_um` in-place!")
116
+ area_um_corr = np.array(area_um, copy=copy)
117
+
118
+ if channel_width_in != channel_width_out:
119
+ area_um_corr *= (channel_width_out / channel_width_in)**2
120
+ return area_um_corr
121
+
122
+
123
+ def scale_emodulus(emodulus, channel_width_in, channel_width_out,
124
+ flow_rate_in, flow_rate_out, viscosity_in,
125
+ viscosity_out, inplace=False):
126
+ """Perform scale conversion for area_um (linear elastic model)
127
+
128
+ The conversion formula is described in :cite:`Mietke2015`.
129
+
130
+ .. versionadded: 0.25.0
131
+
132
+ Parameters
133
+ ----------
134
+ emodulus: ndarray
135
+ Young's Modulus [kPa]
136
+ channel_width_in: float
137
+ Original channel width [µm]
138
+ channel_width_out: float
139
+ Target channel width [µm]
140
+ flow_rate_in: float
141
+ Original flow rate [µL/s]
142
+ flow_rate_out: float
143
+ Target flow rate [µL/s]
144
+ viscosity_in: float
145
+ Original viscosity [mPa*s]
146
+ viscosity_out: float or ndarray
147
+ Target viscosity [mPa*s]; This can be an array
148
+ inplace: bool
149
+ If True, override input arrays with corrected data
150
+
151
+ Returns
152
+ -------
153
+ emodulus_corr: ndarray
154
+ Scaled emodulus [kPa]
155
+ """
156
+ copy = not inplace
157
+
158
+ emodulus_corr = np.array(emodulus, copy=copy)
159
+
160
+ if viscosity_in is not None:
161
+ if isinstance(viscosity_in, np.ndarray):
162
+ raise ValueError("`viscosity_in` must not be an array!")
163
+
164
+ has_nones = (flow_rate_in is None
165
+ or flow_rate_out is None
166
+ or viscosity_in is None
167
+ or viscosity_out is None
168
+ or channel_width_in is None
169
+ or channel_width_out is None
170
+ )
171
+ has_changes = (flow_rate_in != flow_rate_out
172
+ or channel_width_in != channel_width_out
173
+ or (isinstance(viscosity_out, np.ndarray) # check before
174
+ or viscosity_in != viscosity_out)
175
+ )
176
+
177
+ if not has_nones and has_changes:
178
+ emodulus_corr *= (flow_rate_out / flow_rate_in) \
179
+ * (viscosity_out / viscosity_in) \
180
+ * (channel_width_in / channel_width_out)**3
181
+
182
+ return emodulus_corr
183
+
184
+
185
+ def scale_feature(feat, data, inplace=False, **scale_kw):
186
+ """Convenience function for scale conversions (linear elastic model)
187
+
188
+ This method wraps around all the other scale_* methods and also
189
+ supports deform/circ.
190
+
191
+ Parameters
192
+ ----------
193
+ feat: str
194
+ Valid scalar feature name
195
+ data: float or ndarray
196
+ Feature data
197
+ inplace: bool
198
+ If True, override input arrays with corrected data
199
+ **scale_kw:
200
+ Scale keyword arguments for the wrapped methods
201
+ """
202
+ if feat == "area_um":
203
+ return scale_area_um(area_um=data, inplace=inplace, **scale_kw)
204
+ elif feat in ["circ", "deform"]:
205
+ # no units
206
+ return np.array(data, copy=not inplace)
207
+ elif feat == "emodulus":
208
+ return scale_emodulus(emodulus=data, inplace=inplace, **scale_kw)
209
+ elif feat == "volume":
210
+ return scale_volume(volume=data, inplace=inplace, **scale_kw)
211
+ else:
212
+ raise KeyError("No recipe to scale feature '{}'!".format(feat))
213
+
214
+
215
+ def scale_volume(volume, channel_width_in, channel_width_out, inplace=False,
216
+ **kwargs):
217
+ """Perform scale conversion for volume (linear elastic model)
218
+
219
+ The volume scales with the characteristic length
220
+ "channel radius" L according to (L'/L)³.
221
+
222
+ .. versionadded: 0.25.0
223
+
224
+ Parameters
225
+ ----------
226
+ volume: ndarray
227
+ Volume [µm³]
228
+ channel_width_in: float
229
+ Original channel width [µm]
230
+ channel_width_out: float
231
+ Target channel width [µm]
232
+ inplace: bool
233
+ If True, override input arrays with corrected data
234
+ kwargs:
235
+ not used
236
+
237
+ Returns
238
+ -------
239
+ volume_corr: ndarray
240
+ Scaled volume [µm³]
241
+ """
242
+ copy = not inplace
243
+ volume_corr = np.array(volume, copy=copy)
244
+
245
+ if channel_width_in != channel_width_out:
246
+ volume_corr *= (channel_width_out / channel_width_in)**3
247
+ return volume_corr
@@ -0,0 +1,260 @@
1
+ """Viscosity computation for various media"""
2
+ from __future__ import annotations
3
+
4
+ from typing import Literal
5
+ import warnings
6
+
7
+ import numpy as np
8
+
9
+ from ...warn import PipelineWarning
10
+
11
+
12
+ #: Dictionary with different names for one medium
13
+ SAME_MEDIA = {
14
+ "0.49% MC-PBS": ["0.49% MC-PBS",
15
+ "0.5% MC-PBS",
16
+ "0.50% MC-PBS",
17
+ "CellCarrier",
18
+ ],
19
+ # 6g MC in 1010g PBS [!sic]
20
+ "0.59% MC-PBS": ["0.59% MC-PBS",
21
+ "0.6% MC-PBS",
22
+ "0.60% MC-PBS",
23
+ "CellCarrier B",
24
+ "CellCarrierB",
25
+ ],
26
+ "0.83% MC-PBS": ["0.83% MC-PBS",
27
+ "0.8% MC-PBS",
28
+ "0.80% MC-PBS"],
29
+ "water": ["water"],
30
+ }
31
+
32
+ #: Many media names are actually shorthand for one medium
33
+ ALIAS_MEDIA = {}
34
+ for key in SAME_MEDIA:
35
+ for item in SAME_MEDIA[key]:
36
+ ALIAS_MEDIA[item] = key
37
+ ALIAS_MEDIA[item.lower()] = key # also support all-lower case
38
+
39
+ #: Media for which computation of viscosity is defined (has duplicate entries)
40
+ KNOWN_MEDIA = sorted(ALIAS_MEDIA.keys())
41
+
42
+
43
+ class TemperatureOutOfRangeWarning(PipelineWarning):
44
+ pass
45
+
46
+
47
+ def check_temperature(model: str,
48
+ temperature: float | np.array,
49
+ tmin: float,
50
+ tmax: float):
51
+ """Raise a TemperatureOutOfRangeWarning if applicable"""
52
+ if np.min(temperature) < tmin or np.max(temperature) > tmax:
53
+ warnings.warn(
54
+ f"For the {model} model, the temperature should be "
55
+ f"in [{tmin}, {tmax}] degC! Got min/max of "
56
+ f"[{np.min(temperature):.1f}, {np.max(temperature):.1f}] degC.",
57
+ TemperatureOutOfRangeWarning)
58
+
59
+
60
+ def get_viscosity(medium: str = "0.49% MC-PBS",
61
+ channel_width: float = 20.0,
62
+ flow_rate: float = 0.16,
63
+ temperature: float | np.ndarray = 23.0,
64
+ model: Literal['herold-2017',
65
+ 'herold-2017-fallback',
66
+ 'buyukurganci-2022',
67
+ 'kestin-1978'] = 'herold-2017-fallback'):
68
+ """Returns the viscosity for RT-DC-specific media
69
+
70
+ Media that are not pure (e.g. ketchup or polymer solutions)
71
+ often exhibit a non-linear relationship between shear rate
72
+ (determined by the velocity profile) and shear stress
73
+ (determined by pressure differences). If the shear stress
74
+ grows non-linearly with the shear rate resulting in a slope
75
+ in log-log space that is less than one, then we are talking about
76
+ shear thinning. The viscosity is not a constant anymore (as it
77
+ is e.g. for water). At higher flow rates, the viscosity becomes
78
+ smaller, following a power law. Christoph Herold characterized
79
+ shear thinning for the CellCarrier media :cite:`Herold2017`.
80
+ The resulting formulae for computing the viscosities of these
81
+ media at different channel widths, flow rates, and temperatures,
82
+ are implemented here.
83
+
84
+ Parameters
85
+ ----------
86
+ medium: str
87
+ The medium to compute the viscosity for; Valid values
88
+ are defined in :const:`KNOWN_MEDIA`.
89
+ channel_width: float
90
+ The channel width in µm
91
+ flow_rate: float
92
+ Flow rate in µL/s
93
+ temperature: float or ndarray
94
+ Temperature in °C
95
+ model: str
96
+ The model name to use for computing the medium viscosity.
97
+ For water, this value is ignored, as there is only the
98
+ 'kestin-1978' model :cite:`Kestin_1978`. For MC-PBS media,
99
+ there are the 'herold-2017' model :cite:`Herold2017` and the
100
+ 'buyukurganci-2022' model :cite:`Buyukurganci2022`.
101
+
102
+ Returns
103
+ -------
104
+ viscosity: float or ndarray
105
+ Viscosity in mPa*s
106
+
107
+ Notes
108
+ -----
109
+ - CellCarrier (0.49% MC-PBS) and CellCarrier B (0.59% MC-PBS) are
110
+ media designed for RT-DC experiments.
111
+ - A :class:`TemperatureOutOfRangeWarning` is issued if the
112
+ input temperature range exceeds the temperature ranges of
113
+ the models.
114
+ """
115
+ # also support lower-case media and a space before the "B"
116
+ if medium not in KNOWN_MEDIA:
117
+ raise ValueError(f"Invalid medium: {medium}")
118
+ medium = ALIAS_MEDIA[medium]
119
+
120
+ if medium == "water":
121
+ # We ignore the `model`, because it's user convenient.
122
+ eta = get_viscosity_water_kestin_1978(temperature=temperature)
123
+ elif not flow_rate:
124
+ # When flow rate is zero, we cannot compute the viscosity for
125
+ # anything other than water above.
126
+ eta = np.nan
127
+ elif medium in ["0.49% MC-PBS", "0.59% MC-PBS", "0.83% MC-PBS"]:
128
+ kwargs = {"medium": medium,
129
+ "temperature": temperature,
130
+ "flow_rate": flow_rate,
131
+ "channel_width": channel_width}
132
+
133
+ # Let the user know that we have a new model in town.
134
+ if model == "herold-2017-fallback":
135
+ warnings.warn(
136
+ "dclab 0.48.0 introduced a more accurate model for computing "
137
+ "the MC-PBS viscosity. You are now using the old model "
138
+ "'herold-2017'. Unless you are reproducing an old analysis "
139
+ "pipeline, you should consider passing 'buyukurganci-2022' "
140
+ "as a viscosity model!",
141
+ DeprecationWarning)
142
+ model = "herold-2017"
143
+
144
+ if model == "herold-2017":
145
+ eta = get_viscosity_mc_pbs_herold_2017(**kwargs)
146
+ elif model == "buyukurganci-2022":
147
+ eta = get_viscosity_mc_pbs_buyukurganci_2022(**kwargs)
148
+ else:
149
+ raise NotImplementedError(f"Unknown model '{model}' for MC-PBS!")
150
+ else:
151
+ raise NotImplementedError(f"Unknown medium '{medium}'!")
152
+ return eta
153
+
154
+
155
+ def shear_rate_square_channel(flow_rate, channel_width, flow_index):
156
+ """Returns The wall shear rate of a power law liquid in a squared channel.
157
+
158
+ Parameters
159
+ ----------
160
+ flow_rate: float
161
+ Flow rate in µL/s
162
+ channel_width: float
163
+ The channel width in µm
164
+ flow_index: float
165
+ Flow behavior index aka the power law exponent of the shear thinning
166
+
167
+ Returns
168
+ -------
169
+ shear_rate: float
170
+ Shear rate in 1/s.
171
+ """
172
+ # convert channel width to mm
173
+ channel_width = channel_width * 1e-3
174
+ return 8*flow_rate/(channel_width**3) * (0.6671 + 0.2121/flow_index)
175
+
176
+
177
+ def get_viscosity_mc_pbs_buyukurganci_2022(
178
+ medium: Literal["0.49% MC-PBS",
179
+ "0.59% MC-PBS",
180
+ "0.83% MC-PBS"] = "0.49% MC-PBS",
181
+ channel_width: float = 20.0,
182
+ flow_rate: float = 0.16,
183
+ temperature: float = 23.0):
184
+ """Compute viscosity of MC-PBS according to :cite:`Buyukurganci2022`
185
+
186
+ This viscosity model was derived in :cite:`Buyukurganci2022`
187
+ and adapted for RT-DC in :cite:`Reichel2023`.
188
+ """
189
+ check_temperature("'buyukurganci-2022' MC-PBS", temperature, 22, 37)
190
+ # material constants for temperature behavior of MC dissolved in PBS:
191
+ alpha = 0.00223
192
+ lambd = 3379.7
193
+ kelvin = temperature + 273.15
194
+
195
+ if medium == "0.49% MC-PBS":
196
+ a = 2.30e-6 # previously 2.23e-6, changed in Reichel2023 rev 2
197
+ beta = -0.0056
198
+ elif medium == "0.59% MC-PBS":
199
+ a = 5.70e-6
200
+ beta = -0.0744
201
+ elif medium == "0.83% MC-PBS":
202
+ a = 16.52e-6
203
+ beta = -0.1455
204
+ else:
205
+ raise NotImplementedError(
206
+ f"Medium {medium} not supported for model `buyukurganci-2022`!")
207
+
208
+ k = a * np.exp(lambd / kelvin)
209
+ n = alpha * kelvin + beta
210
+ shear_rate = shear_rate_square_channel(flow_rate, channel_width, n)
211
+ return k * shear_rate**(n - 1) * 1e3
212
+
213
+
214
+ def get_viscosity_mc_pbs_herold_2017(
215
+ medium: Literal["0.49% MC-PBS", "0.59% MC-PBS"] = "0.49% MC-PBS",
216
+ channel_width: float = 20.0,
217
+ flow_rate: float = 0.16,
218
+ temperature: float = 23.0):
219
+ r"""Compute viscosity of MC-PBS according to :cite:`Herold2017`
220
+
221
+ Note that all the factors in equation 5.2 in :cite:`Herold2017`
222
+ compute to 8, which is essentially what is implemented in
223
+ :func:`shear_rate_square_channel`:
224
+
225
+ .. math::
226
+
227
+ 1.1856 \cdot 6 \cdot \frac{2}{3} \cdot \frac{1}{0.5928} = 8
228
+ """
229
+ # see figure (9) in Herold arXiv:1704.00572 (2017)
230
+ check_temperature("'herold-2017' MC-PBS", temperature, 18, 26)
231
+ # convert flow_rate from µL/s to m³/s
232
+ # convert channel_width from µm to m
233
+ term1 = 1.1856 * 6 * flow_rate * 1e-9 / (channel_width * 1e-6)**3 * 2 / 3
234
+
235
+ if medium == "0.49% MC-PBS":
236
+ temp_corr = (temperature / 23.2)**-0.866
237
+ term2 = 0.6771 / 0.5928 + 0.2121 / (0.5928 * 0.677)
238
+ eta = 0.179 * (term1 * term2)**(0.677 - 1) * temp_corr * 1e3
239
+ elif medium == "0.59% MC-PBS":
240
+ temp_corr = (temperature / 23.6)**-0.866
241
+ term2 = 0.6771 / 0.5928 + 0.2121 / (0.5928 * 0.634)
242
+ eta = 0.360 * (term1 * term2)**(0.634 - 1) * temp_corr * 1e3
243
+ else:
244
+ raise NotImplementedError(
245
+ f"Medium {medium} not supported for model `herold-2017`!")
246
+ return eta
247
+
248
+
249
+ def get_viscosity_water_kestin_1978(temperature: float = 23.0):
250
+ """Compute the viscosity of water according to :cite:`Kestin_1978`"""
251
+ # see equation (15) in Kestin et al, J. Phys. Chem. 7(3) 1978
252
+ check_temperature("'kestin-1978' water", temperature, 0, 40)
253
+ eta0 = 1.002 # [mPa s]
254
+ right = (20-temperature) / (temperature + 96) \
255
+ * (+ 1.2364
256
+ - 1.37e-3 * (20 - temperature)
257
+ + 5.7e-6 * (20 - temperature)**2
258
+ )
259
+ eta = eta0 * 10**right
260
+ return eta