FlowCyPy 0.5.7__tar.gz → 0.5.8__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 (146) hide show
  1. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/_version.py +2 -2
  2. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/cytometer.py +111 -78
  3. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/flow_cell.py +7 -7
  4. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/noises.py +6 -1
  5. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy.egg-info/PKG-INFO +1 -1
  6. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy.egg-info/SOURCES.txt +2 -0
  7. {flowcypy-0.5.7 → flowcypy-0.5.8}/PKG-INFO +1 -1
  8. flowcypy-0.5.8/developments/scripts/concentration_comparison.py +109 -0
  9. flowcypy-0.5.8/developments/scripts/dev_temp.py +96 -0
  10. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/examples/density_plots/1_populations.py +2 -1
  11. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/examples/density_plots/2_populations.py +3 -2
  12. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/examples/density_plots/3_populations.py +2 -1
  13. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/examples/density_plots/custom_populations.py +2 -1
  14. flowcypy-0.5.8/docs/examples/tutorials/limit_of_detection.py +114 -0
  15. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/examples/tutorials/workflow.py +2 -2
  16. flowcypy-0.5.8/docs/source/internal/objectives/main.rst +111 -0
  17. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/sg_execution_times.rst +16 -16
  18. {flowcypy-0.5.7 → flowcypy-0.5.8}/tests/test_flow_cytometer.py +4 -4
  19. {flowcypy-0.5.7 → flowcypy-0.5.8}/tests/test_peak_analyzer.py +2 -2
  20. flowcypy-0.5.7/developments/scripts/dev_temp.py +0 -154
  21. flowcypy-0.5.7/docs/source/internal/objectives/main.rst +0 -83
  22. {flowcypy-0.5.7 → flowcypy-0.5.8}/.flake8 +0 -0
  23. {flowcypy-0.5.7 → flowcypy-0.5.8}/.github/dependabot.yml +0 -0
  24. {flowcypy-0.5.7 → flowcypy-0.5.8}/.github/workflows/deploy_PyPi.yml +0 -0
  25. {flowcypy-0.5.7 → flowcypy-0.5.8}/.github/workflows/deploy_anaconda.yml +0 -0
  26. {flowcypy-0.5.7 → flowcypy-0.5.8}/.github/workflows/deploy_coverage.yml +0 -0
  27. {flowcypy-0.5.7 → flowcypy-0.5.8}/.github/workflows/deploy_documentation.yml +0 -0
  28. {flowcypy-0.5.7 → flowcypy-0.5.8}/.gitignore +0 -0
  29. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/__init__.py +0 -0
  30. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/classifier.py +0 -0
  31. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/coupling_mechanism/__init__.py +0 -0
  32. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/coupling_mechanism/empirical.py +0 -0
  33. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/coupling_mechanism/mie.py +0 -0
  34. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/coupling_mechanism/rayleigh.py +0 -0
  35. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/coupling_mechanism/uniform.py +0 -0
  36. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/detector.py +0 -0
  37. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/directories.py +0 -0
  38. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/distribution/__init__.py +0 -0
  39. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/distribution/base_class.py +0 -0
  40. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/distribution/delta.py +0 -0
  41. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/distribution/lognormal.py +0 -0
  42. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/distribution/normal.py +0 -0
  43. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/distribution/particle_size_distribution.py +0 -0
  44. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/distribution/uniform.py +0 -0
  45. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/distribution/weibull.py +0 -0
  46. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/event_correlator.py +0 -0
  47. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/helper.py +0 -0
  48. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/logger.py +0 -0
  49. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/particle_count.py +0 -0
  50. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/peak_locator/__init__.py +0 -0
  51. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/peak_locator/base_class.py +0 -0
  52. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/peak_locator/basic.py +0 -0
  53. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/peak_locator/derivative.py +0 -0
  54. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/peak_locator/moving_average.py +0 -0
  55. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/physical_constant.py +0 -0
  56. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/population.py +0 -0
  57. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/populations_instances.py +0 -0
  58. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/scatterer_collection.py +0 -0
  59. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/source.py +0 -0
  60. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/units.py +0 -0
  61. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy/utils.py +0 -0
  62. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy.egg-info/dependency_links.txt +0 -0
  63. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy.egg-info/requires.txt +0 -0
  64. {flowcypy-0.5.7 → flowcypy-0.5.8}/FlowCyPy.egg-info/top_level.txt +0 -0
  65. {flowcypy-0.5.7 → flowcypy-0.5.8}/LICENSE +0 -0
  66. {flowcypy-0.5.7 → flowcypy-0.5.8}/README.rst +0 -0
  67. {flowcypy-0.5.7 → flowcypy-0.5.8}/Untitled.ipynb +0 -0
  68. {flowcypy-0.5.7 → flowcypy-0.5.8}/developments/doc/internship.pdf +0 -0
  69. {flowcypy-0.5.7 → flowcypy-0.5.8}/developments/scripts/create_images.py +0 -0
  70. {flowcypy-0.5.7 → flowcypy-0.5.8}/developments/scripts/data_analysis.py +0 -0
  71. {flowcypy-0.5.7 → flowcypy-0.5.8}/developments/scripts/dev_beads_analysis.py +0 -0
  72. {flowcypy-0.5.7 → flowcypy-0.5.8}/developments/scripts/dev_canto.py +0 -0
  73. {flowcypy-0.5.7 → flowcypy-0.5.8}/developments/scripts/dev_classifier.py +0 -0
  74. {flowcypy-0.5.7 → flowcypy-0.5.8}/developments/scripts/dev_shot_noise_check.py +0 -0
  75. {flowcypy-0.5.7 → flowcypy-0.5.8}/developments/scripts/dev_study_on_ri.py +0 -0
  76. {flowcypy-0.5.7 → flowcypy-0.5.8}/developments/scripts/dev_study_on_size.py +0 -0
  77. {flowcypy-0.5.7 → flowcypy-0.5.8}/developments/scripts/mat2csv.py +0 -0
  78. {flowcypy-0.5.7 → flowcypy-0.5.8}/developments/scripts/profiler.py +0 -0
  79. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/Makefile +0 -0
  80. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/examples/density_plots/README.rst +0 -0
  81. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/examples/extras/README.rst +0 -0
  82. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/examples/extras/distributions.py +0 -0
  83. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/examples/extras/flow_cytometer_signal.py +0 -0
  84. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/examples/extras/full_workflow.py +0 -0
  85. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/examples/extras/scatterer_distribution.py +0 -0
  86. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/examples/noise_sources/README.rst +0 -0
  87. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/examples/noise_sources/dark_current.py +0 -0
  88. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/examples/noise_sources/shot_noise.py +0 -0
  89. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/examples/noise_sources/thermal.py +0 -0
  90. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/examples/tutorials/README.rst +0 -0
  91. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/images/distributions/Delta.png +0 -0
  92. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/images/distributions/LogNormal.png +0 -0
  93. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/images/distributions/Normal.png +0 -0
  94. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/images/distributions/RosinRammler.png +0 -0
  95. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/images/distributions/Uniform.png +0 -0
  96. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/images/distributions/Weibull.png +0 -0
  97. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/images/example_0.png +0 -0
  98. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/images/example_1.png +0 -0
  99. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/images/example_2.png +0 -0
  100. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/images/example_3.png +0 -0
  101. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/images/flow_cytometer.png +0 -0
  102. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/images/logo.png +0 -0
  103. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/make.bat +0 -0
  104. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/_static/default.css +0 -0
  105. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/_static/logo.png +0 -0
  106. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/_static/thumbnail.png +0 -0
  107. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/code/analysis.rst +0 -0
  108. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/code/base.rst +0 -0
  109. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/code/detector.rst +0 -0
  110. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/code/distributions.rst +0 -0
  111. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/code/flow_cell.rst +0 -0
  112. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/code/flow_cytometer.rst +0 -0
  113. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/code/peak_locator.rst +0 -0
  114. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/code/scatterer.rst +0 -0
  115. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/code/source.rst +0 -0
  116. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/code.rst +0 -0
  117. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/conf.py +0 -0
  118. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/examples.rst +0 -0
  119. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/index.rst +0 -0
  120. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/internal/core_components.rst +0 -0
  121. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/internal/getting_started.rst +0 -0
  122. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/internal/objectives/pre.rst +0 -0
  123. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/internal/objectives/stretch.rst +0 -0
  124. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/internal/prerequisites/index.rst +0 -0
  125. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/internal/prerequisites/mathematics.rst +0 -0
  126. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/internal/prerequisites/optics.rst +0 -0
  127. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/internal/prerequisites/programming.rst +0 -0
  128. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/internal/ressources.rst +0 -0
  129. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/internal/tasks.rst +0 -0
  130. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/internal.rst +0 -0
  131. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/references.rst +0 -0
  132. {flowcypy-0.5.7 → flowcypy-0.5.8}/docs/source/theory.rst +0 -0
  133. {flowcypy-0.5.7 → flowcypy-0.5.8}/meta.yaml +0 -0
  134. {flowcypy-0.5.7 → flowcypy-0.5.8}/notebook.ipynb +0 -0
  135. {flowcypy-0.5.7 → flowcypy-0.5.8}/pyproject.toml +0 -0
  136. {flowcypy-0.5.7 → flowcypy-0.5.8}/setup.cfg +0 -0
  137. {flowcypy-0.5.7 → flowcypy-0.5.8}/tests/__init__.py +0 -0
  138. {flowcypy-0.5.7 → flowcypy-0.5.8}/tests/test_coupling_mechanism.py +0 -0
  139. {flowcypy-0.5.7 → flowcypy-0.5.8}/tests/test_detector_noise.py +0 -0
  140. {flowcypy-0.5.7 → flowcypy-0.5.8}/tests/test_distribution.py +0 -0
  141. {flowcypy-0.5.7 → flowcypy-0.5.8}/tests/test_extra.py +0 -0
  142. {flowcypy-0.5.7 → flowcypy-0.5.8}/tests/test_noises.py +0 -0
  143. {flowcypy-0.5.7 → flowcypy-0.5.8}/tests/test_peak_algorithm.py +0 -0
  144. {flowcypy-0.5.7 → flowcypy-0.5.8}/tests/test_population.py +0 -0
  145. {flowcypy-0.5.7 → flowcypy-0.5.8}/tests/test_scatterer_distribution.py +0 -0
  146. {flowcypy-0.5.7 → flowcypy-0.5.8}/tests/test_source.py +0 -0
