FlowCyPy 0.7.3__tar.gz → 0.7.4__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 (161) hide show
  1. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/_version.py +2 -2
  2. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/acquisition.py +84 -26
  3. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/cytometer.py +7 -5
  4. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/flow_cell.py +6 -3
  5. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/helper.py +43 -0
  6. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy.egg-info/PKG-INFO +1 -1
  7. {flowcypy-0.7.3 → flowcypy-0.7.4}/PKG-INFO +1 -1
  8. flowcypy-0.7.4/developments/scripts/temp.py +70 -0
  9. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/examples/extras/flow_cytometer_signal.py +12 -8
  10. {flowcypy-0.7.3 → flowcypy-0.7.4}/tests/test_coupling_mechanism.py +2 -2
  11. {flowcypy-0.7.3 → flowcypy-0.7.4}/tests/test_population.py +1 -1
  12. {flowcypy-0.7.3 → flowcypy-0.7.4}/tests/test_scatterer_distribution.py +2 -2
  13. flowcypy-0.7.3/developments/scripts/temp.py +0 -207
  14. {flowcypy-0.7.3 → flowcypy-0.7.4}/.flake8 +0 -0
  15. {flowcypy-0.7.3 → flowcypy-0.7.4}/.github/dependabot.yml +0 -0
  16. {flowcypy-0.7.3 → flowcypy-0.7.4}/.github/workflows/deploy_PyPi.yml +0 -0
  17. {flowcypy-0.7.3 → flowcypy-0.7.4}/.github/workflows/deploy_anaconda.yml +0 -0
  18. {flowcypy-0.7.3 → flowcypy-0.7.4}/.github/workflows/deploy_coverage.yml +0 -0
  19. {flowcypy-0.7.3 → flowcypy-0.7.4}/.github/workflows/deploy_documentation.yml +0 -0
  20. {flowcypy-0.7.3 → flowcypy-0.7.4}/.gitignore +0 -0
  21. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/__init__.py +0 -0
  22. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/classifier.py +0 -0
  23. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/coupling_mechanism/__init__.py +0 -0
  24. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/coupling_mechanism/empirical.py +0 -0
  25. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/coupling_mechanism/mie.py +0 -0
  26. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/coupling_mechanism/rayleigh.py +0 -0
  27. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/coupling_mechanism/uniform.py +0 -0
  28. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/detector.py +0 -0
  29. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/directories.py +0 -0
  30. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/distribution/__init__.py +0 -0
  31. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/distribution/base_class.py +0 -0
  32. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/distribution/delta.py +0 -0
  33. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/distribution/lognormal.py +0 -0
  34. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/distribution/normal.py +0 -0
  35. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/distribution/particle_size_distribution.py +0 -0
  36. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/distribution/uniform.py +0 -0
  37. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/distribution/weibull.py +0 -0
  38. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/noises.py +0 -0
  39. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/particle_count.py +0 -0
  40. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/peak_locator/__init__.py +0 -0
  41. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/peak_locator/base_class.py +0 -0
  42. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/peak_locator/basic.py +0 -0
  43. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/peak_locator/derivative.py +0 -0
  44. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/peak_locator/moving_average.py +0 -0
  45. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/physical_constant.py +0 -0
  46. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/population.py +0 -0
  47. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/populations_instances.py +0 -0
  48. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/scatterer_collection.py +0 -0
  49. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/signal_digitizer.py +0 -0
  50. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/source.py +0 -0
  51. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/units.py +0 -0
  52. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/utils.py +0 -0
  53. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy.egg-info/SOURCES.txt +0 -0
  54. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy.egg-info/dependency_links.txt +0 -0
  55. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy.egg-info/requires.txt +0 -0
  56. {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy.egg-info/top_level.txt +0 -0
  57. {flowcypy-0.7.3 → flowcypy-0.7.4}/LICENSE +0 -0
  58. {flowcypy-0.7.3 → flowcypy-0.7.4}/README.rst +0 -0
  59. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/Deep_peak_square.ipynb +0 -0
  60. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/Physics-informed_AI.ipynb +0 -0
  61. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/ROI_analysis-Copy1.ipynb +0 -0
  62. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/ROI_analysis.ipynb +0 -0
  63. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/Untitled.ipynb +0 -0
  64. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/Untitled1.ipynb +0 -0
  65. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/Untitled2.ipynb +0 -0
  66. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/ai_dev2.ipynb +0 -0
  67. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/best_model.h5 +0 -0
  68. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/best_model.keras +0 -0
  69. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/concentration_validation.py +0 -0
  70. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/doc/canto_spec.md +0 -0
  71. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/doc/internship.pdf +0 -0
  72. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/get_started.md +0 -0
  73. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/grad_cam_output.png +0 -0
  74. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/image.png +0 -0
  75. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/model.png +0 -0
  76. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/model_example.png +0 -0
  77. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/output_file.prof +0 -0
  78. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/AI_peak_detection.py +0 -0
  79. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/concentration_comparison.py +0 -0
  80. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/create_images.py +0 -0
  81. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/data_analysis.py +0 -0
  82. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/dev_beads_analysis.py +0 -0
  83. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/dev_canto.py +0 -0
  84. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/dev_classifier.py +0 -0
  85. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/dev_shot_noise_check.py +0 -0
  86. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/dev_stats_0.py +0 -0
  87. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/dev_stats_1.py +0 -0
  88. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/dev_stats_2.py +0 -0
  89. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/dev_study_on_ri.py +0 -0
  90. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/dev_study_on_size.py +0 -0
  91. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/mat2csv.py +0 -0
  92. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/profiler.py +0 -0
  93. {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/test.pdf +0 -0
  94. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/Makefile +0 -0
  95. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/examples/extras/README.rst +0 -0
  96. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/examples/extras/distributions.py +0 -0
  97. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/examples/extras/scatterer_distribution.py +0 -0
  98. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/examples/extras/signal_acquisition.py +0 -0
  99. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/examples/noise_sources/README.rst +0 -0
  100. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/examples/noise_sources/dark_current.py +0 -0
  101. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/examples/noise_sources/shot_noise.py +0 -0
  102. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/examples/noise_sources/thermal.py +0 -0
  103. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/examples/tutorials/README.rst +0 -0
  104. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/examples/tutorials/limit_of_detection.py +0 -0
  105. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/examples/tutorials/workflow.py +0 -0
  106. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/images/distributions/Delta.png +0 -0
  107. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/images/distributions/LogNormal.png +0 -0
  108. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/images/distributions/Normal.png +0 -0
  109. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/images/distributions/RosinRammler.png +0 -0
  110. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/images/distributions/Uniform.png +0 -0
  111. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/images/distributions/Weibull.png +0 -0
  112. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/images/example_0.png +0 -0
  113. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/images/example_1.png +0 -0
  114. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/images/example_2.png +0 -0
  115. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/images/example_3.png +0 -0
  116. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/images/flow_cytometer.png +0 -0
  117. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/images/logo.png +0 -0
  118. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/make.bat +0 -0
  119. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/_static/default.css +0 -0
  120. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/_static/logo.png +0 -0
  121. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/_static/thumbnail.png +0 -0
  122. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/code/base.rst +0 -0
  123. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/code/detector.rst +0 -0
  124. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/code/distributions.rst +0 -0
  125. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/code/flow_cell.rst +0 -0
  126. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/code/flow_cytometer.rst +0 -0
  127. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/code/peak_locator.rst +0 -0
  128. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/code/scatterer.rst +0 -0
  129. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/code/source.rst +0 -0
  130. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/code.rst +0 -0
  131. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/conf.py +0 -0
  132. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/examples.rst +0 -0
  133. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/index.rst +0 -0
  134. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/internal/core_components.rst +0 -0
  135. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/internal/getting_started.rst +0 -0
  136. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/internal/objectives/main.rst +0 -0
  137. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/internal/objectives/pre.rst +0 -0
  138. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/internal/objectives/stretch.rst +0 -0
  139. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/internal/prerequisites/index.rst +0 -0
  140. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/internal/prerequisites/mathematics.rst +0 -0
  141. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/internal/prerequisites/optics.rst +0 -0
  142. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/internal/prerequisites/programming.rst +0 -0
  143. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/internal/ressources.rst +0 -0
  144. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/internal/tasks.rst +0 -0
  145. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/internal.rst +0 -0
  146. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/references.rst +0 -0
  147. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/sg_execution_times.rst +0 -0
  148. {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/theory.rst +0 -0
  149. {flowcypy-0.7.3 → flowcypy-0.7.4}/meta.yaml +0 -0
  150. {flowcypy-0.7.3 → flowcypy-0.7.4}/notebook.ipynb +0 -0
  151. {flowcypy-0.7.3 → flowcypy-0.7.4}/pyproject.toml +0 -0
  152. {flowcypy-0.7.3 → flowcypy-0.7.4}/setup.cfg +0 -0
  153. {flowcypy-0.7.3 → flowcypy-0.7.4}/tests/__init__.py +0 -0
  154. {flowcypy-0.7.3 → flowcypy-0.7.4}/tests/test_classifiers.py +0 -0
  155. {flowcypy-0.7.3 → flowcypy-0.7.4}/tests/test_detector_noise.py +0 -0
  156. {flowcypy-0.7.3 → flowcypy-0.7.4}/tests/test_distribution.py +0 -0
  157. {flowcypy-0.7.3 → flowcypy-0.7.4}/tests/test_flow_cytometer.py +0 -0
  158. {flowcypy-0.7.3 → flowcypy-0.7.4}/tests/test_noises.py +0 -0
  159. {flowcypy-0.7.3 → flowcypy-0.7.4}/tests/test_peak_algorithm.py +0 -0
  160. {flowcypy-0.7.3 → flowcypy-0.7.4}/tests/test_peak_analyzer.py +0 -0
  161. {flowcypy-0.7.3 → flowcypy-0.7.4}/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.7.3'
16
- __version_tuple__ = version_tuple = (0, 7, 3)
15
+ __version__ = version = '0.7.4'
16
+ __version_tuple__ = version_tuple = (0, 7, 4)
@@ -465,21 +465,32 @@ class Acquisition:
465
465
  def __init__(self, acquisition: object):
466
466
  self.acquisition = acquisition
467
467
 
468
- def signals(self, figure_size: tuple = (10, 6), show: bool = True) -> None:
468
+ def signals(
469
+ self,
470
+ figure_size: tuple = (10, 6),
471
+ show: bool = True,
472
+ show_populations: str | List[str] = None,
473
+ save_filename: str = None
474
+ ) -> None:
469
475
  """
470
476
  Visualizes raw signals for all detector channels and the scatterer distribution.
471
477
 
472
478
  Parameters
473
479
  ----------
474
480
  figure_size : tuple, optional
475
- Size of the plot (default: (10, 6)).
476
- add_peak_locator : bool, optional
477
- Adds peak location markers to the signals if True (default: False).
481
+ Size of the plot in inches (default: (10, 6)).
478
482
  show : bool, optional
479
- Displays the plot immediately if True (default: True).
483
+ If True, displays the plot immediately (default: True).
484
+ show_populations : str or list of str, optional
485
+ List of population names to highlight in the event plot. If None, shows all populations.
486
+ save_filename : str, optional
487
+ If provided, saves the figure to the specified file.
480
488
  """
481
- n_plots = self.acquisition.n_detectors + 1
489
+ # Handle `show_populations` default case
490
+ if show_populations is None:
491
+ show_populations = self.acquisition.data.scatterer.index.get_level_values('Population').unique()
482
492
 
493
+ n_plots = self.acquisition.n_detectors + 1 # One extra plot for events
483
494
  time_units = self.acquisition.data.continuous.Time.max().to_compact().units
484
495
 
485
496
  with plt.style.context(mps):
@@ -488,52 +499,99 @@ class Acquisition:
488
499
  nrows=n_plots,
489
500
  figsize=figure_size,
490
501
  sharex=True,
491
- height_ratios=[1] * (n_plots - 1) + [0.5],
502
+ height_ratios=[1] * (n_plots - 1) + [0.5]
492
503
  )
493
504
 
505
+ # Plot digitized and continuous signals for each detector
494
506
  for ax, (detector_name, group) in zip(axes[:-1], self.acquisition.data.continuous.groupby("Detector")):
495
507
  detector = self.get_detector(detector_name)
496
508
 
497
- ax.step(group["Time"].pint.to(time_units), group["DigitizedSignal"], label="Digitized Signal", where='mid')
509
+ # Convert time and signal data to the appropriate units
510
+ time_data = group["Time"].pint.to(time_units)
511
+ digitized_signal = group["DigitizedSignal"]
512
+
513
+ # Plot digitized signal
514
+ ax.step(time_data, digitized_signal, label="Digitized Signal", where='mid')
498
515
  ax.set_ylabel(detector_name)
499
516
  ax.set_ylim([0, self.acquisition.cytometer.signal_digitizer._bit_depth])
500
517
 
518
+ # Twin axis for continuous signal
501
519
  ax2 = ax.twinx()
502
- ax2_x = self.acquisition.data.continuous.loc[detector_name, 'Time']
503
- ax2_y = self.acquisition.data.continuous.loc[detector_name, 'Signal']
504
- ax2_y_units = ax2_y.max().to_compact().units
505
- ax2.plot(
506
- ax2_x.pint.to(time_units),
507
- ax2_y.pint.to(ax2_y_units),
508
- color='black',
509
- linewidth=1,
510
- linestyle='-',
511
- label='Continuous signal',
512
- zorder=0,
513
- )
514
-
515
- ax2.set_ylim(detector._saturation_levels if detector._saturation_levels[0] != detector._saturation_levels[1] else None)
520
+ cont_time, cont_signal, cont_signal_units = self._get_continuous_signal(detector_name, time_units)
521
+ ax2.plot(cont_time, cont_signal, color='black', linewidth=1, linestyle='-', label='Continuous Signal', zorder=0)
516
522
 
517
- self._add_event_to_ax(ax=axes[-1], time_units=time_units)
523
+ # Set y-limits for the continuous signal
524
+ if detector._saturation_levels[0] != detector._saturation_levels[1]:
525
+ ax2.set_ylim(detector._saturation_levels)
518
526
 
527
+ # Add event markers to the last subplot
528
+ self._add_event_to_ax(ax=axes[-1], time_units=time_units, show_populations=show_populations)
519
529
  axes[-1].set_xlabel(f"Time [{time_units}]")
530
+
531
+ # Save or show the figure
532
+ if save_filename:
533
+ fig.savefig(fname=save_filename)
520
534
  if show:
521
535
  plt.show()
522
536
 
523
- def _add_event_to_ax(self, ax: plt.Axes, time_units: units.Quantity, palette: str = 'tab10') -> None:
537
+
538
+ def _get_continuous_signal(self, detector_name: str, time_units: units.Quantity):
539
+ """
540
+ Retrieves and converts the continuous signal data for a given detector.
541
+
542
+ Parameters
543
+ ----------
544
+ detector_name : str
545
+ Name of the detector.
546
+ time_units : units.Quantity
547
+ Desired time units.
548
+
549
+ Returns
550
+ -------
551
+ tuple
552
+ (time array, signal array, signal units)
553
+ """
554
+ data = self.acquisition.data.continuous.loc[detector_name]
555
+ cont_time = data['Time'].pint.to(time_units)
556
+ cont_signal = data['Signal']
557
+ cont_signal_units = cont_signal.max().to_compact().units
558
+ return cont_time, cont_signal.pint.to(cont_signal_units), cont_signal_units
559
+
560
+ def _add_event_to_ax(
561
+ self,
562
+ ax: plt.Axes,
563
+ time_units: units.Quantity,
564
+ palette: str = 'tab10',
565
+ show_populations: str | List[str] = None
566
+ ) -> None:
567
+ """
568
+ Adds vertical markers for event occurrences in the scatterer data.
569
+
570
+ Parameters
571
+ ----------
572
+ ax : plt.Axes
573
+ The matplotlib axis to modify.
574
+ time_units : units.Quantity
575
+ Time units to use for plotting.
576
+ palette : str, optional
577
+ Color palette for different populations (default: 'tab10').
578
+ show_populations : str or list of str, optional
579
+ Populations to display. If None, all populations are shown.
580
+ """
581
+ # Get unique population names
524
582
  unique_populations = self.acquisition.data.scatterer.index.get_level_values('Population').unique()
525
583
  color_mapping = dict(zip(unique_populations, sns.color_palette(palette, len(unique_populations))))
526
584
 
527
585
  for population_name, group in self.acquisition.data.scatterer.groupby('Population'):
586
+ if show_populations is not None and population_name not in show_populations:
587
+ continue
528
588
  x = group.Time.pint.to(time_units)
529
589
  color = color_mapping[population_name]
530
590
  ax.vlines(x, ymin=0, ymax=1, transform=ax.get_xaxis_transform(), label=population_name, color=color)
531
591
 
532
592
  ax.tick_params(axis='y', left=False, labelleft=False)
533
-
534
593
  ax.get_yaxis().set_visible(False)
535
594
  ax.set_xlabel(f"Time [{time_units}]")
536
-
537
595
  ax.legend()
538
596
 
539
597
  @helper.plot_sns
@@ -13,6 +13,7 @@ from FlowCyPy.flow_cell import FlowCell
13
13
  from FlowCyPy.detector import Detector
14
14
  from FlowCyPy.acquisition import Acquisition
15
15
  from FlowCyPy.signal_digitizer import SignalDigitizer
16
+ from FlowCyPy.helper import validate_units
16
17
 
17
18
 
18
19
  # Set up logging configuration
@@ -87,7 +88,7 @@ class FlowCytometer:
87
88
  for detector in detectors:
88
89
  detector.cytometer = self
89
90
 
90
- def run_coupling_analysis(self, scatterer_dataframe: pd.DataFrame) -> None:
91
+ def _run_coupling_analysis(self, scatterer_dataframe: pd.DataFrame) -> None:
91
92
  """
92
93
  Computes and assigns the optical coupling power for each particle-detection event.
93
94
 
@@ -184,7 +185,7 @@ class FlowCytometer:
184
185
 
185
186
  scatterer_dataframe['Widths'] = PintArray(widths, dtype=widths.units)
186
187
 
187
- def initialize_signal(self, run_time: Quantity) -> None:
188
+ def _initialize_signal(self, run_time: Quantity) -> None:
188
189
  """
189
190
  Initializes the raw signal for each detector based on the source and flow cell configuration.
190
191
 
@@ -209,6 +210,7 @@ class FlowCytometer:
209
210
 
210
211
  self.dataframe.index.names = ["Detector", "Index"]
211
212
 
213
+ @validate_units(run_time=units.second)
212
214
  def get_acquisition(self, run_time: Quantity) -> None:
213
215
  """
214
216
  Simulates the generation of optical signal pulses for each particle event.
@@ -231,13 +233,13 @@ class FlowCytometer:
231
233
  if not run_time.check('second'):
232
234
  raise ValueError(f"flow_speed must be in meter per second, but got {run_time.units}")
233
235
 
234
- self.initialize_signal(run_time=run_time)
236
+ self._initialize_signal(run_time=run_time)
235
237
 
236
- scatterer_dataframe = self.flow_cell.generate_event_dataframe(self.scatterer_collection.populations, run_time=run_time)
238
+ scatterer_dataframe = self.flow_cell._generate_event_dataframe(self.scatterer_collection.populations, run_time=run_time)
237
239
 
238
240
  self.scatterer_collection.fill_dataframe_with_sampling(scatterer_dataframe)
239
241
 
240
- self.run_coupling_analysis(scatterer_dataframe)
242
+ self._run_coupling_analysis(scatterer_dataframe)
241
243
 
242
244
  self._generate_pulse_parameters(scatterer_dataframe)
243
245
 
@@ -12,6 +12,7 @@ from FlowCyPy.population import Population
12
12
  from FlowCyPy.scatterer_collection import ScattererCollection
13
13
  from FlowCyPy.units import meter, particle, Quantity
14
14
  from FlowCyPy import units
15
+ from FlowCyPy.helper import validate_units
15
16
 
16
17
  config_dict = dict(
17
18
  arbitrary_types_allowed=True,
@@ -21,6 +22,7 @@ config_dict = dict(
21
22
  )
22
23
 
23
24
 
25
+
24
26
  @dataclass(config=config_dict)
25
27
  class FlowCell:
26
28
  """
@@ -63,12 +65,14 @@ class FlowCell:
63
65
  raise ValueError(f"flow_area must be in meter ** 2, but got {value.units}")
64
66
  return value
65
67
 
68
+ @validate_units(run_time=units.second)
66
69
  def get_volume(self, run_time: Quantity) -> Quantity:
67
70
  """
68
71
  Computes the volume passing through the flow cell over the given run time.
69
72
  """
70
- return self.flow_area * self.flow_speed * run_time
73
+ return (self.volume_flow * run_time).to_compact()
71
74
 
75
+ @validate_units(run_time=units.second)
72
76
  def get_population_sampling(self, run_time: Quantity, scatterer_collection: ScattererCollection) -> list[Quantity]:
73
77
  """
74
78
  Calculates the number of events (or particle counts) for each population based on the run time.
@@ -82,12 +86,11 @@ class FlowCell:
82
86
  for p in scatterer_collection.populations
83
87
  ]
84
88
 
85
- def generate_event_dataframe(self, populations: List[Population], run_time: Quantity) -> pd.DataFrame:
89
+ def _generate_event_dataframe(self, populations: List[Population], run_time: Quantity) -> pd.DataFrame:
86
90
  """
87
91
  Generates a DataFrame of event times for each population based on the specified scheme.
88
92
  """
89
93
  # Generate individual DataFrames for each population
90
-
91
94
  population_event_frames = [
92
95
  self._generate_poisson_events(population=population, run_time=run_time)
93
96
  for population in populations
@@ -2,6 +2,49 @@ from typing import Callable
2
2
  import matplotlib.pyplot as plt
3
3
  from MPSPlots.styles import mps
4
4
 
5
+ from functools import wraps
6
+ import inspect
7
+ from FlowCyPy.units import Quantity
8
+
9
+ def validate_units(**expected_units):
10
+ """
11
+ Decorator to enforce that function arguments of type Quantity have the correct units.
12
+
13
+ Parameters
14
+ ----------
15
+ expected_units : dict
16
+ A dictionary where keys are argument names and values are the expected Pint units.
17
+
18
+ Raises
19
+ ------
20
+ ValueError
21
+ If any argument does not have the expected unit.
22
+ """
23
+ def decorator(func):
24
+ @wraps(func)
25
+ def wrapper(*args, **kwargs):
26
+ # Get function signature and argument values
27
+ sig = inspect.signature(func)
28
+ bound_args = sig.bind(*args, **kwargs)
29
+ bound_args.apply_defaults()
30
+
31
+ for arg_name, expected_unit in expected_units.items():
32
+ if arg_name in bound_args.arguments:
33
+ value = bound_args.arguments[arg_name]
34
+
35
+ # Check if the value is a Pint Quantity
36
+ if not isinstance(value, Quantity):
37
+ raise TypeError(f"Argument '{arg_name}' must be a Pint Quantity, but got {type(value)}")
38
+
39
+ # Check if the argument has the expected units
40
+ if not value.check(value.units):
41
+ raise ValueError(f"Argument '{arg_name}' must have units of {expected_unit}, but got {value.units}")
42
+
43
+ return func(*bound_args.args, **bound_args.kwargs)
44
+
45
+ return wrapper
46
+ return decorator
47
+
5
48
 
6
49
  def plot_sns(function: Callable) -> Callable:
7
50
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: FlowCyPy
3
- Version: 0.7.3
3
+ Version: 0.7.4
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: FlowCyPy
3
- Version: 0.7.3
3
+ Version: 0.7.4
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,70 @@
1
+ from FlowCyPy import FlowCytometer, ScattererCollection, Detector, GaussianBeam, FlowCell
2
+ from FlowCyPy import distribution, Population
3
+ from FlowCyPy.signal_digitizer import SignalDigitizer
4
+ from FlowCyPy import units
5
+
6
+ source = GaussianBeam(
7
+ numerical_aperture=0.4 * units.AU, # Numerical aperture: 0.4
8
+ wavelength=1550 * units.nanometer, # Wavelength: 1550 nm
9
+ optical_power=200 * units.milliwatt # Optical power: 2 mW
10
+ )
11
+
12
+ flow_cell = FlowCell(
13
+ source=source,
14
+ volume_flow=10 * units.microliter / units.second, # Flow speed: 10 microliter per second
15
+ flow_area=(40 * units.micrometer) ** 2, # Flow area: 40 x 40 micrometers
16
+ )
17
+
18
+ LP_concentration = 40_000
19
+
20
+ scatterer_collection = ScattererCollection(medium_refractive_index=1.33 * units.RIU)
21
+
22
+ population_0 = Population(
23
+ name='EV',
24
+ particle_count=1e+9 * units.particle / units.milliliter,
25
+ size=distribution.RosinRammler(characteristic_size=200 * units.nanometer, spread=4.5),
26
+ refractive_index=distribution.Normal(mean=1.42 * units.RIU, std_dev=0.05 * units.RIU)
27
+ )
28
+
29
+ population_1 = Population(
30
+ name='LP',
31
+ particle_count=LP_concentration * 1e+9 * units.particle / units.milliliter,
32
+ size=distribution.RosinRammler(characteristic_size=100 * units.nanometer, spread=4.5),
33
+ refractive_index=distribution.Normal(mean=1.39 * units.RIU, std_dev=0.05 * units.RIU)
34
+ )
35
+
36
+ scatterer_collection.add_population(population_1, population_0)
37
+
38
+ scatterer_collection.dilute(100)
39
+
40
+ signal_digitizer = SignalDigitizer(
41
+ bit_depth=1024,
42
+ saturation_levels='auto',
43
+ sampling_freq=10 * units.megahertz, # Sampling frequency: 10 MHz
44
+ )
45
+
46
+ detector_fsc = Detector(
47
+ name='FSC', # Forward Scatter detector
48
+ numerical_aperture=1.2 * units.AU, # Numerical aperture: 0.2
49
+ phi_angle=0 * units.degree, # Angle: 180 degrees for forward scatter
50
+ )
51
+
52
+ detector_ssc = Detector(
53
+ name='SSC', # Side Scatter detector
54
+ numerical_aperture=1.2 * units.AU, # Numerical aperture: 0.2
55
+ phi_angle=90 * units.degree, # Angle: 90 degrees for side scatter
56
+ )
57
+
58
+ cytometer = FlowCytometer(
59
+ signal_digitizer=signal_digitizer,
60
+ scatterer_collection=scatterer_collection,
61
+ flow_cell=flow_cell,
62
+ detectors=[detector_fsc, detector_ssc], # Detectors: FSC and SSC
63
+ )
64
+
65
+ acquisition = cytometer.get_acquisition(run_time=0.2 * units.millisecond)
66
+
67
+ acquisition.plot.signals(
68
+ show_populations=['EV'],
69
+ # save_filename=f'LP_{LP_concentration}.png'
70
+ )
@@ -45,19 +45,23 @@ scatterer_collection = ScattererCollection(medium_refractive_index=1.33 * units.
45
45
 
46
46
  population_0 = Population(
47
47
  name='EV',
48
- particle_count=1e+9 * units.particle / units.milliliter,
49
- size=distribution.RosinRammler(characteristic_size=50 * units.nanometer, spread=4.5),
50
- refractive_index=distribution.Normal(mean=1.39 * units.RIU, std_dev=0.05 * units.RIU)
48
+ # particle_count=1e+9 * units.particle / units.milliliter,
49
+ particle_count=10 * units.particle,
50
+ size=distribution.RosinRammler(characteristic_size=200 * units.nanometer, spread=4.5),
51
+ refractive_index=distribution.Normal(mean=1.42 * units.RIU, std_dev=0.05 * units.RIU)
51
52
  )
52
53
 
53
54
  population_1 = Population(
54
55
  name='LP',
55
- particle_count=1e+9 * units.particle / units.milliliter,
56
- size=distribution.RosinRammler(characteristic_size=200 * units.nanometer, spread=4.5),
57
- refractive_index=distribution.Normal(mean=1.45 * units.RIU, std_dev=0.05 * units.RIU)
56
+ # particle_count=1e+9 * units.particle / units.milliliter,
57
+ particle_count=50_000 * units.particle,
58
+ size=distribution.RosinRammler(characteristic_size=100 * units.nanometer, spread=4.5),
59
+ refractive_index=distribution.Normal(mean=1.39 * units.RIU, std_dev=0.05 * units.RIU)
58
60
  )
59
61
 
60
- scatterer_collection.add_population(population_0, population_1)
62
+ scatterer_collection.add_population(population_1, population_0)
63
+
64
+ # scatterer_collection.dilute(100)
61
65
 
62
66
  # Step 5: Set up the detectors
63
67
  # ----------------------------
@@ -99,7 +103,7 @@ cytometer = FlowCytometer(
99
103
  acquisition = cytometer.get_acquisition(run_time=0.2 * units.millisecond)
100
104
 
101
105
  # Visualize the scatter signals from both detectors
102
- acquisition.plot.signals()
106
+ acquisition.plot.signals(show_populations=['EV'])
103
107
 
104
108
  # %%
105
109
  #
@@ -83,7 +83,7 @@ def test_generate_scatterer_size(scatterer_collection, default_flow_cell):
83
83
  """
84
84
  Test if the sizes are generated correctly in the ScattererCollection.
85
85
  """
86
- scatterer_dataframe = default_flow_cell.generate_event_dataframe(
86
+ scatterer_dataframe = default_flow_cell._generate_event_dataframe(
87
87
  scatterer_collection.populations,
88
88
  run_time=0.001 * units.second
89
89
  )
@@ -101,7 +101,7 @@ def test_rayleigh_mechanism_output(detector, scatterer_collection, source, defau
101
101
  """
102
102
  Test the detected power output of the Rayleigh scattering mechanism.
103
103
  """
104
- scatterer_dataframe = default_flow_cell.generate_event_dataframe(
104
+ scatterer_dataframe = default_flow_cell._generate_event_dataframe(
105
105
  scatterer_collection.populations,
106
106
  run_time=0.001 * units.second
107
107
  )
@@ -62,7 +62,7 @@ def scatterer_collection(populations):
62
62
 
63
63
  @pytest.fixture
64
64
  def population_dataframe(flow_cell, populations):
65
- dataframe = flow_cell.generate_event_dataframe(populations, run_time=RUN_TIME)
65
+ dataframe = flow_cell._generate_event_dataframe(populations, run_time=RUN_TIME)
66
66
 
67
67
  return dataframe
68
68
 
@@ -53,7 +53,7 @@ def test_generate_distribution_size(distribution, default_flow_cell):
53
53
 
54
54
  scatterer_collection.add_population(population_0)
55
55
 
56
- dataframe = default_flow_cell.generate_event_dataframe(
56
+ dataframe = default_flow_cell._generate_event_dataframe(
57
57
  scatterer_collection.populations,
58
58
  run_time=100e-4 * units.second
59
59
  )
@@ -115,7 +115,7 @@ def test_generate_longitudinal_positions(default_flow_cell, distribution):
115
115
 
116
116
  scatterer_collection.add_population(population_0)
117
117
 
118
- dataframe = default_flow_cell.generate_event_dataframe(
118
+ dataframe = default_flow_cell._generate_event_dataframe(
119
119
  scatterer_collection.populations,
120
120
  run_time=100e-4 * units.second
121
121
  )