FlowCyPy 0.5.2__tar.gz → 0.5.6__tar.gz
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-0.5.2 → flowcypy-0.5.6}/FlowCyPy/__init__.py +1 -1
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/_version.py +2 -2
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/coupling_mechanism/empirical.py +2 -2
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/coupling_mechanism/mie.py +6 -6
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/coupling_mechanism/rayleigh.py +5 -5
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/coupling_mechanism/uniform.py +2 -2
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/cytometer.py +16 -16
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/distribution/base_class.py +6 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/distribution/delta.py +1 -8
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/distribution/lognormal.py +1 -8
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/distribution/normal.py +1 -8
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/distribution/particle_size_distribution.py +1 -8
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/distribution/uniform.py +1 -9
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/distribution/weibull.py +4 -9
- flowcypy-0.5.6/FlowCyPy/flow_cell.py +295 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/logger.py +2 -2
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/population.py +0 -104
- flowcypy-0.5.2/FlowCyPy/scatterer.py → flowcypy-0.5.6/FlowCyPy/scatterer_collection.py +19 -89
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy.egg-info/PKG-INFO +7 -7
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy.egg-info/SOURCES.txt +18 -1
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy.egg-info/requires.txt +1 -1
- {flowcypy-0.5.2 → flowcypy-0.5.6}/PKG-INFO +7 -7
- {flowcypy-0.5.2 → flowcypy-0.5.6}/README.rst +5 -5
- flowcypy-0.5.6/developments/create_images.py +42 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/developments/dev_study_on_ri.py +8 -7
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/density_plots/1_populations.py +12 -13
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/density_plots/2_populations.py +16 -17
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/density_plots/3_populations.py +13 -14
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/density_plots/custom_populations.py +14 -15
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/extras/flow_cytometer_signal.py +16 -17
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/extras/full_workflow.py +18 -19
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/extras/scatterer_distribution.py +3 -3
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/tutorials/workflow.py +25 -24
- flowcypy-0.5.6/docs/images/distributions/Delta.png +0 -0
- flowcypy-0.5.6/docs/images/distributions/LogNormal.png +0 -0
- flowcypy-0.5.6/docs/images/distributions/Normal.png +0 -0
- flowcypy-0.5.6/docs/images/distributions/RosinRammler.png +0 -0
- flowcypy-0.5.6/docs/images/distributions/Uniform.png +0 -0
- flowcypy-0.5.6/docs/images/distributions/Weibull.png +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/_static/default.css +1 -1
- flowcypy-0.5.6/docs/source/_static/thumbnail.png +0 -0
- flowcypy-0.5.6/docs/source/code/analysis.rst +10 -0
- flowcypy-0.5.6/docs/source/code/base.rst +18 -0
- flowcypy-0.5.6/docs/source/code/detector.rst +11 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/code/distributions.rst +26 -0
- flowcypy-0.5.6/docs/source/code/flow_cell.rst +9 -0
- flowcypy-0.5.6/docs/source/code/flow_cytometer.rst +9 -0
- flowcypy-0.5.6/docs/source/code/scatterer.rst +15 -0
- flowcypy-0.5.6/docs/source/code/source.rst +15 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/conf.py +6 -3
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/examples.rst +4 -3
- flowcypy-0.5.6/docs/source/internal/core_components.rst +95 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/internal/getting_started.rst +2 -2
- flowcypy-0.5.6/docs/source/internal/objectives/main.rst +83 -0
- flowcypy-0.5.6/docs/source/internal/objectives/pre.rst +31 -0
- flowcypy-0.5.6/docs/source/internal/objectives/stretch.rst +9 -0
- flowcypy-0.5.6/docs/source/internal/prerequisites/index.rst +12 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/internal/prerequisites/mathematics.rst +2 -2
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/internal/prerequisites/optics.rst +2 -2
- flowcypy-0.5.6/docs/source/internal/prerequisites/programming.rst +91 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/internal/ressources.rst +2 -2
- flowcypy-0.5.6/docs/source/internal/tasks.rst +15 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/internal.rst +7 -10
- flowcypy-0.5.6/docs/source/sg_execution_times.rst +70 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/pyproject.toml +1 -1
- {flowcypy-0.5.2 → flowcypy-0.5.6}/tests/test_coupling_mechanism.py +5 -5
- {flowcypy-0.5.2 → flowcypy-0.5.6}/tests/test_distribution.py +0 -2
- {flowcypy-0.5.2 → flowcypy-0.5.6}/tests/test_flow_cytometer.py +17 -18
- {flowcypy-0.5.2 → flowcypy-0.5.6}/tests/test_peak_analyzer.py +14 -17
- {flowcypy-0.5.2 → flowcypy-0.5.6}/tests/test_population.py +14 -11
- {flowcypy-0.5.2 → flowcypy-0.5.6}/tests/test_scatterer_distribution.py +9 -9
- flowcypy-0.5.2/FlowCyPy/flow_cell.py +0 -122
- flowcypy-0.5.2/docs/source/_static/thumbnail.png +0 -0
- flowcypy-0.5.2/docs/source/code/base.rst +0 -90
- flowcypy-0.5.2/docs/source/internal/core_components.rst +0 -32
- flowcypy-0.5.2/docs/source/internal/prerequisites/index.rst +0 -12
- flowcypy-0.5.2/docs/source/internal/prerequisites/programming.rst +0 -41
- flowcypy-0.5.2/docs/source/internal/tasks.rst +0 -136
- {flowcypy-0.5.2 → flowcypy-0.5.6}/.condarc +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/.flake8 +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/.github/dependabot.yml +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/.github/workflows/deploy_PyPi.yml +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/.github/workflows/deploy_anaconda.yml +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/.github/workflows/deploy_coverage.yml +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/.github/workflows/deploy_documentation.yml +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/.gitignore +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/.readthedocs.yml +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/classifier.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/coupling_mechanism/__init__.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/detector.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/directories.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/distribution/__init__.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/event_correlator.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/helper.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/noises.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/particle_count.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/peak_locator/__init__.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/peak_locator/base_class.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/peak_locator/basic.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/peak_locator/derivative.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/peak_locator/moving_average.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/physical_constant.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/plottings.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/populations_instances.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/report.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/source.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/units.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/utils.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy.egg-info/dependency_links.txt +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy.egg-info/top_level.txt +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/LICENSE +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/Untitled.ipynb +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/developments/dev_beads_analysis.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/developments/dev_canto.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/developments/dev_classifier.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/developments/dev_shot_noise_check.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/developments/dev_study_on_size.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/developments/get_started.md +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/developments/image.png +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/developments/internship.pdf +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/developments/output_file.prof +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/developments/profiler.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/developments/test.pdf +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/Makefile +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/density_plots/README.rst +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/extras/README.rst +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/extras/distributions.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/noise_sources/README.rst +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/noise_sources/dark_current.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/noise_sources/shot_noise.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/noise_sources/thermal.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/tutorials/README.rst +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/images/example_0.png +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/images/example_1.png +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/images/example_2.png +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/images/example_3.png +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/images/flow_cytometer.png +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/images/logo.png +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/make.bat +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/_static/logo.png +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/code/peak_locator.rst +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/code.rst +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/index.rst +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/references.rst +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/theory.rst +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/meta.yaml +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/notebook.ipynb +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/setup.cfg +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/tests/__init__.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/tests/test_detector_noise.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/tests/test_extra.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/tests/test_noises.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/tests/test_peak_algorithm.py +0 -0
- {flowcypy-0.5.2 → flowcypy-0.5.6}/tests/test_source.py +0 -0
|
@@ -7,7 +7,7 @@ except ImportError:
|
|
|
7
7
|
from .units import ureg, watt, meter, second, liter, particle
|
|
8
8
|
from .cytometer import FlowCytometer
|
|
9
9
|
from .event_correlator import EventCorrelator
|
|
10
|
-
from .
|
|
10
|
+
from .scatterer_collection import ScattererCollection, CouplingModel
|
|
11
11
|
from .population import Population
|
|
12
12
|
from .detector import Detector
|
|
13
13
|
from .flow_cell import FlowCell
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import numpy as np
|
|
2
|
-
from FlowCyPy import
|
|
2
|
+
from FlowCyPy import ScattererCollection, Detector
|
|
3
3
|
from FlowCyPy.source import BaseBeam
|
|
4
4
|
from FlowCyPy.units import watt, meter
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
def compute_detected_signal(source: BaseBeam, detector: Detector, scatterer:
|
|
7
|
+
def compute_detected_signal(source: BaseBeam, detector: Detector, scatterer: ScattererCollection, granularity: float = 1.0, A: float = 1.5, n: float = 2.0) -> float:
|
|
8
8
|
"""
|
|
9
9
|
Empirical model for scattering intensity based on particle size, granularity, and detector angle.
|
|
10
10
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import numpy as np
|
|
2
|
-
from FlowCyPy import
|
|
2
|
+
from FlowCyPy import ScattererCollection, Detector
|
|
3
3
|
from FlowCyPy.source import BaseBeam
|
|
4
4
|
from PyMieSim.experiment.scatterer import Sphere as PMS_SPHERE
|
|
5
5
|
from PyMieSim.experiment.source import PlaneWave
|
|
@@ -94,13 +94,13 @@ def apply_rin_noise(source: BaseBeam, total_size: int, bandwidth: float) -> np.n
|
|
|
94
94
|
return amplitude_with_rin
|
|
95
95
|
|
|
96
96
|
|
|
97
|
-
def initialize_scatterer(scatterer:
|
|
97
|
+
def initialize_scatterer(scatterer: ScattererCollection, source: PlaneWave) -> PMS_SPHERE:
|
|
98
98
|
"""
|
|
99
99
|
Initializes the scatterer object for the PyMieSim experiment.
|
|
100
100
|
|
|
101
101
|
Parameters
|
|
102
102
|
----------
|
|
103
|
-
scatterer :
|
|
103
|
+
scatterer : ScattererCollection
|
|
104
104
|
The scatterer object containing particle data.
|
|
105
105
|
source : PlaneWave
|
|
106
106
|
The light source for the simulation.
|
|
@@ -114,7 +114,7 @@ def initialize_scatterer(scatterer: Scatterer, source: PlaneWave) -> PMS_SPHERE:
|
|
|
114
114
|
ri_list = scatterer.dataframe['RefractiveIndex'].values
|
|
115
115
|
|
|
116
116
|
if len(size_list) == 0:
|
|
117
|
-
raise ValueError("
|
|
117
|
+
raise ValueError("ScattererCollection size list is empty.")
|
|
118
118
|
|
|
119
119
|
size_list = size_list.quantity.magnitude * size_list.units
|
|
120
120
|
ri_list = ri_list.quantity.magnitude * ri_list.units
|
|
@@ -155,7 +155,7 @@ def initialize_detector(detector: Detector, total_size: int) -> PMS_PHOTODIODE:
|
|
|
155
155
|
)
|
|
156
156
|
|
|
157
157
|
|
|
158
|
-
def compute_detected_signal(source: BaseBeam, detector: Detector, scatterer:
|
|
158
|
+
def compute_detected_signal(source: BaseBeam, detector: Detector, scatterer: ScattererCollection, tolerance: float = 1e-5) -> np.ndarray:
|
|
159
159
|
"""
|
|
160
160
|
Computes the detected signal by analyzing the scattering properties of particles.
|
|
161
161
|
|
|
@@ -165,7 +165,7 @@ def compute_detected_signal(source: BaseBeam, detector: Detector, scatterer: Sca
|
|
|
165
165
|
The light source object containing wavelength, power, and other optical properties.
|
|
166
166
|
detector : Detector
|
|
167
167
|
The detector object containing properties such as numerical aperture and angles.
|
|
168
|
-
scatterer :
|
|
168
|
+
scatterer : ScattererCollection
|
|
169
169
|
The scatterer object containing particle size and refractive index data.
|
|
170
170
|
tolerance : float, optional
|
|
171
171
|
The tolerance for deciding if two values of size and refractive index are "close enough" to be cached.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
|
|
2
2
|
import numpy as np
|
|
3
|
-
from FlowCyPy import
|
|
3
|
+
from FlowCyPy import ScattererCollection, Detector
|
|
4
4
|
from FlowCyPy.source import BaseBeam
|
|
5
5
|
from FlowCyPy.units import meter
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
def compute_scattering_cross_section(scatterer:
|
|
8
|
+
def compute_scattering_cross_section(scatterer: ScattererCollection, source: BaseBeam, detector: Detector) -> np.ndarray:
|
|
9
9
|
r"""
|
|
10
10
|
Computes the Rayleigh scattering cross-section for a spherical particle with angle dependency.
|
|
11
11
|
|
|
@@ -28,8 +28,8 @@ def compute_scattering_cross_section(scatterer: Scatterer, source: BaseBeam, det
|
|
|
28
28
|
|
|
29
29
|
Parameters
|
|
30
30
|
----------
|
|
31
|
-
scatterer :
|
|
32
|
-
An instance of `
|
|
31
|
+
scatterer : ScattererCollection
|
|
32
|
+
An instance of `ScattererCollection` containing the scatterer properties such as size and refractive index.
|
|
33
33
|
source : BaseBeam
|
|
34
34
|
An instance of `BaseBeam` containing the laser properties, including the wavelength.
|
|
35
35
|
detector : Detector
|
|
@@ -63,7 +63,7 @@ def compute_scattering_cross_section(scatterer: Scatterer, source: BaseBeam, det
|
|
|
63
63
|
return cross_section.magnitude * meter**2
|
|
64
64
|
|
|
65
65
|
|
|
66
|
-
def compute_detected_signal(source: BaseBeam, detector: Detector, scatterer:
|
|
66
|
+
def compute_detected_signal(source: BaseBeam, detector: Detector, scatterer: ScattererCollection) -> float:
|
|
67
67
|
r"""
|
|
68
68
|
Computes the power detected by a detector from a Rayleigh scattering event.
|
|
69
69
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import numpy as np
|
|
2
|
-
from FlowCyPy import
|
|
2
|
+
from FlowCyPy import ScattererCollection, Detector, ureg
|
|
3
3
|
from FlowCyPy.source import BaseBeam
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
def compute_detected_signal(source: BaseBeam, detector: Detector, scatterer:
|
|
6
|
+
def compute_detected_signal(source: BaseBeam, detector: Detector, scatterer: ScattererCollection) -> np.ndarray:
|
|
7
7
|
r"""
|
|
8
8
|
Computes the power detected by a detector from a uniform distribution.
|
|
9
9
|
|
|
@@ -6,7 +6,7 @@ import numpy as np
|
|
|
6
6
|
import matplotlib.pyplot as plt
|
|
7
7
|
from typing import List, Callable, Optional
|
|
8
8
|
from MPSPlots.styles import mps
|
|
9
|
-
from FlowCyPy.
|
|
9
|
+
from FlowCyPy.flow_cell import FlowCell
|
|
10
10
|
from FlowCyPy.detector import Detector
|
|
11
11
|
from FlowCyPy.source import GaussianBeam
|
|
12
12
|
import pandas as pd
|
|
@@ -30,8 +30,8 @@ class FlowCytometer:
|
|
|
30
30
|
|
|
31
31
|
Parameters
|
|
32
32
|
----------
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
flow_cell : FlowCell
|
|
34
|
+
Void
|
|
35
35
|
source : GaussianBeam
|
|
36
36
|
The laser source object representing the illumination scheme.
|
|
37
37
|
detectors : List[Detector]
|
|
@@ -40,14 +40,14 @@ class FlowCytometer:
|
|
|
40
40
|
"""
|
|
41
41
|
def __init__(
|
|
42
42
|
self,
|
|
43
|
-
|
|
44
|
-
source: GaussianBeam,
|
|
43
|
+
flow_cell: FlowCell,
|
|
45
44
|
detectors: List[Detector],
|
|
46
45
|
coupling_mechanism: Optional[str] = 'mie',
|
|
47
46
|
background_power: Optional[Quantity] = 0 * milliwatt):
|
|
48
47
|
|
|
49
|
-
self.
|
|
50
|
-
self.
|
|
48
|
+
self.flow_cell = flow_cell
|
|
49
|
+
self.scatterer_collection = flow_cell.scatterer_collection
|
|
50
|
+
self.source = flow_cell.source
|
|
51
51
|
self.detectors = detectors
|
|
52
52
|
self.coupling_mechanism = coupling_mechanism
|
|
53
53
|
self.background_power = background_power
|
|
@@ -70,25 +70,25 @@ class FlowCytometer:
|
|
|
70
70
|
|
|
71
71
|
self._generate_pulse_parameters()
|
|
72
72
|
|
|
73
|
-
_widths = self.
|
|
74
|
-
_centers = self.
|
|
73
|
+
_widths = self.scatterer_collection.dataframe['Widths'].values
|
|
74
|
+
_centers = self.scatterer_collection.dataframe['Time'].values
|
|
75
75
|
|
|
76
76
|
detection_mechanism = self._get_detection_mechanism()
|
|
77
77
|
|
|
78
78
|
# Initialize the detectors
|
|
79
79
|
for detector in self.detectors:
|
|
80
80
|
detector.source = self.source
|
|
81
|
-
detector.init_raw_signal(run_time=self.
|
|
81
|
+
detector.init_raw_signal(run_time=self.flow_cell.run_time)
|
|
82
82
|
|
|
83
83
|
# Fetch the coupling power for each scatterer
|
|
84
84
|
for detector in self.detectors:
|
|
85
85
|
coupling_power = detection_mechanism(
|
|
86
86
|
source=self.source,
|
|
87
87
|
detector=detector,
|
|
88
|
-
scatterer=self.
|
|
88
|
+
scatterer=self.scatterer_collection
|
|
89
89
|
)
|
|
90
90
|
|
|
91
|
-
self.
|
|
91
|
+
self.scatterer_collection.dataframe['CouplingPower'] = pint_pandas.PintArray(coupling_power, dtype=coupling_power.units)
|
|
92
92
|
|
|
93
93
|
for detector in self.detectors:
|
|
94
94
|
# Generate noise components
|
|
@@ -160,11 +160,11 @@ class FlowCytometer:
|
|
|
160
160
|
width : np.ndarray
|
|
161
161
|
The width of the pulse (standard deviation of the Gaussian, in seconds).
|
|
162
162
|
"""
|
|
163
|
-
self.pulse_dataframe['Centers'] = self.
|
|
163
|
+
self.pulse_dataframe['Centers'] = self.scatterer_collection.dataframe['Time']
|
|
164
164
|
|
|
165
|
-
widths = self.source.waist / self.
|
|
165
|
+
widths = self.source.waist / self.flow_cell.flow_speed * np.ones(self.scatterer_collection.n_events)
|
|
166
166
|
|
|
167
|
-
self.
|
|
167
|
+
self.scatterer_collection.dataframe['Widths'] = pint_pandas.PintArray(widths, dtype=widths.units)
|
|
168
168
|
|
|
169
169
|
def plot(self, figure_size: tuple = (10, 6), add_peak_locator: bool = False) -> None:
|
|
170
170
|
"""Plots the signals generated for each detector channel."""
|
|
@@ -179,7 +179,7 @@ class FlowCytometer:
|
|
|
179
179
|
self.detectors[1].plot(ax=axes[1], show=False, time_unit=time_unit, signal_unit=signal_unit, add_peak_locator=add_peak_locator)
|
|
180
180
|
|
|
181
181
|
axes[-1].get_yaxis().set_visible(False)
|
|
182
|
-
self.
|
|
182
|
+
self.scatterer_collection.add_to_ax(axes[-1])
|
|
183
183
|
|
|
184
184
|
# Add legends to each subplot
|
|
185
185
|
for ax in axes:
|
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
|
|
2
|
-
from FlowCyPy.distribution.base_class import Base
|
|
2
|
+
from FlowCyPy.distribution.base_class import Base, config_dict
|
|
3
3
|
import numpy as np
|
|
4
4
|
from typing import Tuple
|
|
5
5
|
from PyMieSim.units import Quantity
|
|
6
6
|
from pydantic.dataclasses import dataclass
|
|
7
7
|
|
|
8
|
-
config_dict = dict(
|
|
9
|
-
arbitrary_types_allowed=True,
|
|
10
|
-
kw_only=True,
|
|
11
|
-
slots=True,
|
|
12
|
-
extra='forbid'
|
|
13
|
-
)
|
|
14
|
-
|
|
15
8
|
|
|
16
9
|
@dataclass(config=config_dict)
|
|
17
10
|
class Delta(Base):
|
|
@@ -1,17 +1,10 @@
|
|
|
1
|
-
from FlowCyPy.distribution.base_class import Base
|
|
1
|
+
from FlowCyPy.distribution.base_class import Base, config_dict
|
|
2
2
|
import numpy as np
|
|
3
3
|
from typing import Tuple
|
|
4
4
|
from scipy.stats import lognorm
|
|
5
5
|
from PyMieSim.units import Quantity
|
|
6
6
|
from pydantic.dataclasses import dataclass
|
|
7
7
|
|
|
8
|
-
config_dict = dict(
|
|
9
|
-
arbitrary_types_allowed=True,
|
|
10
|
-
kw_only=True,
|
|
11
|
-
slots=True,
|
|
12
|
-
extra='forbid'
|
|
13
|
-
)
|
|
14
|
-
|
|
15
8
|
|
|
16
9
|
@dataclass(config=config_dict)
|
|
17
10
|
class LogNormal(Base):
|
|
@@ -1,17 +1,10 @@
|
|
|
1
|
-
from FlowCyPy.distribution.base_class import Base
|
|
1
|
+
from FlowCyPy.distribution.base_class import Base, config_dict
|
|
2
2
|
import numpy as np
|
|
3
3
|
from typing import Tuple
|
|
4
4
|
from scipy.stats import norm
|
|
5
5
|
from PyMieSim.units import Quantity
|
|
6
6
|
from pydantic.dataclasses import dataclass
|
|
7
7
|
|
|
8
|
-
config_dict = dict(
|
|
9
|
-
arbitrary_types_allowed=True,
|
|
10
|
-
kw_only=True,
|
|
11
|
-
slots=True,
|
|
12
|
-
extra='forbid'
|
|
13
|
-
)
|
|
14
|
-
|
|
15
8
|
|
|
16
9
|
@dataclass(config=config_dict)
|
|
17
10
|
class Normal(Base):
|
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
from FlowCyPy.distribution.base_class import Base
|
|
1
|
+
from FlowCyPy.distribution.base_class import Base, config_dict
|
|
2
2
|
import numpy as np
|
|
3
3
|
from typing import Tuple
|
|
4
4
|
from PyMieSim.units import Quantity
|
|
5
5
|
from pydantic.dataclasses import dataclass
|
|
6
6
|
|
|
7
|
-
config_dict = dict(
|
|
8
|
-
arbitrary_types_allowed=True,
|
|
9
|
-
kw_only=True,
|
|
10
|
-
slots=True,
|
|
11
|
-
extra='forbid'
|
|
12
|
-
)
|
|
13
|
-
|
|
14
7
|
|
|
15
8
|
@dataclass(config=config_dict)
|
|
16
9
|
class RosinRammler(Base):
|
|
@@ -1,18 +1,10 @@
|
|
|
1
|
-
from FlowCyPy.distribution.base_class import Base
|
|
1
|
+
from FlowCyPy.distribution.base_class import Base, config_dict
|
|
2
2
|
import numpy as np
|
|
3
3
|
from typing import Tuple
|
|
4
4
|
from scipy.stats import uniform
|
|
5
5
|
from PyMieSim.units import Quantity
|
|
6
6
|
from pydantic.dataclasses import dataclass
|
|
7
7
|
|
|
8
|
-
config_dict = dict(
|
|
9
|
-
arbitrary_types_allowed=True,
|
|
10
|
-
kw_only=True,
|
|
11
|
-
slots=True,
|
|
12
|
-
extra='forbid'
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
|
|
16
8
|
@dataclass(config=config_dict)
|
|
17
9
|
class Uniform(Base):
|
|
18
10
|
r"""
|
|
@@ -1,18 +1,10 @@
|
|
|
1
|
+
from FlowCyPy.distribution.base_class import Base, config_dict
|
|
1
2
|
import numpy as np
|
|
2
3
|
from typing import Tuple
|
|
3
4
|
from PyMieSim.units import Quantity
|
|
4
|
-
from FlowCyPy.distribution.base_class import Base
|
|
5
5
|
from pydantic.dataclasses import dataclass
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
config_dict = dict(
|
|
9
|
-
arbitrary_types_allowed=True,
|
|
10
|
-
kw_only=True,
|
|
11
|
-
slots=True,
|
|
12
|
-
extra='forbid'
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
|
|
16
8
|
@dataclass(config=config_dict)
|
|
17
9
|
class Weibull(Base):
|
|
18
10
|
r"""
|
|
@@ -78,3 +70,6 @@ class Weibull(Base):
|
|
|
78
70
|
pdf = a * b ** (self.shape - 1) * c
|
|
79
71
|
|
|
80
72
|
return x, self.scale_factor * pdf
|
|
73
|
+
|
|
74
|
+
def __repr__(self) -> str:
|
|
75
|
+
return f"Weibull({self.scale:.2f~P}, {self.shape:.2f~P})"
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
from FlowCyPy.units import meter, second, particle
|
|
3
|
+
|
|
4
|
+
from PyMieSim.units import Quantity
|
|
5
|
+
from tabulate import tabulate
|
|
6
|
+
from pydantic.dataclasses import dataclass
|
|
7
|
+
from pydantic import field_validator
|
|
8
|
+
from pint_pandas import PintType, PintArray
|
|
9
|
+
from FlowCyPy.source import BaseBeam
|
|
10
|
+
from FlowCyPy.population import Population
|
|
11
|
+
from FlowCyPy.scatterer_collection import ScattererCollection
|
|
12
|
+
import pandas
|
|
13
|
+
import numpy
|
|
14
|
+
import warnings
|
|
15
|
+
|
|
16
|
+
config_dict = dict(
|
|
17
|
+
arbitrary_types_allowed=True,
|
|
18
|
+
kw_only=True,
|
|
19
|
+
slots=True,
|
|
20
|
+
extra='forbid'
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(config=config_dict)
|
|
25
|
+
class FlowCell(object):
|
|
26
|
+
"""
|
|
27
|
+
Models the flow parameters in a flow cytometer, including flow speed, flow area,
|
|
28
|
+
and particle interactions. This class interacts with ScattererDistribution to simulate
|
|
29
|
+
the flow of particles through the cytometer.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
flow_speed : Quantity
|
|
34
|
+
The speed of the flow in meters per second (m/s).
|
|
35
|
+
flow_area : Quantity
|
|
36
|
+
The cross-sectional area of the flow tube in square meters (m²).
|
|
37
|
+
run_time : Quantity
|
|
38
|
+
The total duration of the flow simulation in seconds.
|
|
39
|
+
"""
|
|
40
|
+
flow_speed: Quantity
|
|
41
|
+
flow_area: Quantity
|
|
42
|
+
run_time: Quantity
|
|
43
|
+
|
|
44
|
+
source: BaseBeam = None
|
|
45
|
+
|
|
46
|
+
def __post_init__(self):
|
|
47
|
+
"""Initialize units for flow parameters."""
|
|
48
|
+
self.flow_speed = Quantity(self.flow_speed, meter / second)
|
|
49
|
+
self.flow_area = Quantity(self.flow_area, meter ** 2)
|
|
50
|
+
self.run_time = Quantity(self.run_time, second)
|
|
51
|
+
|
|
52
|
+
self.volume = self.flow_area * self.flow_speed * self.run_time
|
|
53
|
+
|
|
54
|
+
@field_validator('flow_speed')
|
|
55
|
+
def _validate_flow_speed(cls, value):
|
|
56
|
+
"""
|
|
57
|
+
Validates that the flow speed is provided in meter per second.
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
value : Quantity
|
|
62
|
+
The flow speed to validate.
|
|
63
|
+
|
|
64
|
+
Returns
|
|
65
|
+
-------
|
|
66
|
+
Quantity
|
|
67
|
+
The flow speed frequency.
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
ValueError: If the flow speed is not in meter per second.
|
|
71
|
+
"""
|
|
72
|
+
if not value.check(meter / second):
|
|
73
|
+
raise ValueError(f"flow_speed must be in meter per second, but got {value.units}")
|
|
74
|
+
return value
|
|
75
|
+
|
|
76
|
+
@field_validator('flow_area')
|
|
77
|
+
def _validate_flow_area(cls, value):
|
|
78
|
+
"""
|
|
79
|
+
Validates that the flow area is provided in hertz.
|
|
80
|
+
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
value : Quantity
|
|
84
|
+
The flow area to validate.
|
|
85
|
+
|
|
86
|
+
Returns
|
|
87
|
+
-------
|
|
88
|
+
Quantity
|
|
89
|
+
The validated flow area.
|
|
90
|
+
|
|
91
|
+
Raises:
|
|
92
|
+
ValueError: If the flow area is not in hertz.
|
|
93
|
+
"""
|
|
94
|
+
if not value.check(meter ** 2):
|
|
95
|
+
raise ValueError(f"flow_area must be in meter ** 2, but got {value.units}")
|
|
96
|
+
return value
|
|
97
|
+
|
|
98
|
+
@field_validator('run_time')
|
|
99
|
+
def _validate_run_time(cls, value):
|
|
100
|
+
"""
|
|
101
|
+
Validates that the total time is provided in second.
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
value : Quantity
|
|
106
|
+
The total time to validate.
|
|
107
|
+
|
|
108
|
+
Returns
|
|
109
|
+
-------
|
|
110
|
+
Quantity
|
|
111
|
+
The validated total time.
|
|
112
|
+
|
|
113
|
+
Raises:
|
|
114
|
+
ValueError: If the total time is not in second.
|
|
115
|
+
"""
|
|
116
|
+
if not value.check(second):
|
|
117
|
+
raise ValueError(f"run_time must be in second, but got {value.units}")
|
|
118
|
+
return value
|
|
119
|
+
|
|
120
|
+
def print_properties(self) -> None:
|
|
121
|
+
"""
|
|
122
|
+
Print the core properties of the flow and particle interactions in the flow cytometer.
|
|
123
|
+
"""
|
|
124
|
+
print("\nFlow Properties")
|
|
125
|
+
print(tabulate(self.get_properties(), headers=["Property", "Value"], tablefmt="grid"))
|
|
126
|
+
|
|
127
|
+
def get_properties(self) -> List[List[str]]:
|
|
128
|
+
return [
|
|
129
|
+
['Flow Speed', f"{self.flow_speed:.2f~#P}"],
|
|
130
|
+
['Flow Area', f"{self.flow_area:.2f~#P}"],
|
|
131
|
+
['Total Time', f"{self.run_time:.2f~#P}"]
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
# def initialize(self, scatterer: Population | ScattererCollection) -> None:
|
|
135
|
+
# if isinstance(scatterer, Population):
|
|
136
|
+
# return self._initialize_population(scatterer)
|
|
137
|
+
|
|
138
|
+
# elif isinstance(scatterer, ScattererCollection):
|
|
139
|
+
# return self._initialize_scatterer_collection(scatterer)
|
|
140
|
+
|
|
141
|
+
def _initialize_population(self, population: Population) -> None:
|
|
142
|
+
population.dataframe = pandas.DataFrame()
|
|
143
|
+
|
|
144
|
+
population.n_events = population.particle_count.calculate_number_of_events(
|
|
145
|
+
flow_area=self.flow_area,
|
|
146
|
+
flow_speed=self.flow_speed,
|
|
147
|
+
run_time=self.run_time
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
self._generate_longitudinal_positions(population)
|
|
151
|
+
|
|
152
|
+
size = population.size.generate(population.n_events)
|
|
153
|
+
population.dataframe['Size'] = PintArray(size, dtype=size.units)
|
|
154
|
+
|
|
155
|
+
ri = population.refractive_index.generate(population.n_events)
|
|
156
|
+
population.dataframe['RefractiveIndex'] = PintArray(ri, dtype=ri.units)
|
|
157
|
+
|
|
158
|
+
def initialize(self, scatterer_collection: ScattererCollection, size_units: str = 'micrometer') -> None:
|
|
159
|
+
"""
|
|
160
|
+
Initializes particle size, refractive index, and medium refractive index distributions.
|
|
161
|
+
|
|
162
|
+
Parameters
|
|
163
|
+
----------
|
|
164
|
+
scatterer : Scatterer
|
|
165
|
+
An instance of the Scatterer class that describes the scatterer collection being used.
|
|
166
|
+
|
|
167
|
+
"""
|
|
168
|
+
self.scatterer_collection = scatterer_collection
|
|
169
|
+
|
|
170
|
+
for population in self.scatterer_collection.populations:
|
|
171
|
+
self._initialize_population(population)
|
|
172
|
+
population.dataframe.Size = population.dataframe.Size.pint.to(size_units)
|
|
173
|
+
|
|
174
|
+
if len(self.scatterer_collection.populations) != 0:
|
|
175
|
+
self.scatterer_collection.dataframe = pandas.concat(
|
|
176
|
+
[population.dataframe for population in self.scatterer_collection.populations],
|
|
177
|
+
axis=0,
|
|
178
|
+
keys=[population.name for population in self.scatterer_collection.populations],
|
|
179
|
+
)
|
|
180
|
+
self.scatterer_collection.dataframe.index.names = ['Population', 'Index']
|
|
181
|
+
|
|
182
|
+
else:
|
|
183
|
+
dtypes = {
|
|
184
|
+
'Time': PintType('second'), # Time column with seconds unit
|
|
185
|
+
'Position': PintType('meter'), # Position column with meters unit
|
|
186
|
+
'Size': PintType('meter'), # Size column with micrometers unit
|
|
187
|
+
'RefractiveIndex': PintType('meter') # Dimensionless unit for refractive index
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
multi_index = pandas.MultiIndex.from_tuples([], names=["Population", "Index"])
|
|
191
|
+
|
|
192
|
+
# Create an empty DataFrame with specified column types and a multi-index
|
|
193
|
+
self.scatterer_collection.dataframe = pandas.DataFrame(
|
|
194
|
+
{col: pandas.Series(dtype=dtype) for col, dtype in dtypes.items()},
|
|
195
|
+
index=multi_index
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
self.scatterer_collection.n_events = len(self.scatterer_collection.dataframe)
|
|
199
|
+
|
|
200
|
+
def distribute_time_linearly(self, sequential_population: bool = False) -> None:
|
|
201
|
+
"""
|
|
202
|
+
Distributes particle arrival times linearly across the total runtime of the flow cell.
|
|
203
|
+
|
|
204
|
+
Optionally randomizes the order of times for all populations to simulate non-sequential particle arrivals.
|
|
205
|
+
|
|
206
|
+
Parameters
|
|
207
|
+
----------
|
|
208
|
+
sequential_population : bool, optional
|
|
209
|
+
If `True`, organize the order of arrival times across all populations (default is `False`).
|
|
210
|
+
|
|
211
|
+
"""
|
|
212
|
+
# Generate linearly spaced time values across the flow cell runtime
|
|
213
|
+
linear_spacing = numpy.linspace(0, self.run_time, self.n_events)
|
|
214
|
+
|
|
215
|
+
# Optionally randomize the linear spacing
|
|
216
|
+
if not sequential_population:
|
|
217
|
+
numpy.random.shuffle(linear_spacing)
|
|
218
|
+
|
|
219
|
+
# Assign the linearly spaced or randomized times to the scatterer DataFrame
|
|
220
|
+
self.scatterer_collectionscatterer.dataframe.Time = PintArray(linear_spacing, dtype=self.scatterer_collection.dataframe.Time.pint.units)
|
|
221
|
+
|
|
222
|
+
def _generate_longitudinal_positions(self, population: Population) -> None:
|
|
223
|
+
r"""
|
|
224
|
+
Generate particle arrival times over the entire experiment duration based on a Poisson process.
|
|
225
|
+
|
|
226
|
+
In flow cytometry, the particle arrival times can be modeled as a Poisson process, where the time
|
|
227
|
+
intervals between successive particle arrivals follow an exponential distribution. The average rate
|
|
228
|
+
of particle arrivals (the particle flux) is given by:
|
|
229
|
+
|
|
230
|
+
.. math::
|
|
231
|
+
\text{Particle Flux} = \rho \cdot v \cdot A
|
|
232
|
+
|
|
233
|
+
where:
|
|
234
|
+
- :math:`\rho` is the scatterer density (particles per cubic meter),
|
|
235
|
+
- :math:`v` is the flow speed (meters per second),
|
|
236
|
+
- :math:`A` is the cross-sectional area of the flow tube (square meters).
|
|
237
|
+
|
|
238
|
+
The number of particles arriving in a given time interval follows a Poisson distribution, and the
|
|
239
|
+
time between successive arrivals follows an exponential distribution. The mean inter-arrival time
|
|
240
|
+
is the inverse of the particle flux:
|
|
241
|
+
|
|
242
|
+
.. math::
|
|
243
|
+
\Delta t \sim \text{Exponential}(1/\lambda)
|
|
244
|
+
|
|
245
|
+
where:
|
|
246
|
+
- :math:`\Delta t` is the time between successive particle arrivals,
|
|
247
|
+
- :math:`\lambda` is the particle flux (particles per second).
|
|
248
|
+
|
|
249
|
+
Steps:
|
|
250
|
+
1. Compute the particle flux, which is the average number of particles passing through the detection
|
|
251
|
+
region per second.
|
|
252
|
+
2. Calculate the expected number of particles over the entire experiment duration.
|
|
253
|
+
3. Generate random inter-arrival times using the exponential distribution.
|
|
254
|
+
4. Compute the cumulative arrival times by summing the inter-arrival times.
|
|
255
|
+
5. Ensure that all arrival times fall within the total experiment duration.
|
|
256
|
+
|
|
257
|
+
Returns
|
|
258
|
+
-------
|
|
259
|
+
np.ndarray
|
|
260
|
+
An array of particle arrival times (in seconds) for the entire experiment duration, based on the Poisson process.
|
|
261
|
+
"""
|
|
262
|
+
# Step 1: Compute the average particle flux (particles per second)
|
|
263
|
+
particle_flux = population.particle_count.compute_particle_flux(
|
|
264
|
+
flow_speed=self.flow_speed,
|
|
265
|
+
flow_area=self.flow_area,
|
|
266
|
+
run_time=self.run_time
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# Step 2: Calculate the expected number of particles over the entire experiment
|
|
270
|
+
expected_particles = population.n_events
|
|
271
|
+
|
|
272
|
+
# Step 3: Generate inter-arrival times (exponentially distributed)
|
|
273
|
+
inter_arrival_times = numpy.random.exponential(
|
|
274
|
+
scale=1 / particle_flux.magnitude,
|
|
275
|
+
size=int(expected_particles.magnitude)
|
|
276
|
+
) / (particle_flux.units / particle)
|
|
277
|
+
|
|
278
|
+
# Step 4: Compute cumulative arrival times
|
|
279
|
+
arrival_times = numpy.cumsum(inter_arrival_times)
|
|
280
|
+
|
|
281
|
+
# Step 5: Limit the arrival times to the total experiment duration
|
|
282
|
+
arrival_times = arrival_times[arrival_times <= self.run_time]
|
|
283
|
+
|
|
284
|
+
time = arrival_times[arrival_times <= self.run_time]
|
|
285
|
+
|
|
286
|
+
population.dataframe['Time'] = PintArray(time, dtype=time.units)
|
|
287
|
+
|
|
288
|
+
position = arrival_times * self.flow_speed
|
|
289
|
+
|
|
290
|
+
population.dataframe['Position'] = PintArray(position, dtype=position.units)
|
|
291
|
+
|
|
292
|
+
population.n_events = len(arrival_times) * particle
|
|
293
|
+
|
|
294
|
+
if population.n_events == 0:
|
|
295
|
+
warnings.warn("Population has been initialized with 0 events.")
|
|
@@ -77,7 +77,7 @@ class EventCorrelatorLogger:
|
|
|
77
77
|
time_diffs = times.diff().dropna()
|
|
78
78
|
avg_time_between_peaks = f"{time_diffs.mean().to_compact():.4~P}"
|
|
79
79
|
min_time_between_peaks = f"{time_diffs.min().to_compact():.4~P}"
|
|
80
|
-
measured_concentration = num_events * particle / self.correlator.cytometer.
|
|
80
|
+
measured_concentration = num_events * particle / self.correlator.cytometer.flow_cell.volume.to(milliliter)
|
|
81
81
|
else:
|
|
82
82
|
avg_time_between_peaks = "N/A"
|
|
83
83
|
min_time_between_peaks = "N/A"
|
|
@@ -289,7 +289,7 @@ class SimulationLogger:
|
|
|
289
289
|
first_event_time = self._format_time(centers.min()) if num_events > 0 else "N/A"
|
|
290
290
|
last_event_time = self._format_time(centers.max()) if num_events > 0 else "N/A"
|
|
291
291
|
|
|
292
|
-
mean_event_rate = (num_events / self.cytometer.
|
|
292
|
+
mean_event_rate = (num_events / self.cytometer.flow_cell.run_time).to('Hz')
|
|
293
293
|
|
|
294
294
|
return [
|
|
295
295
|
detector.name,
|