@@ -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.7'
16
- __version_tuple__ = version_tuple = (0, 5, 7)
15
+ __version__ = version = '0.5.8'
16
+ __version_tuple__ = version_tuple = (0, 5, 8)
@@ -76,6 +76,7 @@ class FlowCytometer:
76
76
  self.detectors = detectors
77
77
  self.coupling_mechanism = coupling_mechanism
78
78
  self.background_power = background_power
79
+ self.plot = self.PlotInterface(self)
79
80
 
80
81
  assert len(self.detectors) == 2, 'For now, FlowCytometer can only take two detectors for the analysis.'
81
82
  assert self.detectors[0].name != self.detectors[1].name, 'Both detectors cannot have the same name'
@@ -85,7 +86,7 @@ class FlowCytometer:
85
86
  Computes and assigns the optical coupling power for each particle-detection event.
86
87
 
87
88
  This method evaluates the coupling between the scatterers in the flow cell and the detectors
88
- using the specified detection mechanism. The computed coupling power is stored in the
89
+ using the specified detection mechanism. The computed coupling power is stored in the
89
90
  `scatterer_collection` dataframe under detector-specific columns.
90
91
 
91
92
  Updates
@@ -98,7 +99,7 @@ class FlowCytometer:
98
99
  ------
99
100
  ValueError
