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.
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/_version.py +2 -2
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/acquisition.py +109 -43
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/classifier.py +21 -19
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/distribution/particle_size_distribution.py +6 -5
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/flow_cell.py +0 -1
- flowcypy-0.7.1/FlowCyPy/helper.py +166 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy.egg-info/PKG-INFO +1 -1
- {flowcypy-0.7.0 → flowcypy-0.7.1}/PKG-INFO +1 -1
- flowcypy-0.7.1/developments/scripts/temp.py +219 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/examples/tutorials/workflow.py +10 -11
- flowcypy-0.7.0/FlowCyPy/helper.py +0 -81
- flowcypy-0.7.0/developments/scripts/temp.py +0 -127
- {flowcypy-0.7.0 → flowcypy-0.7.1}/.flake8 +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/.github/dependabot.yml +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/.github/workflows/deploy_PyPi.yml +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/.github/workflows/deploy_anaconda.yml +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/.github/workflows/deploy_coverage.yml +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/.github/workflows/deploy_documentation.yml +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/.gitignore +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/__init__.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/coupling_mechanism/__init__.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/coupling_mechanism/empirical.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/coupling_mechanism/mie.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/coupling_mechanism/rayleigh.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/coupling_mechanism/uniform.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/coupling_mechanism.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/cytometer.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/detector.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/directories.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/distribution/__init__.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/distribution/base_class.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/distribution/delta.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/distribution/lognormal.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/distribution/normal.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/distribution/uniform.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/distribution/weibull.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/logger.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/noises.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/particle_count.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/peak_locator/__init__.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/peak_locator/base_class.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/peak_locator/basic.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/peak_locator/derivative.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/peak_locator/moving_average.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/physical_constant.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/plottings.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/population.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/populations_instances.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/scatterer_collection.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/signal_digitizer.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/source.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/units.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy/utils.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy.egg-info/SOURCES.txt +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy.egg-info/dependency_links.txt +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy.egg-info/requires.txt +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/FlowCyPy.egg-info/top_level.txt +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/LICENSE +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/README.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/doc/canto_spec.md +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/doc/internship.pdf +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/get_started.md +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/image.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/output_file.prof +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/concentration_comparison.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/create_images.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/data_analysis.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/dev_beads_analysis.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/dev_canto.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/dev_classifier.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/dev_shot_noise_check.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/dev_stats_0.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/dev_stats_1.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/dev_stats_2.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/dev_study_on_ri.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/dev_study_on_size.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/mat2csv.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/scripts/profiler.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/developments/test.pdf +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/Makefile +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/examples/extras/README.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/examples/extras/distributions.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/examples/extras/flow_cytometer_signal.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/examples/extras/scatterer_distribution.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/examples/extras/signal_acquisition.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/examples/noise_sources/README.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/examples/noise_sources/dark_current.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/examples/noise_sources/shot_noise.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/examples/noise_sources/thermal.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/examples/tutorials/README.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/examples/tutorials/limit_of_detection.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/images/distributions/Delta.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/images/distributions/LogNormal.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/images/distributions/Normal.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/images/distributions/RosinRammler.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/images/distributions/Uniform.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/images/distributions/Weibull.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/images/example_0.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/images/example_1.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/images/example_2.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/images/example_3.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/images/flow_cytometer.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/images/logo.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/make.bat +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/_static/default.css +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/_static/logo.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/_static/thumbnail.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/code/base.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/code/detector.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/code/distributions.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/code/flow_cell.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/code/flow_cytometer.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/code/peak_locator.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/code/scatterer.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/code/source.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/code.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/conf.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/examples.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/index.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/internal/core_components.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/internal/getting_started.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/internal/objectives/main.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/internal/objectives/pre.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/internal/objectives/stretch.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/internal/prerequisites/index.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/internal/prerequisites/mathematics.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/internal/prerequisites/optics.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/internal/prerequisites/programming.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/internal/ressources.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/internal/tasks.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/internal.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/references.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/sg_execution_times.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/docs/source/theory.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/meta.yaml +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/notebook.ipynb +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/pyproject.toml +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/setup.cfg +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/tests/__init__.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/tests/test_coupling_mechanism.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/tests/test_detector_noise.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/tests/test_distribution.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/tests/test_extra.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/tests/test_flow_cytometer.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/tests/test_noises.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/tests/test_peak_algorithm.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/tests/test_peak_analyzer.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/tests/test_population.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/tests/test_scatterer_distribution.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.1}/tests/test_source.py +0 -0
|
@@ -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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
476
|
-
|
|
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
|
-
|
|
482
|
-
joint_plot.ax_joint.set_ylabel(f"Signal {y_detector} [{y_units}]")
|
|
520
|
+
grid.figure.suptitle("Theoretical coupling distribution")
|
|
483
521
|
|
|
484
|
-
|
|
522
|
+
return grid
|
|
485
523
|
|
|
486
|
-
|
|
487
|
-
|
|
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.
|
|
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
|
-
|
|
575
|
+
grid.ax_joint.set_xlabel(f"Size [{x_unit}]")
|
|
552
576
|
|
|
553
|
-
|
|
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
|
-
|
|
589
|
-
|
|
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 =
|
|
34
|
+
detectors = dataframe.columns.get_level_values(0).unique().tolist()
|
|
35
35
|
|
|
36
|
-
return
|
|
36
|
+
return dataframe.loc[:, (features, detectors)]
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
class KmeansClassifier(BaseClassifier):
|
|
40
|
-
def __init__(self,
|
|
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.
|
|
50
|
-
self.dataframe
|
|
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,
|
|
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
|
-
|
|
59
|
-
|
|
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 '
|
|
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
|
-
|
|
84
|
+
dataframe['Label'] = labels
|
|
81
85
|
|
|
82
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
123
|
-
|
|
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) * (
|
|
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
|