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.
Files changed (154) hide show
  1. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/__init__.py +1 -1
  2. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/_version.py +2 -2
  3. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/coupling_mechanism/empirical.py +2 -2
  4. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/coupling_mechanism/mie.py +6 -6
  5. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/coupling_mechanism/rayleigh.py +5 -5
  6. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/coupling_mechanism/uniform.py +2 -2
  7. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/cytometer.py +16 -16
  8. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/distribution/base_class.py +6 -0
  9. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/distribution/delta.py +1 -8
  10. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/distribution/lognormal.py +1 -8
  11. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/distribution/normal.py +1 -8
  12. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/distribution/particle_size_distribution.py +1 -8
  13. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/distribution/uniform.py +1 -9
  14. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/distribution/weibull.py +4 -9
  15. flowcypy-0.5.6/FlowCyPy/flow_cell.py +295 -0
  16. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/logger.py +2 -2
  17. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/population.py +0 -104
  18. flowcypy-0.5.2/FlowCyPy/scatterer.py → flowcypy-0.5.6/FlowCyPy/scatterer_collection.py +19 -89
  19. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy.egg-info/PKG-INFO +7 -7
  20. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy.egg-info/SOURCES.txt +18 -1
  21. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy.egg-info/requires.txt +1 -1
  22. {flowcypy-0.5.2 → flowcypy-0.5.6}/PKG-INFO +7 -7
  23. {flowcypy-0.5.2 → flowcypy-0.5.6}/README.rst +5 -5
  24. flowcypy-0.5.6/developments/create_images.py +42 -0
  25. {flowcypy-0.5.2 → flowcypy-0.5.6}/developments/dev_study_on_ri.py +8 -7
  26. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/density_plots/1_populations.py +12 -13
  27. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/density_plots/2_populations.py +16 -17
  28. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/density_plots/3_populations.py +13 -14
  29. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/density_plots/custom_populations.py +14 -15
  30. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/extras/flow_cytometer_signal.py +16 -17
  31. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/extras/full_workflow.py +18 -19
  32. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/extras/scatterer_distribution.py +3 -3
  33. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/tutorials/workflow.py +25 -24
  34. flowcypy-0.5.6/docs/images/distributions/Delta.png +0 -0
  35. flowcypy-0.5.6/docs/images/distributions/LogNormal.png +0 -0
  36. flowcypy-0.5.6/docs/images/distributions/Normal.png +0 -0
  37. flowcypy-0.5.6/docs/images/distributions/RosinRammler.png +0 -0
  38. flowcypy-0.5.6/docs/images/distributions/Uniform.png +0 -0
  39. flowcypy-0.5.6/docs/images/distributions/Weibull.png +0 -0
  40. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/_static/default.css +1 -1
  41. flowcypy-0.5.6/docs/source/_static/thumbnail.png +0 -0
  42. flowcypy-0.5.6/docs/source/code/analysis.rst +10 -0
  43. flowcypy-0.5.6/docs/source/code/base.rst +18 -0
  44. flowcypy-0.5.6/docs/source/code/detector.rst +11 -0
  45. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/code/distributions.rst +26 -0
  46. flowcypy-0.5.6/docs/source/code/flow_cell.rst +9 -0
  47. flowcypy-0.5.6/docs/source/code/flow_cytometer.rst +9 -0
  48. flowcypy-0.5.6/docs/source/code/scatterer.rst +15 -0
  49. flowcypy-0.5.6/docs/source/code/source.rst +15 -0
  50. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/conf.py +6 -3
  51. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/examples.rst +4 -3
  52. flowcypy-0.5.6/docs/source/internal/core_components.rst +95 -0
  53. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/internal/getting_started.rst +2 -2
  54. flowcypy-0.5.6/docs/source/internal/objectives/main.rst +83 -0
  55. flowcypy-0.5.6/docs/source/internal/objectives/pre.rst +31 -0
  56. flowcypy-0.5.6/docs/source/internal/objectives/stretch.rst +9 -0
  57. flowcypy-0.5.6/docs/source/internal/prerequisites/index.rst +12 -0
  58. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/internal/prerequisites/mathematics.rst +2 -2
  59. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/internal/prerequisites/optics.rst +2 -2
  60. flowcypy-0.5.6/docs/source/internal/prerequisites/programming.rst +91 -0
  61. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/internal/ressources.rst +2 -2
  62. flowcypy-0.5.6/docs/source/internal/tasks.rst +15 -0
  63. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/internal.rst +7 -10
  64. flowcypy-0.5.6/docs/source/sg_execution_times.rst +70 -0
  65. {flowcypy-0.5.2 → flowcypy-0.5.6}/pyproject.toml +1 -1
  66. {flowcypy-0.5.2 → flowcypy-0.5.6}/tests/test_coupling_mechanism.py +5 -5
  67. {flowcypy-0.5.2 → flowcypy-0.5.6}/tests/test_distribution.py +0 -2
  68. {flowcypy-0.5.2 → flowcypy-0.5.6}/tests/test_flow_cytometer.py +17 -18
  69. {flowcypy-0.5.2 → flowcypy-0.5.6}/tests/test_peak_analyzer.py +14 -17
  70. {flowcypy-0.5.2 → flowcypy-0.5.6}/tests/test_population.py +14 -11
  71. {flowcypy-0.5.2 → flowcypy-0.5.6}/tests/test_scatterer_distribution.py +9 -9
  72. flowcypy-0.5.2/FlowCyPy/flow_cell.py +0 -122
  73. flowcypy-0.5.2/docs/source/_static/thumbnail.png +0 -0
  74. flowcypy-0.5.2/docs/source/code/base.rst +0 -90
  75. flowcypy-0.5.2/docs/source/internal/core_components.rst +0 -32
  76. flowcypy-0.5.2/docs/source/internal/prerequisites/index.rst +0 -12
  77. flowcypy-0.5.2/docs/source/internal/prerequisites/programming.rst +0 -41
  78. flowcypy-0.5.2/docs/source/internal/tasks.rst +0 -136
  79. {flowcypy-0.5.2 → flowcypy-0.5.6}/.condarc +0 -0
  80. {flowcypy-0.5.2 → flowcypy-0.5.6}/.flake8 +0 -0
  81. {flowcypy-0.5.2 → flowcypy-0.5.6}/.github/dependabot.yml +0 -0
  82. {flowcypy-0.5.2 → flowcypy-0.5.6}/.github/workflows/deploy_PyPi.yml +0 -0
  83. {flowcypy-0.5.2 → flowcypy-0.5.6}/.github/workflows/deploy_anaconda.yml +0 -0
  84. {flowcypy-0.5.2 → flowcypy-0.5.6}/.github/workflows/deploy_coverage.yml +0 -0
  85. {flowcypy-0.5.2 → flowcypy-0.5.6}/.github/workflows/deploy_documentation.yml +0 -0
  86. {flowcypy-0.5.2 → flowcypy-0.5.6}/.gitignore +0 -0
  87. {flowcypy-0.5.2 → flowcypy-0.5.6}/.readthedocs.yml +0 -0
  88. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/classifier.py +0 -0
  89. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/coupling_mechanism/__init__.py +0 -0
  90. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/detector.py +0 -0
  91. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/directories.py +0 -0
  92. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/distribution/__init__.py +0 -0
  93. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/event_correlator.py +0 -0
  94. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/helper.py +0 -0
  95. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/noises.py +0 -0
  96. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/particle_count.py +0 -0
  97. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/peak_locator/__init__.py +0 -0
  98. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/peak_locator/base_class.py +0 -0
  99. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/peak_locator/basic.py +0 -0
  100. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/peak_locator/derivative.py +0 -0
  101. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/peak_locator/moving_average.py +0 -0
  102. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/physical_constant.py +0 -0
  103. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/plottings.py +0 -0
  104. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/populations_instances.py +0 -0
  105. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/report.py +0 -0
  106. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/source.py +0 -0
  107. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/units.py +0 -0
  108. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy/utils.py +0 -0
  109. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy.egg-info/dependency_links.txt +0 -0
  110. {flowcypy-0.5.2 → flowcypy-0.5.6}/FlowCyPy.egg-info/top_level.txt +0 -0
  111. {flowcypy-0.5.2 → flowcypy-0.5.6}/LICENSE +0 -0
  112. {flowcypy-0.5.2 → flowcypy-0.5.6}/Untitled.ipynb +0 -0
  113. {flowcypy-0.5.2 → flowcypy-0.5.6}/developments/dev_beads_analysis.py +0 -0
  114. {flowcypy-0.5.2 → flowcypy-0.5.6}/developments/dev_canto.py +0 -0
  115. {flowcypy-0.5.2 → flowcypy-0.5.6}/developments/dev_classifier.py +0 -0
  116. {flowcypy-0.5.2 → flowcypy-0.5.6}/developments/dev_shot_noise_check.py +0 -0
  117. {flowcypy-0.5.2 → flowcypy-0.5.6}/developments/dev_study_on_size.py +0 -0
  118. {flowcypy-0.5.2 → flowcypy-0.5.6}/developments/get_started.md +0 -0
  119. {flowcypy-0.5.2 → flowcypy-0.5.6}/developments/image.png +0 -0
  120. {flowcypy-0.5.2 → flowcypy-0.5.6}/developments/internship.pdf +0 -0
  121. {flowcypy-0.5.2 → flowcypy-0.5.6}/developments/output_file.prof +0 -0
  122. {flowcypy-0.5.2 → flowcypy-0.5.6}/developments/profiler.py +0 -0
  123. {flowcypy-0.5.2 → flowcypy-0.5.6}/developments/test.pdf +0 -0
  124. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/Makefile +0 -0
  125. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/density_plots/README.rst +0 -0
  126. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/extras/README.rst +0 -0
  127. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/extras/distributions.py +0 -0
  128. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/noise_sources/README.rst +0 -0
  129. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/noise_sources/dark_current.py +0 -0
  130. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/noise_sources/shot_noise.py +0 -0
  131. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/noise_sources/thermal.py +0 -0
  132. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/examples/tutorials/README.rst +0 -0
  133. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/images/example_0.png +0 -0
  134. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/images/example_1.png +0 -0
  135. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/images/example_2.png +0 -0
  136. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/images/example_3.png +0 -0
  137. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/images/flow_cytometer.png +0 -0
  138. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/images/logo.png +0 -0
  139. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/make.bat +0 -0
  140. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/_static/logo.png +0 -0
  141. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/code/peak_locator.rst +0 -0
  142. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/code.rst +0 -0
  143. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/index.rst +0 -0
  144. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/references.rst +0 -0
  145. {flowcypy-0.5.2 → flowcypy-0.5.6}/docs/source/theory.rst +0 -0
  146. {flowcypy-0.5.2 → flowcypy-0.5.6}/meta.yaml +0 -0
  147. {flowcypy-0.5.2 → flowcypy-0.5.6}/notebook.ipynb +0 -0
  148. {flowcypy-0.5.2 → flowcypy-0.5.6}/setup.cfg +0 -0
  149. {flowcypy-0.5.2 → flowcypy-0.5.6}/tests/__init__.py +0 -0
  150. {flowcypy-0.5.2 → flowcypy-0.5.6}/tests/test_detector_noise.py +0 -0
  151. {flowcypy-0.5.2 → flowcypy-0.5.6}/tests/test_extra.py +0 -0
  152. {flowcypy-0.5.2 → flowcypy-0.5.6}/tests/test_noises.py +0 -0
  153. {flowcypy-0.5.2 → flowcypy-0.5.6}/tests/test_peak_algorithm.py +0 -0
  154. {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 .scatterer import Scatterer, CouplingModel
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
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.5.2'
16
- __version_tuple__ = version_tuple = (0, 5, 2)
15
+ __version__ = version = '0.5.6'
16
+ __version_tuple__ = version_tuple = (0, 5, 6)
@@ -1,10 +1,10 @@
1
1
  import numpy as np
2
- from FlowCyPy import Scatterer, Detector
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: Scatterer, granularity: float = 1.0, A: float = 1.5, n: float = 2.0) -> float:
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 Scatterer, Detector
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: Scatterer, source: PlaneWave) -> PMS_SPHERE:
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 : 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("Scatterer size list is empty.")
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: Scatterer, tolerance: float = 1e-5) -> np.ndarray:
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 : 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 Scatterer, Detector
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: Scatterer, source: BaseBeam, detector: Detector) -> np.ndarray:
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 : Scatterer
32
- An instance of `Scatterer` containing the scatterer properties such as size and refractive index.
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: Scatterer) -> float:
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 Scatterer, Detector, ureg
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: Scatterer) -> np.ndarray:
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.scatterer import Scatterer
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
- scatterer : Scatterer
34
- The distribution of particle sizes which affects scattering signals.
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
- scatterer: Scatterer,
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.scatterer = scatterer
50
- self.source = source
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.scatterer.dataframe['Widths'].values
74
- _centers = self.scatterer.dataframe['Time'].values
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.scatterer.flow_cell.run_time)
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.scatterer
88
+ scatterer=self.scatterer_collection
89
89
  )