100
101
  If an invalid coupling mechanism is specified during initialization.
101
- """
102
+ """
102
103
  detection_mechanism = self._get_detection_mechanism()
103
104
 
104
105
  for detector in self.detectors:
@@ -109,7 +110,7 @@ class FlowCytometer:
109
110
  )
110
111
 
111
112
  self.scatterer_collection.dataframe["detector: " + detector.name] = pint_pandas.PintArray(self.coupling_power, dtype=self.coupling_power.units)
112
-
113
+
113
114
  self._generate_pulse_parameters()
114
115
 
115
116
  def initialize_signal(self) -> None:
@@ -124,11 +125,11 @@ class FlowCytometer:
124
125
  Each detector's `raw_signal` attribute is initialized with time-dependent values
125
126
  based on the flow cell's runtime.
126
127
 
127
- """
128
+ """
128
129
  # Initialize the detectors
129
130
  for detector in self.detectors:
130
131
  detector.source = self.source
131
- detector.init_raw_signal(run_time=self.flow_cell.run_time)
132
+ detector.init_raw_signal(run_time=self.flow_cell.run_time)
132
133
 
133
134
  def simulate_pulse(self) -> None:
134
135
  """
@@ -260,84 +261,13 @@ class FlowCytometer:
260
261
  )
261
262
 
262
263
  self.pulse_dataframe = pd.DataFrame(columns=columns)
263
-
264
+
264
265
  self.pulse_dataframe['Centers'] = self.scatterer_collection.dataframe['Time']
265
266
 
266
267
  widths = self.source.waist / self.flow_cell.flow_speed * np.ones(self.scatterer_collection.n_events)
267
268
 
268
269
  self.scatterer_collection.dataframe['Widths'] = pint_pandas.PintArray(widths, dtype=widths.units)
269
270
 
270
- def plot(self, figure_size: tuple = (10, 6), add_peak_locator: bool = False, show: bool = True) -> None:
271
- """
272
- Visualizes the raw signals for all detector channels along with the scatterer distribution.
273
-
274
- Parameters
275
- ----------
276
- figure_size : tuple, optional
277
- Dimensions of the generated plot (default: (10, 6)).
278
- add_peak_locator : bool, optional
279
- If True, adds visual markers for detected signal peaks (default: False).
280
-
281
- Effects
282
- -------
283
- Displays a multi-panel plot showing:
284
- - Raw signals for each detector channel.
285
- - Scatterer distribution along the time axis.
286
- """
287
- logging.info("Plotting the signal for the different channels.")
288
-
289
- n_detectors = len(self.detectors)
290
-
291
- with plt.style.context(mps):
292
- _, axes = plt.subplots(ncols=1, nrows=n_detectors + 1, figsize=figure_size, sharex=True, sharey=True, gridspec_kw={'height_ratios': [1, 1, 0.4]})
293
-
294
- time_unit, signal_unit = self.detectors[0].plot(ax=axes[0], show=False, add_peak_locator=add_peak_locator)
295
- self.detectors[1].plot(ax=axes[1], show=False, time_unit=time_unit, signal_unit=signal_unit, add_peak_locator=add_peak_locator)
296
-
297
- axes[-1].get_yaxis().set_visible(False)
298
- self.scatterer_collection.add_to_ax(axes[-1])
299
-
300
- # Add legends to each subplot
301
- for ax in axes:
302
- ax.legend()
303
-
304
- if show: # Display the plot
305
- plt.show()
306
-
307
- def plot_coupling_density(self, log_scale: bool = False, show: bool = True) -> None:
308
- """
309
- Plots the density distribution of optical coupling in the FSC and SSC channels.
310
-
311
- This method generates a joint plot showing the relationship between the signals from
312
- the forward scatter ('detector: forward') and side scatter ('detector: side') detectors.
313
- The plot is color-coded by particle population and can optionally display axes on a logarithmic scale.
314
-
315
- Parameters
316
- ----------
317
- log_scale : bool, optional
318
- If True, applies a logarithmic scale to both the x and y axes of the plot (default: False).
319
- show : bool, optional
320
- If True, displays the plot immediately. If False, the plot is created but not displayed,
321
- allowing for further customization or saving externally (default: True).
322
-
323
- """
324
- with plt.style.context(mps):
325
- joint_plot = sns.jointplot(
326
- data=self.scatterer_collection.dataframe,
327
- y='detector: side',
328
- x='detector: forward',
329
- hue="Population",
330
- alpha=0.8,
331
- )
332
-
333
-
334
- if log_scale:
335
- joint_plot.ax_joint.set_xscale('log')
336
- joint_plot.ax_joint.set_yscale('log')
337
-
338
- if show: # Display the plot
339
- plt.show()
340
-
341
271
  def add_detector(self, **kwargs) -> Detector:
342
272
  """
