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.
- dclab/__init__.py +41 -0
- dclab/_version.py +34 -0
- dclab/cached.py +97 -0
- dclab/cli/__init__.py +10 -0
- dclab/cli/common.py +237 -0
- dclab/cli/task_compress.py +126 -0
- dclab/cli/task_condense.py +223 -0
- dclab/cli/task_join.py +229 -0
- dclab/cli/task_repack.py +98 -0
- dclab/cli/task_split.py +154 -0
- dclab/cli/task_tdms2rtdc.py +186 -0
- dclab/cli/task_verify_dataset.py +75 -0
- dclab/definitions/__init__.py +79 -0
- dclab/definitions/feat_const.py +202 -0
- dclab/definitions/feat_logic.py +182 -0
- dclab/definitions/meta_const.py +252 -0
- dclab/definitions/meta_logic.py +111 -0
- dclab/definitions/meta_parse.py +94 -0
- dclab/downsampling.cpython-314t-darwin.so +0 -0
- dclab/downsampling.pyx +230 -0
- dclab/external/__init__.py +4 -0
- dclab/external/packaging/LICENSE +3 -0
- dclab/external/packaging/LICENSE.APACHE +177 -0
- dclab/external/packaging/LICENSE.BSD +23 -0
- dclab/external/packaging/__init__.py +6 -0
- dclab/external/packaging/_structures.py +61 -0
- dclab/external/packaging/version.py +505 -0
- dclab/external/skimage/LICENSE +28 -0
- dclab/external/skimage/__init__.py +2 -0
- dclab/external/skimage/_find_contours.py +216 -0
- dclab/external/skimage/_find_contours_cy.cpython-314t-darwin.so +0 -0
- dclab/external/skimage/_find_contours_cy.pyx +188 -0
- dclab/external/skimage/_pnpoly.cpython-314t-darwin.so +0 -0
- dclab/external/skimage/_pnpoly.pyx +99 -0
- dclab/external/skimage/_shared/__init__.py +1 -0
- dclab/external/skimage/_shared/geometry.cpython-314t-darwin.so +0 -0
- dclab/external/skimage/_shared/geometry.pxd +6 -0
- dclab/external/skimage/_shared/geometry.pyx +55 -0
- dclab/external/skimage/measure.py +7 -0
- dclab/external/skimage/pnpoly.py +53 -0
- dclab/external/statsmodels/LICENSE +35 -0
- dclab/external/statsmodels/__init__.py +6 -0
- dclab/external/statsmodels/nonparametric/__init__.py +1 -0
- dclab/external/statsmodels/nonparametric/_kernel_base.py +203 -0
- dclab/external/statsmodels/nonparametric/kernel_density.py +165 -0
- dclab/external/statsmodels/nonparametric/kernels.py +36 -0
- dclab/features/__init__.py +9 -0
- dclab/features/bright.py +81 -0
- dclab/features/bright_bc.py +93 -0
- dclab/features/bright_perc.py +63 -0
- dclab/features/contour.py +161 -0
- dclab/features/emodulus/__init__.py +339 -0
- dclab/features/emodulus/load.py +252 -0
- dclab/features/emodulus/lut_HE-2D-FEM-22.txt +16432 -0
- dclab/features/emodulus/lut_HE-3D-FEM-22.txt +1276 -0
- dclab/features/emodulus/lut_LE-2D-FEM-19.txt +13082 -0
- dclab/features/emodulus/pxcorr.py +135 -0
- dclab/features/emodulus/scale_linear.py +247 -0
- dclab/features/emodulus/viscosity.py +260 -0
- dclab/features/fl_crosstalk.py +95 -0
- dclab/features/inert_ratio.py +377 -0
- dclab/features/volume.py +242 -0
- dclab/http_utils.py +322 -0
- dclab/isoelastics/__init__.py +468 -0
- dclab/isoelastics/iso_HE-2D-FEM-22-area_um-deform.txt +2440 -0
- dclab/isoelastics/iso_HE-2D-FEM-22-volume-deform.txt +2635 -0
- dclab/isoelastics/iso_HE-3D-FEM-22-area_um-deform.txt +1930 -0
- dclab/isoelastics/iso_HE-3D-FEM-22-volume-deform.txt +2221 -0
- dclab/isoelastics/iso_LE-2D-FEM-19-area_um-deform.txt +2151 -0
- dclab/isoelastics/iso_LE-2D-FEM-19-volume-deform.txt +2250 -0
- dclab/isoelastics/iso_LE-2D-ana-18-area_um-deform.txt +1266 -0
- dclab/kde/__init__.py +1 -0
- dclab/kde/base.py +459 -0
- dclab/kde/contours.py +222 -0
- dclab/kde/methods.py +313 -0
- dclab/kde_contours.py +10 -0
- dclab/kde_methods.py +11 -0
- dclab/lme4/__init__.py +5 -0
- dclab/lme4/lme4_template.R +94 -0
- dclab/lme4/rsetup.py +204 -0
- dclab/lme4/wrapr.py +386 -0
- dclab/polygon_filter.py +398 -0
- dclab/rtdc_dataset/__init__.py +15 -0
- dclab/rtdc_dataset/check.py +902 -0
- dclab/rtdc_dataset/config.py +533 -0
- dclab/rtdc_dataset/copier.py +353 -0
- dclab/rtdc_dataset/core.py +896 -0
- dclab/rtdc_dataset/export.py +867 -0
- dclab/rtdc_dataset/feat_anc_core/__init__.py +24 -0
- dclab/rtdc_dataset/feat_anc_core/af_basic.py +75 -0
- dclab/rtdc_dataset/feat_anc_core/af_emodulus.py +160 -0
- dclab/rtdc_dataset/feat_anc_core/af_fl_max_ctc.py +133 -0
- dclab/rtdc_dataset/feat_anc_core/af_image_contour.py +113 -0
- dclab/rtdc_dataset/feat_anc_core/af_ml_class.py +102 -0
- dclab/rtdc_dataset/feat_anc_core/ancillary_feature.py +320 -0
- dclab/rtdc_dataset/feat_anc_ml/__init__.py +32 -0
- dclab/rtdc_dataset/feat_anc_plugin/__init__.py +3 -0
- dclab/rtdc_dataset/feat_anc_plugin/plugin_feature.py +329 -0
- dclab/rtdc_dataset/feat_basin.py +762 -0
- dclab/rtdc_dataset/feat_temp.py +102 -0
- dclab/rtdc_dataset/filter.py +263 -0
- dclab/rtdc_dataset/fmt_dcor/__init__.py +7 -0
- dclab/rtdc_dataset/fmt_dcor/access_token.py +52 -0
- dclab/rtdc_dataset/fmt_dcor/api.py +173 -0
- dclab/rtdc_dataset/fmt_dcor/base.py +299 -0
- dclab/rtdc_dataset/fmt_dcor/basin.py +73 -0
- dclab/rtdc_dataset/fmt_dcor/logs.py +26 -0
- dclab/rtdc_dataset/fmt_dcor/tables.py +66 -0
- dclab/rtdc_dataset/fmt_dict.py +103 -0
- dclab/rtdc_dataset/fmt_hdf5/__init__.py +6 -0
- dclab/rtdc_dataset/fmt_hdf5/base.py +192 -0
- dclab/rtdc_dataset/fmt_hdf5/basin.py +30 -0
- dclab/rtdc_dataset/fmt_hdf5/events.py +276 -0
- dclab/rtdc_dataset/fmt_hdf5/feat_defect.py +164 -0
- dclab/rtdc_dataset/fmt_hdf5/logs.py +33 -0
- dclab/rtdc_dataset/fmt_hdf5/tables.py +60 -0
- dclab/rtdc_dataset/fmt_hierarchy/__init__.py +11 -0
- dclab/rtdc_dataset/fmt_hierarchy/base.py +278 -0
- dclab/rtdc_dataset/fmt_hierarchy/events.py +146 -0
- dclab/rtdc_dataset/fmt_hierarchy/hfilter.py +140 -0
- dclab/rtdc_dataset/fmt_hierarchy/mapper.py +134 -0
- dclab/rtdc_dataset/fmt_http.py +102 -0
- dclab/rtdc_dataset/fmt_s3.py +354 -0
- dclab/rtdc_dataset/fmt_tdms/__init__.py +476 -0
- dclab/rtdc_dataset/fmt_tdms/event_contour.py +264 -0
- dclab/rtdc_dataset/fmt_tdms/event_image.py +220 -0
- dclab/rtdc_dataset/fmt_tdms/event_mask.py +62 -0
- dclab/rtdc_dataset/fmt_tdms/event_trace.py +146 -0
- dclab/rtdc_dataset/fmt_tdms/exc.py +37 -0
- dclab/rtdc_dataset/fmt_tdms/naming.py +151 -0
- dclab/rtdc_dataset/load.py +77 -0
- dclab/rtdc_dataset/meta_table.py +25 -0
- dclab/rtdc_dataset/writer.py +1019 -0
- dclab/statistics.py +226 -0
- dclab/util.py +176 -0
- dclab/warn.py +15 -0
- dclab-0.67.0.dist-info/METADATA +153 -0
- dclab-0.67.0.dist-info/RECORD +142 -0
- dclab-0.67.0.dist-info/WHEEL +6 -0
- dclab-0.67.0.dist-info/entry_points.txt +8 -0
- dclab-0.67.0.dist-info/licenses/LICENSE +283 -0
- 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
|