FlowCyPy 0.7.0__py3-none-any.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.
- FlowCyPy/__init__.py +13 -0
- FlowCyPy/_version.py +16 -0
- FlowCyPy/acquisition.py +652 -0
- FlowCyPy/classifier.py +208 -0
- FlowCyPy/coupling_mechanism/__init__.py +4 -0
- FlowCyPy/coupling_mechanism/empirical.py +47 -0
- FlowCyPy/coupling_mechanism/mie.py +207 -0
- FlowCyPy/coupling_mechanism/rayleigh.py +116 -0
- FlowCyPy/coupling_mechanism/uniform.py +40 -0
- FlowCyPy/coupling_mechanism.py +205 -0
- FlowCyPy/cytometer.py +314 -0
- FlowCyPy/detector.py +439 -0
- FlowCyPy/directories.py +36 -0
- FlowCyPy/distribution/__init__.py +16 -0
- FlowCyPy/distribution/base_class.py +79 -0
- FlowCyPy/distribution/delta.py +104 -0
- FlowCyPy/distribution/lognormal.py +124 -0
- FlowCyPy/distribution/normal.py +128 -0
- FlowCyPy/distribution/particle_size_distribution.py +132 -0
- FlowCyPy/distribution/uniform.py +117 -0
- FlowCyPy/distribution/weibull.py +115 -0
- FlowCyPy/flow_cell.py +198 -0
- FlowCyPy/helper.py +81 -0
- FlowCyPy/logger.py +136 -0
- FlowCyPy/noises.py +34 -0
- FlowCyPy/particle_count.py +127 -0
- FlowCyPy/peak_locator/__init__.py +4 -0
- FlowCyPy/peak_locator/base_class.py +163 -0
- FlowCyPy/peak_locator/basic.py +108 -0
- FlowCyPy/peak_locator/derivative.py +143 -0
- FlowCyPy/peak_locator/moving_average.py +166 -0
- FlowCyPy/physical_constant.py +19 -0
- FlowCyPy/plottings.py +269 -0
- FlowCyPy/population.py +136 -0
- FlowCyPy/populations_instances.py +65 -0
- FlowCyPy/scatterer_collection.py +306 -0
- FlowCyPy/signal_digitizer.py +90 -0
- FlowCyPy/source.py +249 -0
- FlowCyPy/units.py +30 -0
- FlowCyPy/utils.py +191 -0
- FlowCyPy-0.7.0.dist-info/LICENSE +21 -0
- FlowCyPy-0.7.0.dist-info/METADATA +252 -0
- FlowCyPy-0.7.0.dist-info/RECORD +45 -0
- FlowCyPy-0.7.0.dist-info/WHEEL +5 -0
- FlowCyPy-0.7.0.dist-info/top_level.txt +1 -0
FlowCyPy/plottings.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
from typing import Optional, Union, Tuple
|
|
2
|
+
from MPSPlots.styles import mps
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
import seaborn as sns
|
|
5
|
+
import pandas as pd
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MetricPlotter:
|
|
10
|
+
"""
|
|
11
|
+
A class for creating 2D density and scatter plots of scattering intensities from two detectors.
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
coincidence_dataframe : pd.DataFrame
|
|
16
|
+
The dataframe containing the coincidence data, including detector and feature columns.
|
|
17
|
+
detector_names : tuple of str
|
|
18
|
+
A tuple containing the names of the two detectors (detector 0 and detector 1).
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, coincidence_dataframe: pd.DataFrame, detector_names: Tuple[str, str]):
|
|
22
|
+
self.coincidence_dataframe = coincidence_dataframe.reset_index()
|
|
23
|
+
self.detector_names = detector_names
|
|
24
|
+
|
|
25
|
+
def _extract_feature_data(self, feature: str):
|
|
26
|
+
"""
|
|
27
|
+
Extracts and processes the feature data for the two detectors.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
feature : str
|
|
32
|
+
The feature to extract.
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
Tuple[pd.Series, pd.Series, str, str]
|
|
37
|
+
Processed x_data, y_data, x_units, and y_units.
|
|
38
|
+
"""
|
|
39
|
+
name_0, name_1 = self.detector_names
|
|
40
|
+
x_data = self.coincidence_dataframe[(name_0, feature)]
|
|
41
|
+
y_data = self.coincidence_dataframe[(name_1, feature)]
|
|
42
|
+
|
|
43
|
+
x_units = x_data.max().to_compact().units
|
|
44
|
+
y_units = y_data.max().to_compact().units
|
|
45
|
+
|
|
46
|
+
x_data = x_data.pint.to(x_units)
|
|
47
|
+
y_data = y_data.pint.to(y_units)
|
|
48
|
+
|
|
49
|
+
return x_data, y_data, x_units, y_units
|
|
50
|
+
|
|
51
|
+
def _create_density_plot(
|
|
52
|
+
self,
|
|
53
|
+
x_data: pd.Series,
|
|
54
|
+
y_data: pd.Series,
|
|
55
|
+
bandwidth_adjust: float,
|
|
56
|
+
):
|
|
57
|
+
"""
|
|
58
|
+
Creates a KDE density plot.
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
x_data : pd.Series
|
|
63
|
+
The x-axis data.
|
|
64
|
+
y_data : pd.Series
|
|
65
|
+
The y-axis data.
|
|
66
|
+
bandwidth_adjust : float
|
|
67
|
+
Adjustment factor for the KDE bandwidth.
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
sns.JointGrid
|
|
72
|
+
The seaborn JointGrid object.
|
|
73
|
+
"""
|
|
74
|
+
return sns.jointplot(
|
|
75
|
+
data=self.coincidence_dataframe,
|
|
76
|
+
x=x_data,
|
|
77
|
+
y=y_data,
|
|
78
|
+
kind="kde",
|
|
79
|
+
alpha=0.8,
|
|
80
|
+
fill=True,
|
|
81
|
+
joint_kws={"alpha": 0.7, "bw_adjust": bandwidth_adjust},
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def _add_scatterplot(
|
|
85
|
+
self,
|
|
86
|
+
g: sns.JointGrid,
|
|
87
|
+
x_data: pd.Series,
|
|
88
|
+
y_data: pd.Series,
|
|
89
|
+
color_palette: Optional[Union[str, dict]],
|
|
90
|
+
):
|
|
91
|
+
"""
|
|
92
|
+
Adds a scatterplot layer to the KDE density plot.
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
g : sns.JointGrid
|
|
97
|
+
The seaborn JointGrid object to which the scatterplot is added.
|
|
98
|
+
x_data : pd.Series
|
|
99
|
+
The x-axis data.
|
|
100
|
+
y_data : pd.Series
|
|
101
|
+
The y-axis data.
|
|
102
|
+
color_palette : str or dict, optional
|
|
103
|
+
The color palette to use for the hue in the scatterplot.
|
|
104
|
+
|
|
105
|
+
Returns
|
|
106
|
+
-------
|
|
107
|
+
None
|
|
108
|
+
"""
|
|
109
|
+
sns.scatterplot(
|
|
110
|
+
data=self.coincidence_dataframe,
|
|
111
|
+
x=x_data,
|
|
112
|
+
y=y_data,
|
|
113
|
+
hue="Label",
|
|
114
|
+
palette=color_palette,
|
|
115
|
+
ax=g.ax_joint,
|
|
116
|
+
alpha=0.6,
|
|
117
|
+
zorder=1,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def _apply_axis_labels(
|
|
121
|
+
self,
|
|
122
|
+
g: sns.JointGrid,
|
|
123
|
+
feature: str,
|
|
124
|
+
x_units: str,
|
|
125
|
+
y_units: str) -> None:
|
|
126
|
+
"""
|
|
127
|
+
Sets the x and y labels with units on the plot.
|
|
128
|
+
|
|
129
|
+
Parameters
|
|
130
|
+
----------
|
|
131
|
+
g : sns.JointGrid
|
|
132
|
+
The seaborn JointGrid object.
|
|
133
|
+
feature : str
|
|
134
|
+
The feature being plotted.
|
|
135
|
+
x_units : str
|
|
136
|
+
Units of the x-axis data.
|
|
137
|
+
y_units : str
|
|
138
|
+
Units of the y-axis data.
|
|
139
|
+
|
|
140
|
+
Returns
|
|
141
|
+
-------
|
|
142
|
+
None
|
|
143
|
+
"""
|
|
144
|
+
name_0, name_1 = self.detector_names
|
|
145
|
+
g.ax_joint.set_xlabel(f"{feature} : {name_0} [{x_units:P}]")
|
|
146
|
+
g.ax_joint.set_ylabel(f"{feature}: {name_1} [{y_units:P}]")
|
|
147
|
+
|
|
148
|
+
def _apply_axis_limits(
|
|
149
|
+
self,
|
|
150
|
+
g: sns.JointGrid,
|
|
151
|
+
x_limits: Optional[Tuple],
|
|
152
|
+
y_limits: Optional[Tuple],
|
|
153
|
+
x_units: str,
|
|
154
|
+
y_units: str):
|
|
155
|
+
"""
|
|
156
|
+
Sets the axis limits if specified.
|
|
157
|
+
|
|
158
|
+
Parameters
|
|
159
|
+
----------
|
|
160
|
+
g : sns.JointGrid
|
|
161
|
+
The seaborn JointGrid object.
|
|
162
|
+
x_limits : tuple, optional
|
|
163
|
+
The x-axis limits (min, max), by default None.
|
|
164
|
+
y_limits : tuple, optional
|
|
165
|
+
The y-axis limits (min, max), by default None.
|
|
166
|
+
x_units : str
|
|
167
|
+
Units of the x-axis data.
|
|
168
|
+
y_units : str
|
|
169
|
+
Units of the y-axis data.
|
|
170
|
+
|
|
171
|
+
Returns
|
|
172
|
+
-------
|
|
173
|
+
None
|
|
174
|
+
"""
|
|
175
|
+
if x_limits:
|
|
176
|
+
x0, x1 = x_limits
|
|
177
|
+
x0 = x0.to(x_units).magnitude
|
|
178
|
+
x1 = x1.to(x_units).magnitude
|
|
179
|
+
g.ax_joint.set_xlim(x0, x1)
|
|
180
|
+
|
|
181
|
+
if y_limits:
|
|
182
|
+
y0, y1 = y_limits
|
|
183
|
+
y0 = y0.to(y_units).magnitude
|
|
184
|
+
y1 = y1.to(y_units).magnitude
|
|
185
|
+
g.ax_joint.set_ylim(y0, y1)
|
|
186
|
+
|
|
187
|
+
def plot(
|
|
188
|
+
self,
|
|
189
|
+
feature: str,
|
|
190
|
+
show: bool = True,
|
|
191
|
+
log_plot: bool = True,
|
|
192
|
+
x_limits: Optional[Tuple] = None,
|
|
193
|
+
y_limits: Optional[Tuple] = None,
|
|
194
|
+
equal_axes: bool = False,
|
|
195
|
+
bandwidth_adjust: float = 1.0,
|
|
196
|
+
color_palette: Optional[Union[str, dict]] = 'tab10') -> None:
|
|
197
|
+
"""
|
|
198
|
+
Generates a 2D density plot of the scattering intensities, overlaid with individual peak heights.
|
|
199
|
+
|
|
200
|
+
Parameters
|
|
201
|
+
----------
|
|
202
|
+
feature : str
|
|
203
|
+
The feature to plot (e.g., 'intensity').
|
|
204
|
+
show : bool, optional
|
|
205
|
+
Whether to display the plot immediately, by default True.
|
|
206
|
+
log_plot : bool, optional
|
|
207
|
+
Whether to use logarithmic scaling for the plot axes, by default True.
|
|
208
|
+
x_limits : tuple, optional
|
|
209
|
+
The x-axis limits (min, max), by default None.
|
|
210
|
+
y_limits : tuple, optional
|
|
211
|
+
The y-axis limits (min, max), by default None.
|
|
212
|
+
equal_axes : bool, optional
|
|
213
|
+
Whether to enforce the same range for the x and y axes, by default False.
|
|
214
|
+
bandwidth_adjust : float, optional
|
|
215
|
+
Bandwidth adjustment factor for the kernel density estimate of the marginal distributions. Default is 1.0.
|
|
216
|
+
color_palette : str or dict, optional
|
|
217
|
+
The color palette to use for the hue in the scatterplot.
|
|
218
|
+
|
|
219
|
+
Returns
|
|
220
|
+
-------
|
|
221
|
+
None
|
|
222
|
+
"""
|
|
223
|
+
x_data, y_data, x_units, y_units = self._extract_feature_data(feature)
|
|
224
|
+
|
|
225
|
+
# Determine equal axis limits if required
|
|
226
|
+
if equal_axes:
|
|
227
|
+
min_x = x_limits[0].to(x_units).magnitude if x_limits else x_data.min()
|
|
228
|
+
max_x = x_limits[1].to(x_units).magnitude if x_limits else x_data.max()
|
|
229
|
+
min_y = y_limits[0].to(y_units).magnitude if y_limits else y_data.min()
|
|
230
|
+
max_y = y_limits[1].to(y_units).magnitude if y_limits else y_data.max()
|
|
231
|
+
|
|
232
|
+
# Set common limits
|
|
233
|
+
min_val = min(min_x, min_y)
|
|
234
|
+
max_val = max(max_x, max_y)
|
|
235
|
+
x_limits = (min_val, max_val)
|
|
236
|
+
y_limits = (min_val, max_val)
|
|
237
|
+
|
|
238
|
+
with plt.style.context(mps):
|
|
239
|
+
if not log_plot:
|
|
240
|
+
# KDE + Scatterplot for linear plots
|
|
241
|
+
g = self._create_density_plot(x_data, y_data, bandwidth_adjust)
|
|
242
|
+
self._add_scatterplot(g, x_data, y_data, color_palette)
|
|
243
|
+
self._apply_axis_labels(g, feature, x_units, y_units)
|
|
244
|
+
self._apply_axis_limits(g, x_limits, y_limits, x_units, y_units)
|
|
245
|
+
else:
|
|
246
|
+
# Scatterplot only for log-scaled plots
|
|
247
|
+
fig, ax = plt.subplots()
|
|
248
|
+
sns.scatterplot(
|
|
249
|
+
x=x_data,
|
|
250
|
+
y=y_data,
|
|
251
|
+
hue=self.coincidence_dataframe["Label"],
|
|
252
|
+
palette=color_palette,
|
|
253
|
+
alpha=0.6,
|
|
254
|
+
ax=ax,
|
|
255
|
+
)
|
|
256
|
+
ax.set_xscale("log")
|
|
257
|
+
ax.set_yscale("log")
|
|
258
|
+
ax.set_xlabel(f"{feature} : {self.detector_names[0]} [{x_units:P}]")
|
|
259
|
+
ax.set_ylabel(f"{feature} : {self.detector_names[1]} [{y_units:P}]")
|
|
260
|
+
if x_limits:
|
|
261
|
+
ax.set_xlim([lim.to(x_units).magnitude for lim in x_limits])
|
|
262
|
+
if y_limits:
|
|
263
|
+
ax.set_ylim([lim.to(y_units).magnitude for lim in y_limits])
|
|
264
|
+
ax.legend()
|
|
265
|
+
|
|
266
|
+
plt.tight_layout()
|
|
267
|
+
if show:
|
|
268
|
+
plt.show()
|
|
269
|
+
|
FlowCyPy/population.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import Union
|
|
3
|
+
from FlowCyPy import distribution
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from pydantic.dataclasses import dataclass
|
|
6
|
+
from pydantic import field_validator
|
|
7
|
+
from FlowCyPy import units
|
|
8
|
+
from FlowCyPy.utils import PropertiesReport
|
|
9
|
+
from PyMieSim.units import Quantity, RIU, meter
|
|
10
|
+
from FlowCyPy.particle_count import ParticleCount
|
|
11
|
+
from pint_pandas import PintArray
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
config_dict = dict(
|
|
15
|
+
arbitrary_types_allowed=True,
|
|
16
|
+
kw_only=True,
|
|
17
|
+
slots=True,
|
|
18
|
+
extra='forbid'
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(config=config_dict)
|
|
23
|
+
class Population(PropertiesReport):
|
|
24
|
+
"""
|
|
25
|
+
A class representing a population of scatterers in a flow cytometry setup.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
name : str
|
|
30
|
+
Name of the population distribution.
|
|
31
|
+
refractive_index : Union[distribution.Base, Quantity]
|
|
32
|
+
Refractive index or refractive index distributions.
|
|
33
|
+
size : Union[distribution.Base, Quantity]
|
|
34
|
+
Particle size or size distributions.
|
|
35
|
+
particle_count : ParticleCount
|
|
36
|
+
Scatterer density in particles per cubic meter, default is 1 particle/m³.
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
name: str
|
|
40
|
+
refractive_index: Union[distribution.Base, Quantity]
|
|
41
|
+
size: Union[distribution.Base, Quantity]
|
|
42
|
+
particle_count: ParticleCount | Quantity
|
|
43
|
+
|
|
44
|
+
def __post_init__(self):
|
|
45
|
+
"""
|
|
46
|
+
Automatically converts all Quantity attributes to their base SI units (i.e., without any prefixes).
|
|
47
|
+
This strips units like millimeter to meter, kilogram to gram, etc.
|
|
48
|
+
"""
|
|
49
|
+
self.particle_count = ParticleCount(self.particle_count)
|
|
50
|
+
# Convert all Quantity attributes to base SI units (without any prefixes)
|
|
51
|
+
for attr_name, attr_value in vars(self).items():
|
|
52
|
+
if isinstance(attr_value, Quantity):
|
|
53
|
+
# Convert the quantity to its base unit (strip prefix)
|
|
54
|
+
setattr(self, attr_name, attr_value.to_base_units())
|
|
55
|
+
|
|
56
|
+
@field_validator('refractive_index')
|
|
57
|
+
def _validate_refractive_index(cls, value):
|
|
58
|
+
"""
|
|
59
|
+
Validates that the refractive index is either a Quantity or a valid distribution.Base instance.
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
value : Union[distribution.Base, Quantity]
|
|
64
|
+
The refractive index to validate.
|
|
65
|
+
|
|
66
|
+
Returns
|
|
67
|
+
-------
|
|
68
|
+
Union[distribution.Base, Quantity]
|
|
69
|
+
The validated refractive index.
|
|
70
|
+
|
|
71
|
+
Raises
|
|
72
|
+
------
|
|
73
|
+
TypeError
|
|
74
|
+
If the refractive index is not of type Quantity or distribution.Base.
|
|
75
|
+
"""
|
|
76
|
+
if isinstance(value, Quantity):
|
|
77
|
+
assert value.check(RIU), "The refractive index value provided does not have refractive index units [RIU]"
|
|
78
|
+
return distribution.Delta(position=value)
|
|
79
|
+
|
|
80
|
+
if isinstance(value, distribution.Base):
|
|
81
|
+
return value
|
|
82
|
+
|
|
83
|
+
raise TypeError(f"refractive_index must be of type Quantity<RIU or refractive_index_units> or distribution.Base, but got {type(value)}")
|
|
84
|
+
|
|
85
|
+
@field_validator('size')
|
|
86
|
+
def _validate_size(cls, value):
|
|
87
|
+
"""
|
|
88
|
+
Validates that the size is either a Quantity or a valid distribution.Base instance.
|
|
89
|
+
|
|
90
|
+
Parameters
|
|
91
|
+
----------
|
|
92
|
+
value : Union[distribution.Base, Quantity]
|
|
93
|
+
The size to validate.
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
Union[distribution.Base, Quantity]
|
|
98
|
+
The validated size.
|
|
99
|
+
|
|
100
|
+
Raises
|
|
101
|
+
------
|
|
102
|
+
TypeError
|
|
103
|
+
If the size is not of type Quantity or distribution.Base.
|
|
104
|
+
"""
|
|
105
|
+
if isinstance(value, Quantity):
|
|
106
|
+
assert value.check(meter), "The size value provided does not have length units [meter]"
|
|
107
|
+
return distribution.Delta(position=value)
|
|
108
|
+
|
|
109
|
+
if isinstance(value, distribution.Base):
|
|
110
|
+
return value
|
|
111
|
+
|
|
112
|
+
raise TypeError(f"suze must be of type Quantity or distribution.Base, but got {type(value)}")
|
|
113
|
+
|
|
114
|
+
def dilute(self, factor: float) -> None:
|
|
115
|
+
self.particle_count /= factor
|
|
116
|
+
|
|
117
|
+
def generate_sampling(self, sampling: Quantity) -> tuple:
|
|
118
|
+
size = self.size.generate(sampling)
|
|
119
|
+
|
|
120
|
+
ri = self.refractive_index.generate(sampling)
|
|
121
|
+
|
|
122
|
+
return size, ri
|
|
123
|
+
|
|
124
|
+
def _get_sampling(self, sampling: int) -> pd.DataFrame:
|
|
125
|
+
size = self.size.generate(sampling)
|
|
126
|
+
|
|
127
|
+
ri = self.refractive_index.generate(sampling)
|
|
128
|
+
|
|
129
|
+
dataframe = pd.DataFrame(columns=['Size', 'RefractiveIndex'])
|
|
130
|
+
|
|
131
|
+
dataframe['Size'] = PintArray(size, dtype=size.units)
|
|
132
|
+
dataframe['RefractiveIndex'] = PintArray(ri, dtype=ri.units)
|
|
133
|
+
|
|
134
|
+
return dataframe
|
|
135
|
+
|
|
136
|
+
from FlowCyPy.populations_instances import * # noqa F403
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from FlowCyPy.units import Quantity, nanometer, RIU, micrometer, particle
|
|
2
|
+
from FlowCyPy.population import Population
|
|
3
|
+
from FlowCyPy import distribution
|
|
4
|
+
|
|
5
|
+
class CallablePopulationMeta(type):
|
|
6
|
+
def __getattr__(cls, attr):
|
|
7
|
+
raise AttributeError(f"{cls.__name__} must be called as {cls.__name__}() to access its population instance.")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CallablePopulation(metaclass=CallablePopulationMeta):
|
|
11
|
+
def __init__(self, name, size_dist, ri_dist):
|
|
12
|
+
self._name = name
|
|
13
|
+
self._size_distribution = size_dist
|
|
14
|
+
self._ri_distribution = ri_dist
|
|
15
|
+
|
|
16
|
+
def __call__(self, particle_count: Quantity = 1 * particle):
|
|
17
|
+
return Population(
|
|
18
|
+
particle_count=particle_count,
|
|
19
|
+
name=self._name,
|
|
20
|
+
size=self._size_distribution,
|
|
21
|
+
refractive_index=self._ri_distribution,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Define populations
|
|
26
|
+
_populations = (
|
|
27
|
+
('Exosome', 70 * nanometer, 2.0, 1.39 * RIU, 0.02 * RIU),
|
|
28
|
+
('MicroVesicle', 400 * nanometer, 1.5, 1.39 * RIU, 0.02 * RIU),
|
|
29
|
+
('ApoptoticBodies', 2 * micrometer, 1.2, 1.40 * RIU, 0.03 * RIU),
|
|
30
|
+
('HDL', 10 * nanometer, 3.5, 1.33 * RIU, 0.01 * RIU),
|
|
31
|
+
('LDL', 20 * nanometer, 3.0, 1.35 * RIU, 0.02 * RIU),
|
|
32
|
+
('VLDL', 50 * nanometer, 2.0, 1.445 * RIU, 0.0005 * RIU),
|
|
33
|
+
('Platelet', 2000 * nanometer, 2.5, 1.38 * RIU, 0.01 * RIU),
|
|
34
|
+
('CellularDebris', 3 * micrometer, 1.0, 1.40 * RIU, 0.03 * RIU),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# Dynamically create population classes
|
|
38
|
+
for (name, size, size_spread, ri, ri_spread) in _populations:
|
|
39
|
+
size_distribution = distribution.RosinRammler(
|
|
40
|
+
characteristic_size=size,
|
|
41
|
+
spread=size_spread
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
ri_distribution = distribution.Normal(
|
|
45
|
+
mean=ri,
|
|
46
|
+
std_dev=ri_spread
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Create a class dynamically for each population
|
|
50
|
+
cls = type(name, (CallablePopulation,), {})
|
|
51
|
+
globals()[name] = cls(name, size_distribution, ri_distribution)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Helper function for microbeads
|
|
55
|
+
def get_microbeads(size: Quantity, refractive_index: Quantity, name: str) -> Population:
|
|
56
|
+
size_distribution = distribution.Delta(position=size)
|
|
57
|
+
ri_distribution = distribution.Delta(position=refractive_index)
|
|
58
|
+
|
|
59
|
+
microbeads = Population(
|
|
60
|
+
name=name,
|
|
61
|
+
size=size_distribution,
|
|
62
|
+
refractive_index=ri_distribution
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return microbeads
|