343
273
  Dynamically adds a new detector to the system configuration.
@@ -355,9 +285,112 @@ class FlowCytometer:
355
285
  Effects
356
286
  -------
357
287
  - Appends the created detector to the `detectors` list.
358
- """
288
+ """
359
289
  detector = Detector(**kwargs)
360
290
 
361
291
  self.detectors.append(detector)
362
292
 
363
293
  return detector
294
+
295
+ class PlotInterface:
296
+ def __init__(self, cytometer):
297
+ self.cytometer = cytometer
298
+
299
+ def signals(self, figure_size: tuple = (10, 6), add_peak_locator: bool = False, show: bool = True) -> None:
300
+ """
301
+ Visualizes the raw signals for all detector channels along with the scatterer distribution.
302
+
303
+ Parameters
304
+ ----------
305
+ figure_size : tuple, optional
306
+ Dimensions of the generated plot (default: (10, 6)).
307
+ add_peak_locator : bool, optional
308
+ If True, adds visual markers for detected signal peaks (default: False).
309
+
310
+ Effects
311
+ -------
312
+ Displays a multi-panel plot showing:
313
+ - Raw signals for each detector channel.
314
+ - Scatterer distribution along the time axis.
315
+ """
316
+ logging.info("Plotting the signal for the different channels.")
317
+
318
+ scatterer_collection = self.cytometer.scatterer_collection
319
+ detectors = self.cytometer.detectors
320
+
321
+ n_detectors = len(detectors)
322
+
323
+ with plt.style.context(mps):
324
+ _, axes = plt.subplots(ncols=1, nrows=n_detectors + 1, figsize=figure_size, sharex=True, sharey=True, gridspec_kw={'height_ratios': [1, 1, 0.4]})
325
+
326
+ time_unit, signal_unit = detectors[0].plot(ax=axes[0], show=False, add_peak_locator=add_peak_locator)
327
+ detectors[1].plot(ax=axes[1], show=False, time_unit=time_unit, signal_unit=signal_unit, add_peak_locator=add_peak_locator)
328
+
329
+ axes[-1].get_yaxis().set_visible(False)
330
+ scatterer_collection.add_to_ax(axes[-1])
331
+
332
+ # Add legends to each subplot
333
+ for ax in axes:
334
+ ax.legend()
335
+
336
+ if show: # Display the plot
337
+ plt.show()
338
+
339
+ def coupling_distribution(self, log_scale: bool = False, show: bool = True, equal_limits: bool = False, save_path: str = None) -> None:
340
+ """
341
+ Plots the density distribution of optical coupling in the FSC and SSC channels.
342
+
343
+ This method generates a joint plot showing the relationship between the signals from
344
+ the forward scatter ('detector: forward') and side scatter ('detector: side') detectors.
345
+ The plot is color-coded by particle population and can optionally display axes on a logarithmic scale.
346
+
347
+ Parameters
348
+ ----------
349
+ log_scale : bool, optional
350
+ If True, applies a logarithmic scale to both the x and y axes of the plot (default: False).
351
+ show : bool, optional
352
+ If True, displays the plot immediately. If False, the plot is created but not displayed,
353
+ allowing for further customization or saving externally (default: True).
354
+ equal_limits : bool, optional
355
+ If True, sets the same limits for both the x and y axes based on the maximum range
356
+ across both axes. If False, the limits are set automatically based on the data (default: False).
357
+
358
+ """
359
+ scatterer_collection = self.cytometer.scatterer_collection
360
+ detector_0, detector_1 = self.cytometer.detectors
361
+
362
+ with plt.style.context(mps):
363
+ joint_plot = sns.jointplot(
364
+ data=scatterer_collection.dataframe,
365
+ x=f'detector: {detector_0.name}',
366
+ y=f'detector: {detector_1.name}',
367
+ hue="Population",
368
+ alpha=0.8,
369
+ )
370
+
371
+ if log_scale:
372
+ joint_plot.ax_joint.set_xscale('log')
373
+ joint_plot.ax_joint.set_yscale('log')
374
+
375
+ if equal_limits:
376
+ # Get data limits
377
+ x_data = scatterer_collection.dataframe[f'detector: {detector_0.name}']
378
+ y_data = scatterer_collection.dataframe[f'detector: {detector_1.name}']
379
+
380
+ x_min, x_max = x_data.min(), x_data.max()
381
+ y_min, y_max = y_data.min(), y_data.max()
382
+
383
+ # Find the overall min and max
384
+ overall_min = min(x_min, y_min)
385
+ overall_max = max(x_max, y_max)
386
+
387
+ # Set equal limits
388
+ joint_plot.ax_joint.set_xlim(overall_min, overall_max)
389
+ joint_plot.ax_joint.set_ylim(overall_min, overall_max)
390
+
391
+ if save_path:
392
+ joint_plot.figure.savefig(save_path, dpi=300, bbox_inches='tight')
393
+ print(f"Plot saved to {save_path}")
394
+
395
+ if show: # Display the plot
396
+ plt.show()
@@ -130,14 +130,14 @@ class FlowCell(object):
130
130
  ['Flow Area', f"{self.flow_area:.2f~#P}"],