90
90
 
91
- self.scatterer.dataframe['CouplingPower'] = pint_pandas.PintArray(coupling_power, dtype=coupling_power.units)
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.scatterer.dataframe['Time']
163
+ self.pulse_dataframe['Centers'] = self.scatterer_collection.dataframe['Time']
164
164
 
165
- widths = self.source.waist / self.scatterer.flow_cell.flow_speed * np.ones(self.scatterer.n_events)
165
+ widths = self.source.waist / self.flow_cell.flow_speed * np.ones(self.scatterer_collection.n_events)
166
166
 
167
- self.scatterer.dataframe['Widths'] = pint_pandas.PintArray(widths, dtype=widths.units)
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.scatterer.add_to_ax(axes[-1])
182
+ self.scatterer_collection.add_to_ax(axes[-1])
183
183
 
184
184
  # Add legends to each subplot
185
185
  for ax in axes:
@@ -4,6 +4,12 @@ import matplotlib.pyplot as plt
4
4
  from MPSPlots.styles import mps
5
5
  from FlowCyPy.units import particle, Quantity
6
6
 
7
+ config_dict = dict(
8
+ arbitrary_types_allowed=True,
9
+ kw_only=True,
10
+ slots=True,
11
+ extra='forbid'
12
+ )
7
13
 
8
14
  class Base:
9
15
  """
@@ -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.scatterer.flow_cell.volume.to(milliliter)
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.scatterer.flow_cell.run_time).to('Hz')
292
+ mean_event_rate = (num_events / self.cytometer.flow_cell.run_time).to('Hz')
293
293
 
294
294
  return [
295
295
  detector.name,