FlowCyPy 0.7.0__tar.gz → 0.7.1__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 (150) hide show
  1. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/_version.py +2 -2
  2. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/acquisition.py +109 -43
  3. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/classifier.py +21 -19
  4. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/distribution/particle_size_distribution.py +6 -5
  5. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/flow_cell.py +0 -1
  6. flowcypy-0.7.1/FlowCyPy/helper.py +166 -0
  7. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy.egg-info/PKG-INFO +1 -1
  8. {flowcypy-0.7.0 → flowcypy-0.7.1}/PKG-INFO +1 -1
  9. flowcypy-0.7.1/developments/scripts/temp.py +219 -0
  10. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/examples/tutorials/workflow.py +10 -11
  11. flowcypy-0.7.0/FlowCyPy/helper.py +0 -81
  12. flowcypy-0.7.0/developments/scripts/temp.py +0 -127
  13. {flowcypy-0.7.0 → flowcypy-0.7.1}/.flake8 +0 -0
  14. {flowcypy-0.7.0 → flowcypy-0.7.1}/.github/dependabot.yml +0 -0
  15. {flowcypy-0.7.0 → flowcypy-0.7.1}/.github/workflows/deploy_PyPi.yml +0 -0
  16. {flowcypy-0.7.0 → flowcypy-0.7.1}/.github/workflows/deploy_anaconda.yml +0 -0
  17. {flowcypy-0.7.0 → flowcypy-0.7.1}/.github/workflows/deploy_coverage.yml +0 -0
  18. {flowcypy-0.7.0 → flowcypy-0.7.1}/.github/workflows/deploy_documentation.yml +0 -0
  19. {flowcypy-0.7.0 → flowcypy-0.7.1}/.gitignore +0 -0
  20. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/__init__.py +0 -0
  21. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/coupling_mechanism/__init__.py +0 -0
  22. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/coupling_mechanism/empirical.py +0 -0
  23. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/coupling_mechanism/mie.py +0 -0
  24. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/coupling_mechanism/rayleigh.py +0 -0
  25. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/coupling_mechanism/uniform.py +0 -0
  26. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/coupling_mechanism.py +0 -0
  27. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/cytometer.py +0 -0
  28. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/detector.py +0 -0
  29. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/directories.py +0 -0
  30. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/distribution/__init__.py +0 -0
  31. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/distribution/base_class.py +0 -0
  32. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/distribution/delta.py +0 -0
  33. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/distribution/lognormal.py +0 -0
  34. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/distribution/normal.py +0 -0
  35. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/distribution/uniform.py +0 -0
  36. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/distribution/weibull.py +0 -0
  37. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/logger.py +0 -0
  38. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/noises.py +0 -0
  39. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/particle_count.py +0 -0
  40. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/peak_locator/__init__.py +0 -0
  41. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/peak_locator/base_class.py +0 -0
  42. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/peak_locator/basic.py +0 -0
  43. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/peak_locator/derivative.py +0 -0
  44. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/peak_locator/moving_average.py +0 -0
  45. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/physical_constant.py +0 -0
  46. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/plottings.py +0 -0
  47. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/population.py +0 -0
  48. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/populations_instances.py +0 -0
  49. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/scatterer_collection.py +0 -0
  50. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/signal_digitizer.py +0 -0
  51. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/source.py +0 -0
  52. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/units.py +0 -0
  53. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/utils.py +0 -0
  54. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy.egg-info/SOURCES.txt +0 -0
  55. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy.egg-info/dependency_links.txt +0 -0
  56. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy.egg-info/requires.txt +0 -0
  57. {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy.egg-info/top_level.txt +0 -0
  58. {flowcypy-0.7.0 → flowcypy-0.7.1}/LICENSE +0 -0
  59. {flowcypy-0.7.0 → flowcypy-0.7.1}/README.rst +0 -0
  60. {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/doc/canto_spec.md +0 -0
  61. {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/doc/internship.pdf +0 -0
  62. {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/get_started.md +0 -0
  63. {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/image.png +0 -0
  64. {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/output_file.prof +0 -0
  65. {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/concentration_comparison.py +0 -0
  66. {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/create_images.py +0 -0
  67. {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/data_analysis.py +0 -0
  68. {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/dev_beads_analysis.py +0 -0
  69. {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/dev_canto.py +0 -0
  70. {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/dev_classifier.py +0 -0
  71. {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/dev_shot_noise_check.py +0 -0
  72. {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/dev_stats_0.py +0 -0
  73. {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/dev_stats_1.py +0 -0
  74. {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/dev_stats_2.py +0 -0
  75. {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/dev_study_on_ri.py +0 -0
  76. {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/dev_study_on_size.py +0 -0
  77. {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/mat2csv.py +0 -0
  78. {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/profiler.py +0 -0
  79. {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/test.pdf +0 -0
  80. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/Makefile +0 -0
  81. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/examples/extras/README.rst +0 -0
  82. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/examples/extras/distributions.py +0 -0
  83. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/examples/extras/flow_cytometer_signal.py +0 -0
  84. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/examples/extras/scatterer_distribution.py +0 -0
  85. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/examples/extras/signal_acquisition.py +0 -0
  86. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/examples/noise_sources/README.rst +0 -0
  87. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/examples/noise_sources/dark_current.py +0 -0
  88. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/examples/noise_sources/shot_noise.py +0 -0
  89. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/examples/noise_sources/thermal.py +0 -0
  90. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/examples/tutorials/README.rst +0 -0
  91. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/examples/tutorials/limit_of_detection.py +0 -0
  92. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/images/distributions/Delta.png +0 -0
  93. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/images/distributions/LogNormal.png +0 -0
  94. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/images/distributions/Normal.png +0 -0
  95. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/images/distributions/RosinRammler.png +0 -0
  96. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/images/distributions/Uniform.png +0 -0
  97. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/images/distributions/Weibull.png +0 -0
  98. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/images/example_0.png +0 -0
  99. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/images/example_1.png +0 -0
  100. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/images/example_2.png +0 -0
  101. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/images/example_3.png +0 -0
  102. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/images/flow_cytometer.png +0 -0
  103. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/images/logo.png +0 -0
  104. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/make.bat +0 -0
  105. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/_static/default.css +0 -0
  106. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/_static/logo.png +0 -0
  107. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/_static/thumbnail.png +0 -0
  108. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/code/base.rst +0 -0
  109. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/code/detector.rst +0 -0
  110. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/code/distributions.rst +0 -0
  111. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/code/flow_cell.rst +0 -0
  112. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/code/flow_cytometer.rst +0 -0
  113. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/code/peak_locator.rst +0 -0
  114. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/code/scatterer.rst +0 -0
  115. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/code/source.rst +0 -0
  116. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/code.rst +0 -0
  117. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/conf.py +0 -0
  118. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/examples.rst +0 -0
  119. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/index.rst +0 -0
  120. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/internal/core_components.rst +0 -0
  121. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/internal/getting_started.rst +0 -0
  122. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/internal/objectives/main.rst +0 -0
  123. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/internal/objectives/pre.rst +0 -0
  124. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/internal/objectives/stretch.rst +0 -0
  125. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/internal/prerequisites/index.rst +0 -0
  126. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/internal/prerequisites/mathematics.rst +0 -0
  127. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/internal/prerequisites/optics.rst +0 -0
  128. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/internal/prerequisites/programming.rst +0 -0
  129. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/internal/ressources.rst +0 -0
  130. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/internal/tasks.rst +0 -0
  131. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/internal.rst +0 -0
  132. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/references.rst +0 -0
  133. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/sg_execution_times.rst +0 -0
  134. {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/theory.rst +0 -0
  135. {flowcypy-0.7.0 → flowcypy-0.7.1}/meta.yaml +0 -0
  136. {flowcypy-0.7.0 → flowcypy-0.7.1}/notebook.ipynb +0 -0
  137. {flowcypy-0.7.0 → flowcypy-0.7.1}/pyproject.toml +0 -0
  138. {flowcypy-0.7.0 → flowcypy-0.7.1}/setup.cfg +0 -0
  139. {flowcypy-0.7.0 → flowcypy-0.7.1}/tests/__init__.py +0 -0
  140. {flowcypy-0.7.0 → flowcypy-0.7.1}/tests/test_coupling_mechanism.py +0 -0
  141. {flowcypy-0.7.0 → flowcypy-0.7.1}/tests/test_detector_noise.py +0 -0
  142. {flowcypy-0.7.0 → flowcypy-0.7.1}/tests/test_distribution.py +0 -0
  143. {flowcypy-0.7.0 → flowcypy-0.7.1}/tests/test_extra.py +0 -0
  144. {flowcypy-0.7.0 → flowcypy-0.7.1}/tests/test_flow_cytometer.py +0 -0
  145. {flowcypy-0.7.0 → flowcypy-0.7.1}/tests/test_noises.py +0 -0
  146. {flowcypy-0.7.0 → flowcypy-0.7.1}/tests/test_peak_algorithm.py +0 -0
  147. {flowcypy-0.7.0 → flowcypy-0.7.1}/tests/test_peak_analyzer.py +0 -0
  148. {flowcypy-0.7.0 → flowcypy-0.7.1}/tests/test_population.py +0 -0
  149. {flowcypy-0.7.0 → flowcypy-0.7.1}/tests/test_scatterer_distribution.py +0 -0
  150. {flowcypy-0.7.0 → flowcypy-0.7.1}/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.0'
16
- __version_tuple__ = version_tuple = (0, 7, 0)
15
+ __version__ = version = '0.7.1'
16
+ __version_tuple__ = version_tuple = (0, 7, 1)
@@ -10,6 +10,7 @@ import matplotlib.pyplot as plt
10
10
  import seaborn as sns
11
11
  from tabulate import tabulate
12
12
  import warnings
13
+ from FlowCyPy import helper
13
14
 
14
15
  class DataAccessor:
15
16
  def __init__(self, outer):
@@ -110,13 +111,36 @@ class Acquisition:
110
111
  )
111
112
 
112
113
  def _get_trigger_indices(
113
- self,
114
- threshold: units.Quantity,
115
- trigger_detector_name: str = None,
116
- pre_buffer: int = 64,
117
- post_buffer: int = 64) -> tuple[np.ndarray, np.ndarray]:
114
+ self,
115
+ threshold: units.Quantity,
116
+ trigger_detector_name: str = None,
117
+ pre_buffer: int = 64,
118
+ post_buffer: int = 64
119
+ ) -> tuple[np.ndarray, np.ndarray]:
118
120
  """
119
- Calculate start and end indices for triggered segments.
121
+ Calculate start and end indices for triggered segments, ensuring no retriggering
122
+ occurs during an active buffer period.
123
+
124
+ Parameters
125
+ ----------
126
+ threshold : units.Quantity
127
+ The threshold value for triggering.
128
+ trigger_detector_name : str, optional
129
+ The name of the detector to use for the triggering signal.
130
+ pre_buffer : int, optional
131
+ Number of samples to include before the trigger point.
132
+ post_buffer : int, optional
133
+ Number of samples to include after the trigger point.
134
+
135
+ Returns
136
+ -------
137
+ tuple[np.ndarray, np.ndarray]
138
+ The start and end indices of non-overlapping triggered segments.
139
+
140
+ Raises
141
+ ------
142
+ ValueError
143
+ If the specified detector is not found in the data.
120
144
  """
121
145
  if trigger_detector_name not in self.data.continuous.index.get_level_values('Detector').unique():
122
146
  raise ValueError(f"Detector '{trigger_detector_name}' not found.")
@@ -128,7 +152,18 @@ class Acquisition:
128
152
  start_indices = np.clip(crossings - pre_buffer, 0, len(trigger_signal) - 1)
129
153
  end_indices = np.clip(crossings + post_buffer, 0, len(trigger_signal) - 1)
130
154
 
131
- return start_indices, end_indices
155
+ # Suppress retriggering within an active buffer period
156
+ suppressed_start_indices = []
157
+ suppressed_end_indices = []
158
+
159
+ last_end = -1
160
+ for start, end in zip(start_indices, end_indices):
161
+ if start > last_end: # Ensure no overlap with the last active buffer
162
+ suppressed_start_indices.append(start)
163
+ suppressed_end_indices.append(end)
164
+ last_end = end # Update the end of the current active buffer
165
+
166
+ return np.array(suppressed_start_indices), np.array(suppressed_end_indices)
132
167
 
133
168
  def run_triggering(self,
134
169
  threshold: units.Quantity,
@@ -191,6 +226,16 @@ class Acquisition:
191
226
 
192
227
  self.detect_peaks()
193
228
 
229
+ def classify_dataset(self, classifier: object, features: List[str], detectors: list[str]) -> None:
230
+ self.data.peaks = self.data.peaks.unstack('Detector')
231
+ self.classifier = classifier
232
+
233
+ self.classifier.run(
234
+ dataframe=self.data.peaks,
235
+ features=features,
236
+ detectors=detectors
237
+ )
238
+
194
239
  class LoggerInterface:
195
240
  """
196
241
  A nested class for logging statistical information about the experiment.
@@ -443,7 +488,8 @@ class Acquisition:
443
488
 
444
489
  ax.legend()
445
490
 
446
- def coupling_distribution(self, x_detector: str, y_detector: str, log_scale: bool = False, show: bool = True, equal_limits: bool = False, save_path: str = None) -> None:
491
+ @helper.plot_sns
492
+ def coupling_distribution(self, x_detector: str, y_detector: str, equal_limits: bool = False) -> None:
447
493
  """
448
494
  Plots the density distribution of optical coupling between two detector channels.
449
495
 
@@ -466,31 +512,17 @@ class Acquisition:
466
512
  y = df[y_detector].pint.to(y_units)
467
513
 
468
514
  with plt.style.context(mps):
469
- joint_plot = sns.jointplot(data=df, x=x, y=y, hue="Population", alpha=0.8)
470
-
471
- if log_scale:
472
- joint_plot.ax_joint.set_xscale("log")
473
- joint_plot.ax_joint.set_yscale("log")
515
+ grid = sns.jointplot(data=df, x=x, y=y, hue="Population", alpha=0.8)
474
516
 
475
- if equal_limits:
476
- min_limit = min(x.min(), y.min())
477
- max_limit = max(x.max(), y.max())
478
- joint_plot.ax_joint.set_xlim(min_limit, max_limit)
479
- joint_plot.ax_joint.set_ylim(min_limit, max_limit)
517
+ grid.ax_joint.set_xlabel(f"Signal {x_detector} [{x_units}]")
518
+ grid.ax_joint.set_ylabel(f"Signal {y_detector} [{y_units}]")
480
519
 
481
- joint_plot.ax_joint.set_xlabel(f"Signal {x_detector} [{x_units}]")
482
- joint_plot.ax_joint.set_ylabel(f"Signal {y_detector} [{y_units}]")
520
+ grid.figure.suptitle("Theoretical coupling distribution")
483
521
 
484
- plt.tight_layout()
522
+ return grid
485
523
 
486
- if save_path:
487
- joint_plot.figure.savefig(save_path, dpi=300, bbox_inches="tight")
488
- logging.info(f"Plot saved to {save_path}")
489
-
490
- if show:
491
- plt.show()
492
-
493
- def scatterer(self, show: bool = True, alpha: float = 0.8, bandwidth_adjust: float = 1, log_scale: bool = False, color_palette: Optional[Union[str, dict]] = None) -> None:
524
+ @helper.plot_sns
525
+ def scatterer(self, alpha: float = 0.8, bandwidth_adjust: float = 1, log_scale: bool = False, color_palette: Optional[Union[str, dict]] = None) -> None:
494
526
  """
495
527
  Visualizes the joint distribution of scatterer sizes and refractive indices using a Seaborn jointplot.
496
528
 
@@ -504,8 +536,6 @@ class Acquisition:
504
536
  Transparency level for the scatter plot points, ranging from 0 (fully transparent) to 1 (fully opaque). Default is 0.8.
505
537
  bandwidth_adjust : float, optional
506
538
  Bandwidth adjustment factor for the kernel density estimate of the marginal distributions. Higher values produce smoother density estimates. Default is 1.
507
- log_scale : bool, optional
508
- If `True`, applies a logarithmic scale to both axes of the joint plot and their marginal distributions. Default is `False`.
509
539
  color_palette : str or dict, optional
510
540
  The color palette to use for the hue in the scatterplot. Can be a seaborn palette name
511
541
  (e.g., 'viridis', 'coolwarm') or a dictionary mapping hue levels to specific colors. Default is None.
@@ -540,19 +570,13 @@ class Acquisition:
540
570
  marginal_kws=dict(bw_adjust=bandwidth_adjust)
541
571
  )
542
572
 
543
- grid.ax_joint.set_xlabel(f"Size [{x_unit}]")
544
-
545
- if log_scale:
546
- grid.ax_joint.set_xscale('log')
547
- grid.ax_joint.set_yscale('log')
548
- grid.ax_marg_x.set_xscale('log')
549
- grid.ax_marg_y.set_yscale('log')
573
+ grid.figure.suptitle("Scatterer sampling distribution")
550
574
 
551
- plt.tight_layout()
575
+ grid.ax_joint.set_xlabel(f"Size [{x_unit}]")
552
576
 
553
- if show:
554
- plt.show()
577
+ return grid
555
578
 
579
+ @helper.plot_sns
556
580
  def peaks(self, x_detector: str, y_detector: str, signal: str = 'Height', bandwidth_adjust: float = 0.8) -> None:
557
581
  """
558
582
  Plot the joint KDE distribution of the specified signal between two detectors using seaborn,
@@ -582,11 +606,12 @@ class Acquisition:
582
606
  joint_kws={'bw_adjust': bandwidth_adjust, 'alpha': 0.7}
583
607
  )
584
608
 
609
+ grid.figure.suptitle("Peaks properties")
585
610
  grid.ax_joint.scatter(x_data, y_data, color='C1', alpha=0.6)
586
611
 
587
612
  grid.set_axis_labels(f"{signal} ({x_detector}) [{x_units}]", f"{signal} ({y_detector}) [{y_units}]", fontsize=12)
588
- plt.tight_layout()
589
- plt.show()
613
+
614
+ return grid
590
615
 
591
616
  def trigger(self, show: bool = True) -> None:
592
617
  """Plot detected peaks on signal segments."""
@@ -646,6 +671,47 @@ class Acquisition:
646
671
  if show:
647
672
  plt.show()
648
673
 
674
+ @helper.plot_sns
675
+ def classifier(self, feature: str, x_detector: str, y_detector: str) -> None:
676
+ """
677
+ Visualize the classification of peaks using a scatter plot.
678
+
679
+ Parameters
680
+ ----------
681
+ feature : str
682
+ The feature to classify (e.g., 'Height', 'Width', 'Area').
683
+ x_detector : str
684
+ The detector to use for the x-axis.
685
+ y_detector : str
686
+ The detector to use for the y-axis.
687
+
688
+ Raises
689
+ ------
690
+ ValueError
691
+ If the 'Label' column is missing in the data, suggesting that
692
+ the `classify_dataset` method must be called first.
693
+ """
694
+ # Check if 'Label' exists in the dataset
695
+ if 'Label' not in self.acquisition.data.peaks.columns:
696
+ raise ValueError(
697
+ "The 'Label' column is missing. Ensure the dataset has been classified "
698
+ "by calling the `classify_dataset` method before using `classifier`."
699
+ )
700
+
701
+ # Set the plotting style
702
+ with plt.style.context(mps):
703
+ # Generate a scatter plot using seaborn's jointplot
704
+ grid = sns.jointplot(
705
+ data=self.acquisition.data.peaks,
706
+ x=(feature, x_detector),
707
+ y=(feature, y_detector),
708
+ hue='Label',
709
+ )
710
+
711
+ grid.figure.suptitle('Event classification')
712
+
713
+ return grid
714
+
649
715
  def get_detector(self, name: str):
650
716
  for detector in self.acquisition.cytometer.detectors:
651
717
  if detector.name == name:
@@ -7,7 +7,7 @@ import matplotlib.pyplot as plt
7
7
  from MPSPlots.styles import mps
8
8
 
9
9
  class BaseClassifier:
10
- def filter_dataframe(self, features: list, detectors: list = None) -> object:
10
+ def filter_dataframe(self, dataframe: pd.DataFrame, features: list, detectors: list = None) -> object:
11
11
  """
12
12
  Filter the DataFrame based on the selected features and detectors.
13
13
 
@@ -31,13 +31,13 @@ class BaseClassifier:
31
31
  # Determine detectors to use
32
32
 
33
33
  if detectors is None:
34
- detectors = self.dataframe.columns.get_level_values(0).unique().tolist()
34
+ detectors = dataframe.columns.get_level_values(0).unique().tolist()
35
35
 
36
- return self.dataframe.loc[detectors, features]
36
+ return dataframe.loc[:, (features, detectors)]
37
37
 
38
38
 
39
39
  class KmeansClassifier(BaseClassifier):
40
- def __init__(self, dataframe: object) -> None:
40
+ def __init__(self, number_of_cluster: int) -> None:
41
41
  """
42
42
  Initialize the Classifier.
43
43
 
@@ -46,42 +46,44 @@ class KmeansClassifier(BaseClassifier):
46
46
  dataframe : DataFrame
47
47
  The input dataframe with multi-index columns.
48
48
  """
49
- self.dataframe = dataframe
50
- self.dataframe['Label'] = 0 # Initialize labels as 0
49
+ self.number_of_cluster = number_of_cluster
50
+ # self.dataframe = dataframe
51
+ # self.dataframe['Label'] = 0 # Initialize labels as 0
51
52
 
52
- def run(self, number_of_cluster: int, features: list = ['Height'], detectors: list = None, random_state: int = 42) -> None:
53
+ def run(self, dataframe: pd.DataFrame, features: list = ['Height'], detectors: list = None, random_state: int = 42) -> pd.DataFrame:
53
54
  """
54
55
  Run KMeans clustering on the selected features and detectors.
55
56
 
56
57
  Parameters
57
58
  ----------
58
- number_of_cluster : int
59
- Number of clusters for KMeans.
59
+ dataframe : pd.DataFrame
60
+ The input DataFrame with multi-index (e.g., by 'Detector').
60
61
  features : list
61
- List of features to use for clustering. Options include 'Heights', 'Widths', 'Areas'.
62
+ List of features to use for clustering. Options include 'Height', 'Width', 'Area'.
62
63
  detectors : list, optional
63
64
  List of detectors to use. If None, use all detectors.
64
65
  random_state : int, optional
65
66
  Random state for KMeans, by default 42.
67
+
68
+ Returns
69
+ -------
70
+ pd.DataFrame
71
+ DataFrame with clustering labels added.
66
72
  """
67
73
  # Filter the DataFrame
68
- sub_dataframe = self.filter_dataframe(features=features, detectors=detectors)
69
- sub_dataframe = sub_dataframe.unstack('Detector')
74
+ sub_dataframe = self.filter_dataframe(dataframe=dataframe, features=features, detectors=detectors)
70
75
 
71
76
  # Ensure data is dequantified if it uses Pint quantities
72
77
  if hasattr(sub_dataframe, 'pint'):
73
78
  sub_dataframe = sub_dataframe.pint.dequantify().droplevel('unit', axis=1)
74
79
 
75
- sub_dataframe = sub_dataframe.droplevel(0, axis=1)
76
-
77
80
  # Run KMeans
78
- kmeans = KMeans(n_clusters=number_of_cluster, random_state=random_state)
81
+ kmeans = KMeans(n_clusters=self.number_of_cluster, random_state=random_state)
82
+ labels = kmeans.fit_predict(sub_dataframe)
79
83
 
80
- sub_dataframe['Label'] = kmeans.fit_predict(sub_dataframe)
84
+ dataframe['Label'] = labels
81
85
 
82
- self.sub_dataframe = sub_dataframe
83
-
84
- return sub_dataframe
86
+ return labels
85
87
 
86
88
  def plot(self, x_detector: str, y_detector: str) -> None:
87
89
  with plt.style.context(mps):
@@ -64,7 +64,7 @@ class RosinRammler(Base):
64
64
  # Apply inverse CDF of Rosin-Rammler distribution
65
65
  return d * (-np.log(1 - u))**(1 / self.spread)
66
66
 
67
- def _generate_default_x(self, x_min: float, x_max: float, n_samples: int = 20) -> np.ndarray:
67
+ def _generate_default_x(self, x_min: float, x_max: float, n_samples: int = 100) -> np.ndarray:
68
68
  """
69
69
  Generates a default range for x-values based on the characteristic size
70
70
  and spread of the Rosin-Rammler distribution.
@@ -93,7 +93,7 @@ class RosinRammler(Base):
93
93
  x_max = d * x_max # Scale x_max by characteristic size
94
94
  return np.linspace(x_min, x_max, n_samples) * self._units
95
95
 
96
- def get_pdf(self, x_min: float = 0.01, x_max: float = 2, n_samples: int = 20) -> Tuple[np.ndarray, np.ndarray]:
96
+ def get_pdf(self, x_min: float = 0.01, x_max: float = 4, n_samples: int = 100) -> Tuple[np.ndarray, np.ndarray]:
97
97
  r"""
98
98
  Returns the x-values and the scaled PDF values for the particle size distribution.
99
99
 
@@ -119,12 +119,13 @@ class RosinRammler(Base):
119
119
  # Generate x-values based on user-defined or default parameters
120
120
  x = self._generate_default_x(x_min=x_min, x_max=x_max, n_samples=n_samples)
121
121
 
122
- common_units = x.units
123
- d = self.characteristic_size.to(common_units).magnitude
122
+ x = x.to(self._units)
123
+ _x = x.magnitude
124
+ d = self.characteristic_size.to(self._units).magnitude
124
125
  k = self.spread
125
126
 
126
127
  # Rosin-Rammler PDF formula
127
- pdf = (k / d) * (x.magnitude / d)**(k - 1) * np.exp(-(x.magnitude / d)**k)
128
+ pdf = (k / d) * (_x / d)**(k - 1) * np.exp(-(_x / d)**k)
128
129
 
129
130
  return x, pdf
130
131
 
@@ -174,7 +174,6 @@ class FlowCell(object):
174
174
 
175
175
  # Step 2: Calculate the expected number of particles over the entire experiment
176
176
  expected_particles = particle_flux * run_time
177
- # expected_particles = population.n_events
178
177
 
179
178
  # Step 3: Generate inter-arrival times (exponentially distributed)
180
179
  inter_arrival_times = numpy.random.exponential(
@@ -0,0 +1,166 @@
1
+ from typing import Callable
2
+ import matplotlib.pyplot as plt
3
+ from MPSPlots.styles import mps
4
+
5
+
6
+
7
+ def plot_sns(function: Callable) -> Callable:
8
+ """
9
+ A decorator that helps in plotting by wrapping a plotting function with additional functionality
10
+ such as handling axes creation, setting the figure style, managing legends, and saving figures.
11
+
12
+ Parameters
13
+ ----------
14
+ function : Callable
15
+ The plotting function that is decorated. It should accept `self`, `ax`, and `mode_of_interest`
16
+ as parameters.
17
+
18
+ Returns
19
+ -------
20
+ Callable
21
+ A wrapper function that adds the specified plotting functionalities.
22
+
23
+ Notes
24
+ -----
25
+ This decorator expects the decorated function to have the following signature:
26
+ `function(self, ax=None, mode_of_interest='all', **kwargs)`.
27
+ """
28
+ def wrapper(self, show: bool = True, equal_limits: bool = False, save_filename: str = None, log_scale: bool = False, **kwargs) -> plt.Figure:
29
+ """
30
+ A wrapped version of the plotting function that provides additional functionality for creating
31
+ and managing plots.
32
+
33
+ Parameters
34
+ ----------
35
+ self : object
36
+ The instance of the class calling this method.
37
+ ax : plt.Axes, optional
38
+ A matplotlib Axes object to draw the plot on. If None, a new figure and axes are created.
39
+ Default is None.
40
+ show : bool, optional
41
+ Whether to display the plot. If False, the plot will not be shown but can still be saved
42
+ or returned. Default is True.
43
+ log_scale : bool, optional
44
+ If `True`, applies a logarithmic scale to both axes of the joint plot and their marginal distributions. Default is `False`.
45
+ save_filename : str, optional
46
+ A file path to save the figure. If None, the figure will not be saved. Default is None.
47
+ equal_limits : bool, optional
48
+ Ensures equal axis limits if True (default: False).
49
+ **kwargs : dict
50
+ Additional keyword arguments passed to the decorated function.
51
+
52
+ Returns
53
+ -------
54
+ plt.Figure
55
+ The matplotlib Figure object created or used for the plot.
56
+
57
+ Notes
58
+ -----
59
+ - If no `ax` is provided, a new figure and axes are created using the style context `mps`.
60
+ - The legend is only added if there are labels to display.
61
+ - If `save_filename` is specified, the figure is saved to the given path.
62
+ - The plot is shown if `show` is set to True.
63
+ """
64
+ grid = function(self, **kwargs)
65
+
66
+ grid.figure.tight_layout()
67
+
68
+ if equal_limits:
69
+ min_limit = min(x.min(), y.min())
70
+ max_limit = max(x.max(), y.max())
71
+ grid.ax_joint.set_xlim(min_limit, max_limit)
72
+ grid.ax_joint.set_ylim(min_limit, max_limit)
73
+
74
+ if log_scale:
75
+ grid.ax_joint.set_xscale('log')
76
+ grid.ax_joint.set_yscale('log')
77
+ grid.ax_marg_x.set_xscale('log')
78
+ grid.ax_marg_y.set_yscale('log')
79
+
80
+ if save_filename:
81
+ grid.figure.savefig(save_filename)
82
+
83
+ if show:
84
+ plt.show()
85
+
86
+ return grid
87
+
88
+ return wrapper
89
+
90
+
91
+ def plot_helper(function: Callable) -> Callable:
92
+ """
93
+ A decorator that helps in plotting by wrapping a plotting function with additional functionality
94
+ such as handling axes creation, setting the figure style, managing legends, and saving figures.
95
+
96
+ Parameters
97
+ ----------
98
+ function : Callable
99
+ The plotting function that is decorated. It should accept `self`, `ax`, and `mode_of_interest`
100
+ as parameters.
101
+
102
+ Returns
103
+ -------
104
+ Callable
105
+ A wrapper function that adds the specified plotting functionalities.
106
+
107
+ Notes
108
+ -----
109
+ This decorator expects the decorated function to have the following signature:
110
+ `function(self, ax=None, mode_of_interest='all', **kwargs)`.
111
+ """
112
+ def wrapper(self, ax: plt.Axes = None, show: bool = True, save_filename: str = None, figure_size: tuple = None, **kwargs) -> plt.Figure:
113
+ """
114
+ A wrapped version of the plotting function that provides additional functionality for creating
115
+ and managing plots.
116
+
117
+ Parameters
118
+ ----------
119
+ self : object
120
+ The instance of the class calling this method.
121
+ ax : plt.Axes, optional
122
+ A matplotlib Axes object to draw the plot on. If None, a new figure and axes are created.
123
+ Default is None.
124
+ show : bool, optional
125
+ Whether to display the plot. If False, the plot will not be shown but can still be saved
126
+ or returned. Default is True.
127
+ mode_of_interest : str, optional
128
+ Specifies the mode of interest for the plot. If 'all', all available modes will be plotted.
129
+ This parameter is interpreted using the `interpret_mode_of_interest` function. Default is 'all'.
130
+ save_filename : str, optional
131
+ A file path to save the figure. If None, the figure will not be saved. Default is None.
132
+ **kwargs : dict
133
+ Additional keyword arguments passed to the decorated function.
134
+
135
+ Returns
136
+ -------
137
+ plt.Figure
138
+ The matplotlib Figure object created or used for the plot.
139
+
140
+ Notes
141
+ -----
142
+ - If no `ax` is provided, a new figure and axes are created using the style context `mps`.
143
+ - The legend is only added if there are labels to display.
144
+ - If `save_filename` is specified, the figure is saved to the given path.
145
+ - The plot is shown if `show` is set to True.
146
+ """
147
+ if ax is None:
148
+ with plt.style.context(mps):
149
+ figure, ax = plt.subplots(1, 1, figsize=figure_size)
150
+
151
+ else:
152
+ figure = ax.get_figure()
153
+
154
+ output = function(self, ax=ax, **kwargs)
155
+
156
+ _, labels = ax.get_legend_handles_labels()
157
+
158
+ if save_filename:
159
+ figure.savefig(save_filename)
160
+
161
+ if show:
162
+ plt.show()
163
+
164
+ return output
165
+
166
+ return wrapper
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: FlowCyPy
3
- Version: 0.7.0
3
+ Version: 0.7.1
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.0
3
+ Version: 0.7.1
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