131
131
  ['Total Time', f"{self.run_time:.2f~#P}"]
132
132
  ]
133
-
133
+
134
134
  # def initialize(self, scatterer: Population | ScattererCollection) -> None:
135
135
  # if isinstance(scatterer, Population):
136
136
  # return self._initialize_population(scatterer)
137
137
 
138
138
  # elif isinstance(scatterer, ScattererCollection):
139
139
  # return self._initialize_scatterer_collection(scatterer)
140
-
140
+
141
141
  def _initialize_population(self, population: Population) -> None:
142
142
  population.dataframe = pandas.DataFrame()
143
143
 
@@ -164,13 +164,13 @@ class FlowCell(object):
164
164
  scatterer : Scatterer
165
165
  An instance of the Scatterer class that describes the scatterer collection being used.
166
166
 
167
- """
167
+ """
168
168
  self.scatterer_collection = scatterer_collection
169
169
 
170
170
  for population in self.scatterer_collection.populations:
171
171
  self._initialize_population(population)
172
172
  population.dataframe.Size = population.dataframe.Size.pint.to(size_units)
173
-
173
+
174
174
  if len(self.scatterer_collection.populations) != 0:
175
175
  self.scatterer_collection.dataframe = pandas.concat(
176
176
  [population.dataframe for population in self.scatterer_collection.populations],
@@ -195,7 +195,7 @@ class FlowCell(object):
195
195
  index=multi_index
196
196
  )
197
197
 
198
- self.scatterer_collection.n_events = len(self.scatterer_collection.dataframe)
198
+ self.scatterer_collection.n_events = len(self.scatterer_collection.dataframe)
199
199
 
200
200
  def distribute_time_linearly(self, sequential_population: bool = False) -> None:
201
201
  """
@@ -210,14 +210,14 @@ class FlowCell(object):
210
210
 
211
211
  """
212
212
  # Generate linearly spaced time values across the flow cell runtime
213
- linear_spacing = numpy.linspace(0, self.run_time, self.n_events)
213
+ linear_spacing = numpy.linspace(0, self.run_time, self.scatterer_collection.n_events)
214
214
 
215
215
  # Optionally randomize the linear spacing
216
216
  if not sequential_population:
217
217
  numpy.random.shuffle(linear_spacing)
218
218
 
219
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)
220
+ self.scatterer_collection.dataframe.Time = PintArray(linear_spacing, dtype=self.scatterer_collection.dataframe.Time.pint.units)
221
221
 
222
222
  def _generate_longitudinal_positions(self, population: Population) -> None:
223
223
  r"""
@@ -1,6 +1,11 @@
1
+ class RestrictiveMeta(type):
2
+ def __setattr__(cls, name, value):
3
+ if not hasattr(cls, name):
4
+ raise AttributeError(f"Cannot set unknown class-level attribute '{name}' in {cls.__name__}.")
5
+ super().__setattr__(name, value)
1
6
 
2
7
 
3
- class NoiseSetting:
8
+ class NoiseSetting(metaclass=RestrictiveMeta):
4
9
  _instance = None
5
10
 
6
11
  def __new__(cls, *args, **kwargs):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: FlowCyPy
3
- Version: 0.5.7
3
+ Version: 0.5.8
4
4
  Summary: A package for flow-cytometry simulations.
5
5
  Author-email: Martin Poinsinet de Sivry-Houle <martin.poinsinet.de.sivry@gmail.com>
6
6
  License: MIT License
@@ -54,6 +54,7 @@ FlowCyPy/peak_locator/basic.py
54
54
  FlowCyPy/peak_locator/derivative.py
55
55
  FlowCyPy/peak_locator/moving_average.py
56
56
  developments/doc/internship.pdf
57
+ developments/scripts/concentration_comparison.py
57
58
  developments/scripts/create_images.py
58
59
  developments/scripts/data_analysis.py
59
60
  developments/scripts/dev_beads_analysis.py
@@ -82,6 +83,7 @@ docs/examples/noise_sources/dark_current.py
82
83
  docs/examples/noise_sources/shot_noise.py
83
84
  docs/examples/noise_sources/thermal.py
84
85
  docs/examples/tutorials/README.rst
86
+ docs/examples/tutorials/limit_of_detection.py
85
87
  docs/examples/tutorials/workflow.py
86
88
  docs/images/example_0.png
87
89
  docs/images/example_1.png
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: FlowCyPy
3
- Version: 0.5.7
3
+ Version: 0.5.8
4
4
  Summary: A package for flow-cytometry simulations.
5
5
  Author-email: Martin Poinsinet de Sivry-Houle <martin.poinsinet.de.sivry@gmail.com>
6
6
  License: MIT License
@@ -0,0 +1,109 @@
1
+
2
+ import numpy as np
3
+ from FlowCyPy import FlowCytometer, ScattererCollection, EventCorrelator, Detector, GaussianBeam, FlowCell
4
+ from FlowCyPy import peak_locator
5
+ from FlowCyPy.units import particle, milliliter, nanometer, RIU, second, micrometer, millisecond, meter
6
+ from FlowCyPy.units import degree, watt, ampere, millivolt, ohm, kelvin, milliampere, megahertz, microvolt
7
+ from FlowCyPy.units import microsecond
8
+ from FlowCyPy.units import milliwatt, AU
9
+ from FlowCyPy import NoiseSetting
10
+ from FlowCyPy import Population, distribution
11
+ from FlowCyPy.population import Exosome, HDL
12
+
13
+ NoiseSetting.include_noises = True
14
+
15
+ np.random.seed(3)
16
+
17
+ source = GaussianBeam(
18
+ numerical_aperture=0.3 * AU, # Numerical aperture of the laser: 0.3
19
+ wavelength=488 * nanometer, # Laser wavelength: 800 nanometers
20
+ optical_power=100 * milliwatt # Laser optical power: 10 milliwatts
21
+ )
22
+
23
+ flow_cell = FlowCell(
24
+ source=source,
25
+ flow_speed=7.56 * meter / second, # Flow speed: 7.56 meters per second
26
+ flow_area=(10 * micrometer) ** 2, # Flow area: 10 x 10 micrometers
27
+ run_time=.2 * millisecond # Total simulation time: 0.3 milliseconds
28
+ )
29
+
30
+ scatterer = ScattererCollection(medium_refractive_index=1.33 * RIU) # Medium refractive index: 1.33
31
+
32
+ population_0 = Population(
33
+ name='Pop 0',
34
+ particle_count=100 * particle,
35
+ size=distribution.RosinRammler(characteristic_size=100 * nanometer, spread=4.5),
36
+ refractive_index=distribution.Normal(mean=1.39 * RIU, std_dev=0.02 * RIU)
37
+ )
38
+
39
+ scatterer.add_population(population_0)
40
+ # scatterer.add_population(hdl)
41
+
42
+
43
+ flow_cell.initialize(scatterer_collection=scatterer)
44
+
45
+ scatterer._log_properties()
46
+
47
+ source.print_properties() # Print the laser source properties
48
+
49
+ detector_0 = Detector(
50
+ name='side', # Detector name: Side scatter detector
51
+ phi_angle=90 * degree, # Angle: 90 degrees (Side Scatter)
52
+ numerical_aperture=.2 * AU, # Numerical aperture: 1.2
53
+ responsitivity=1 * ampere / watt, # Responsitivity: 1 ampere per watt
54
+ sampling_freq=60 * megahertz, # Sampling frequency: 60 MHz
55
+ saturation_level=0.04 * millivolt, # Saturation level: 2 millivolts
56
+ # n_bins='16bit', # Number of bins: 14-bit resolution
57
+ resistance=50 * ohm, # Detector resistance: 50 ohms
58
+ dark_current=0.1 * milliampere, # Dark current: 0.1 milliamps
59
+ temperature=300 * kelvin # Operating temperature: 300 Kelvin
60
+ )
61
+
62
+ detector_1 = Detector(
63
+ name='forward', # Detector name: Forward scatter detector
64
+ phi_angle=0 * degree, # Angle: 0 degrees (Forward Scatter)
65
+ numerical_aperture=.2 * AU, # Numerical aperture: 1.2
66
+ responsitivity=1 * ampere / watt, # Responsitivity: 1 ampere per watt
67
+ sampling_freq=60 * megahertz, # Sampling frequency: 60 MHz
68
+ saturation_level=0.04 * millivolt, # Saturation level: 2 millivolts
69
+ # n_bins='16bit', # Number of bins: 14-bit resolution
70
+ resistance=50 * ohm, # Detector resistance: 50 ohms
71
+ dark_current=0.1 * milliampere, # Dark current: 0.1 milliamps
72
+ temperature=300 * kelvin # Operating temperature: 300 Kelvin
73
+ )
74
+
75
+
76
+ detector_1.print_properties() # Print the properties of the forward scatter detector
77
+
78
+ cytometer = FlowCytometer( # Laser source used in the experiment
79
+ flow_cell=flow_cell, # Populations used in the experiment
80
+ background_power=0.0 * milliwatt,
81
+ detectors=[detector_0, detector_1] # List of detectors: Side scatter and Forward scatter
82
+ )
83
+
84
+ cytometer.run_coupling_analysis()
85
+
86
+ cytometer.initialize_signal()
87
+
88
+ cytometer.simulate_pulse()
89
+
90
+ cytometer.plot.signals()
91
+
92
+ algorithm = peak_locator.MovingAverage(
93
+ threshold=10 * microvolt, # Signal threshold: 0.1 mV
94
+ window_size=1 * microsecond, # Moving average window size: 1 µs
95
+ min_peak_distance=0.1 * microsecond # Minimum distance between peaks: 0.3 µs
96
+ )
97
+
98
+ detector_0.set_peak_locator(algorithm)
99
+ detector_1.set_peak_locator(algorithm)
100
+
101
+ cytometer.plot.signals(add_peak_locator=True)
102
+
103
+ analyzer = EventCorrelator(cytometer=cytometer)
104
+
105
+ analyzer.run_analysis(compute_peak_area=False)
106
+
107
+ analyzer.get_coincidence(margin=0.1 * microsecond)
108
+
109
+ analyzer.plot(log_plot=False)
@@ -0,0 +1,96 @@
1
+ import numpy as np
2
+ from FlowCyPy import FlowCell
3
+ from FlowCyPy.units import meter, micrometer, millisecond, second, degree
4
+ from FlowCyPy import ScattererCollection
5
+ from FlowCyPy.units import particle, milliliter, nanometer, RIU, milliwatt, AU
6
+ from FlowCyPy import FlowCytometer
7
+ from FlowCyPy import Population, distribution
8
+ from FlowCyPy.detector import Detector
9
+ from FlowCyPy.units import ohm, megahertz, ampere, volt, kelvin, watt, microsecond, microvolt
10
+ from FlowCyPy import EventCorrelator, peak_locator
11
+ from FlowCyPy import GaussianBeam
12
+ from FlowCyPy import NoiseSetting
13
+
14
+ NoiseSetting.include_noises = False
15
+
16
+ np.random.seed(3)
17
+
18
+ source = GaussianBeam(
19
+ numerical_aperture=0.3 * AU,
20
+ wavelength=988 * nanometer,
21
+ optical_power=20 * milliwatt
22
+ )
23
+
24
+
25
+ flow_cell = FlowCell(
26
+ source=source,
27
+ flow_speed=7.56 * meter / second,
28
+ flow_area=(10 * micrometer) ** 2,
29
+ run_time=0.2 * millisecond
30
+ )
31
+
32
+
33
+ scatterer = ScattererCollection(medium_refractive_index=1.33 * RIU) # Medium refractive index of 1.33 (water)
34
+
35
+ lymphocyte = Population(
36
+ name='lymphocyte',
37
+ particle_count=200 * particle,
38
+ size=distribution.Normal(mean=14_000 * nanometer, std_dev=1000 * nanometer),
39
+ refractive_index=distribution.Normal(mean=1.39 * RIU, std_dev=0.001 * RIU)
40
+ )
41
+
42
+
43
+ monocyte = Population(
44
+ name='monocyte',
45
+ particle_count=200 * particle,
46
+ size=distribution.Normal(mean=23_000 * nanometer, std_dev=1000 * nanometer),
47
+ refractive_index=distribution.Normal(mean=1.39 * RIU, std_dev=0.001 * RIU)
48
+ )
49
+
50
+
51
+ scatterer.add_population(lymphocyte, monocyte)
52
+
53
+ flow_cell.initialize(scatterer_collection=scatterer)
54
+
55
+ scatterer.plot()
56
+
57
+
58
+ detector_0 = Detector(
59
+ name='forward',
60
+ phi_angle=0 * degree,
61
+ numerical_aperture=.2 * AU,
62
+ responsitivity=1 * ampere / watt,
63
+ sampling_freq=60 * megahertz,
64
+ noise_level=0.0 * volt,
65
+ saturation_level=1600 * microvolt,
66
+ resistance=150 * ohm,
67
+ temperature=300 * kelvin,
68
+ )
69
+
70
+
71
+ detector_1 = Detector(
72
+ name='side',
73
+ phi_angle=90 * degree,
74
+ numerical_aperture=.2 * AU,
75
+ responsitivity=1 * ampere / watt,
76
+ sampling_freq=60 * megahertz,
77
+ noise_level=0.0 * volt,
78
+ saturation_level=1600 * microvolt,
79
+ resistance=150 * ohm,
80
+ temperature=300 * kelvin,
81
+ )
82
+
83
+
84
+ cytometer = FlowCytometer(
85
+ detectors=[detector_0, detector_1],
86
+ flow_cell=flow_cell
87
+ )
88
+
89
+
90
+ cytometer.run_coupling_analysis()
91
+
92
+ cytometer.initialize_signal()
93
+
94
+ cytometer.simulate_pulse()
95
+
96
+ cytometer.plot.coupling_distribution(log_scale=True, equal_limits=True)
@@ -60,6 +60,7 @@ flow_cell.initialize(scatterer_collection=scatterer) # Link populations to flow
60
60
  scatterer._log_properties() # Display population properties
61
61
  scatterer.plot() # Visualize the population distributions
62
62
 
63
+ # %%
63
64
  # Add forward scatter detector
64
65
  detector_0 = Detector(
65
66
  name='forward', # Detector name: Forward scatter
@@ -106,7 +107,7 @@ cytometer.initialize_signal()
106
107
  cytometer.simulate_pulse()
107
108
 
108
109
  # Visualize the scatter signals from both detectors
109
- cytometer.plot()
110
+ cytometer.plot.signals()
110
111
 
111
112
  # %%
112
113
  # Step 5: Analyzing Pulse Signals
@@ -62,6 +62,7 @@ scatterer.plot()
62
62
 
63
63
  source.print_properties() # Print the laser source properties
64
64
 
65
+ # %%
65
66
  # Step 5: Configure Detectors
66
67
  # Side scatter detector
67
68
  detector_0 = Detector(
@@ -109,7 +110,7 @@ cytometer.initialize_signal()
109
110
  cytometer.simulate_pulse()
110
111
 
111
112
  # Plot the results from both detectors
112
- cytometer.plot()
113
+ cytometer.plot.signals()
113
114
 
114
115
  # %%
115
116
  # Step 5: Analyzing Pulse Signals
@@ -122,7 +123,7 @@ algorithm = peak_locator.MovingAverage(
122
123
  detector_0.set_peak_locator(algorithm)
123
124
  detector_1.set_peak_locator(algorithm)
124
125
 
125
- cytometer.plot(add_peak_locator=True)
126
+ cytometer.plot.signals(add_peak_locator=True)
126
127
 
127
128
  analyzer = EventCorrelator(cytometer=cytometer)
128
129
 
@@ -64,6 +64,7 @@ flow_cell.initialize(scatterer_collection=scatterer) # Link populations to flo
64
64
  scatterer._log_properties() # Display population properties
65
65
  scatterer.plot() # Visualize the population distributions
66
66
 
67
+ # %%
67
68
  # Step 4: Simulating the Flow Cytometry Experiment
68
69
  # Initialize the cytometer and configure detectors
69
70
  # Add forward scatter detector
@@ -106,7 +107,7 @@ cytometer.initialize_signal()
106
107
  cytometer.simulate_pulse()
107
108
 
108
109
  # Visualize the scatter signals from both detectors
109
- cytometer.plot()
110
+ cytometer.plot.signals()
110
111
 
111
112
  # %%
112
113
  # Step 5: Analyzing Pulse Signals
@@ -73,6 +73,7 @@ flow_cell.initialize(scatterer_collection=scatterer) # Link populations to flow
73
73
  scatterer._log_properties() # Display population properties
74
74
  scatterer.plot() # Visualize the population distributions
75
75
 
76
+ # %%
76
77
  # Step 4: Simulating the Flow Cytometry Experiment
77
78
  # Initialize the cytometer and configure detectors
78
79
  # Add forward scatter detector
@@ -115,7 +116,7 @@ cytometer.initialize_signal()
115
116
  cytometer.simulate_pulse()
116
117
 
117
118
  # Visualize the scatter signals from both detectors
118
- cytometer.plot()
119
+ cytometer.plot.signals()
119
120
 
120
121
  # %%
121
122
  # Step 5: Analyzing Pulse Signals