FlowCyPy 0.7.0__tar.gz → 0.7.3__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.3}/FlowCyPy/_version.py +2 -2
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/acquisition.py +178 -64
- flowcypy-0.7.3/FlowCyPy/classifier.py +182 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/cytometer.py +63 -24
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/detector.py +7 -64
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/distribution/particle_size_distribution.py +6 -5
- flowcypy-0.7.3/FlowCyPy/flow_cell.py +137 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/helper.py +22 -14
- flowcypy-0.7.3/FlowCyPy/noises.py +87 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/particle_count.py +3 -2
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/population.py +3 -4
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/scatterer_collection.py +7 -7
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/signal_digitizer.py +1 -3
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/source.py +4 -7
- flowcypy-0.7.3/FlowCyPy/utils.py +74 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy.egg-info/PKG-INFO +2 -2
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy.egg-info/SOURCES.txt +16 -4
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy.egg-info/requires.txt +1 -1
- {flowcypy-0.7.0 → flowcypy-0.7.3}/PKG-INFO +2 -2
- flowcypy-0.7.3/developments/Deep_peak_square.ipynb +1049 -0
- flowcypy-0.7.3/developments/Physics-informed_AI.ipynb +876 -0
- flowcypy-0.7.3/developments/ROI_analysis-Copy1.ipynb +639 -0
- flowcypy-0.7.3/developments/ROI_analysis.ipynb +778 -0
- flowcypy-0.7.3/developments/Untitled.ipynb +227 -0
- flowcypy-0.7.3/developments/Untitled1.ipynb +668 -0
- flowcypy-0.7.3/developments/Untitled2.ipynb +313 -0
- flowcypy-0.7.3/developments/ai_dev2.ipynb +1745 -0
- flowcypy-0.7.3/developments/best_model.h5 +0 -0
- flowcypy-0.7.3/developments/best_model.keras +0 -0
- flowcypy-0.7.0/developments/scripts/temp.py → flowcypy-0.7.3/developments/concentration_validation.py +34 -61
- flowcypy-0.7.3/developments/grad_cam_output.png +0 -0
- flowcypy-0.7.3/developments/model.png +0 -0
- flowcypy-0.7.3/developments/model_example.png +0 -0
- flowcypy-0.7.3/developments/scripts/AI_peak_detection.py +85 -0
- flowcypy-0.7.3/developments/scripts/temp.py +207 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/examples/extras/flow_cytometer_signal.py +2 -2
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/examples/extras/scatterer_distribution.py +3 -2
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/examples/extras/signal_acquisition.py +1 -1
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/examples/tutorials/limit_of_detection.py +1 -1
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/examples/tutorials/workflow.py +12 -15
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/sg_execution_times.rst +15 -15
- {flowcypy-0.7.0 → flowcypy-0.7.3}/pyproject.toml +1 -1
- flowcypy-0.7.3/tests/test_classifiers.py +83 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/tests/test_coupling_mechanism.py +1 -1
- {flowcypy-0.7.0 → flowcypy-0.7.3}/tests/test_flow_cytometer.py +26 -8
- {flowcypy-0.7.0 → flowcypy-0.7.3}/tests/test_peak_analyzer.py +1 -1
- {flowcypy-0.7.0 → flowcypy-0.7.3}/tests/test_population.py +2 -2
- {flowcypy-0.7.0 → flowcypy-0.7.3}/tests/test_scatterer_distribution.py +1 -1
- flowcypy-0.7.0/FlowCyPy/classifier.py +0 -208
- flowcypy-0.7.0/FlowCyPy/coupling_mechanism.py +0 -205
- flowcypy-0.7.0/FlowCyPy/flow_cell.py +0 -198
- flowcypy-0.7.0/FlowCyPy/logger.py +0 -136
- flowcypy-0.7.0/FlowCyPy/noises.py +0 -34
- flowcypy-0.7.0/FlowCyPy/plottings.py +0 -269
- flowcypy-0.7.0/FlowCyPy/utils.py +0 -191
- flowcypy-0.7.0/tests/test_extra.py +0 -55
- {flowcypy-0.7.0 → flowcypy-0.7.3}/.flake8 +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/.github/dependabot.yml +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/.github/workflows/deploy_PyPi.yml +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/.github/workflows/deploy_anaconda.yml +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/.github/workflows/deploy_coverage.yml +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/.github/workflows/deploy_documentation.yml +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/.gitignore +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/__init__.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/coupling_mechanism/__init__.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/coupling_mechanism/empirical.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/coupling_mechanism/mie.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/coupling_mechanism/rayleigh.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/coupling_mechanism/uniform.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/directories.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/distribution/__init__.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/distribution/base_class.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/distribution/delta.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/distribution/lognormal.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/distribution/normal.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/distribution/uniform.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/distribution/weibull.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/peak_locator/__init__.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/peak_locator/base_class.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/peak_locator/basic.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/peak_locator/derivative.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/peak_locator/moving_average.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/physical_constant.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/populations_instances.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy/units.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy.egg-info/dependency_links.txt +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/FlowCyPy.egg-info/top_level.txt +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/LICENSE +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/README.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/developments/doc/canto_spec.md +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/developments/doc/internship.pdf +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/developments/get_started.md +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/developments/image.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/developments/output_file.prof +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/developments/scripts/concentration_comparison.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/developments/scripts/create_images.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/developments/scripts/data_analysis.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/developments/scripts/dev_beads_analysis.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/developments/scripts/dev_canto.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/developments/scripts/dev_classifier.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/developments/scripts/dev_shot_noise_check.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/developments/scripts/dev_stats_0.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/developments/scripts/dev_stats_1.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/developments/scripts/dev_stats_2.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/developments/scripts/dev_study_on_ri.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/developments/scripts/dev_study_on_size.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/developments/scripts/mat2csv.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/developments/scripts/profiler.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/developments/test.pdf +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/Makefile +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/examples/extras/README.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/examples/extras/distributions.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/examples/noise_sources/README.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/examples/noise_sources/dark_current.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/examples/noise_sources/shot_noise.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/examples/noise_sources/thermal.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/examples/tutorials/README.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/images/distributions/Delta.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/images/distributions/LogNormal.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/images/distributions/Normal.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/images/distributions/RosinRammler.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/images/distributions/Uniform.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/images/distributions/Weibull.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/images/example_0.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/images/example_1.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/images/example_2.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/images/example_3.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/images/flow_cytometer.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/images/logo.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/make.bat +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/_static/default.css +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/_static/logo.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/_static/thumbnail.png +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/code/base.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/code/detector.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/code/distributions.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/code/flow_cell.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/code/flow_cytometer.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/code/peak_locator.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/code/scatterer.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/code/source.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/code.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/conf.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/examples.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/index.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/internal/core_components.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/internal/getting_started.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/internal/objectives/main.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/internal/objectives/pre.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/internal/objectives/stretch.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/internal/prerequisites/index.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/internal/prerequisites/mathematics.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/internal/prerequisites/optics.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/internal/prerequisites/programming.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/internal/ressources.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/internal/tasks.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/internal.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/references.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/docs/source/theory.rst +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/meta.yaml +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/notebook.ipynb +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/setup.cfg +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/tests/__init__.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/tests/test_detector_noise.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/tests/test_distribution.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/tests/test_noises.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/tests/test_peak_algorithm.py +0 -0
- {flowcypy-0.7.0 → flowcypy-0.7.3}/tests/test_source.py +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import warnings
|
|
2
3
|
from typing import Optional, Union, List
|
|
3
4
|
from MPSPlots.styles import mps
|
|
4
5
|
import pandas as pd
|
|
@@ -9,7 +10,9 @@ from scipy.signal import find_peaks
|
|
|
9
10
|
import matplotlib.pyplot as plt
|
|
10
11
|
import seaborn as sns
|
|
11
12
|
from tabulate import tabulate
|
|
12
|
-
|
|
13
|
+
|
|
14
|
+
from FlowCyPy import helper
|
|
15
|
+
from FlowCyPy.classifier import BaseClassifier
|
|
13
16
|
|
|
14
17
|
class DataAccessor:
|
|
15
18
|
def __init__(self, outer):
|
|
@@ -93,13 +96,13 @@ class Acquisition:
|
|
|
93
96
|
results = results.reset_index(drop=True)
|
|
94
97
|
|
|
95
98
|
# Check for multiple peaks and issue a warning
|
|
96
|
-
peak_counts = results.groupby(['Detector', 'SegmentID']).size()
|
|
97
|
-
multiple_peak_segments = peak_counts[peak_counts > 1]
|
|
98
|
-
if not multiple_peak_segments.empty:
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
# peak_counts = results.groupby(['Detector', 'SegmentID']).size()
|
|
100
|
+
# multiple_peak_segments = peak_counts[peak_counts > 1]
|
|
101
|
+
# if not multiple_peak_segments.empty:
|
|
102
|
+
# warnings.warn(
|
|
103
|
+
# f"Multiple peaks detected in the following segments: {multiple_peak_segments.index.tolist()}",
|
|
104
|
+
# UserWarning
|
|
105
|
+
# )
|
|
103
106
|
|
|
104
107
|
_temp = results.reset_index()[['Detector', 'SegmentID', 'Height']].pint.dequantify().droplevel('unit', axis=1)
|
|
105
108
|
|
|
@@ -110,13 +113,36 @@ class Acquisition:
|
|
|
110
113
|
)
|
|
111
114
|
|
|
112
115
|
def _get_trigger_indices(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
self,
|
|
117
|
+
threshold: units.Quantity,
|
|
118
|
+
trigger_detector_name: str = None,
|
|
119
|
+
pre_buffer: int = 64,
|
|
120
|
+
post_buffer: int = 64
|
|
121
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
118
122
|
"""
|
|
119
|
-
Calculate start and end indices for triggered segments
|
|
123
|
+
Calculate start and end indices for triggered segments, ensuring no retriggering
|
|
124
|
+
occurs during an active buffer period.
|
|
125
|
+
|
|
126
|
+
Parameters
|
|
127
|
+
----------
|
|
128
|
+
threshold : units.Quantity
|
|
129
|
+
The threshold value for triggering.
|
|
130
|
+
trigger_detector_name : str, optional
|
|
131
|
+
The name of the detector to use for the triggering signal.
|
|
132
|
+
pre_buffer : int, optional
|
|
133
|
+
Number of samples to include before the trigger point.
|
|
134
|
+
post_buffer : int, optional
|
|
135
|
+
Number of samples to include after the trigger point.
|
|
136
|
+
|
|
137
|
+
Returns
|
|
138
|
+
-------
|
|
139
|
+
tuple[np.ndarray, np.ndarray]
|
|
140
|
+
The start and end indices of non-overlapping triggered segments.
|
|
141
|
+
|
|
142
|
+
Raises
|
|
143
|
+
------
|
|
144
|
+
ValueError
|
|
145
|
+
If the specified detector is not found in the data.
|
|
120
146
|
"""
|
|
121
147
|
if trigger_detector_name not in self.data.continuous.index.get_level_values('Detector').unique():
|
|
122
148
|
raise ValueError(f"Detector '{trigger_detector_name}' not found.")
|
|
@@ -128,7 +154,18 @@ class Acquisition:
|
|
|
128
154
|
start_indices = np.clip(crossings - pre_buffer, 0, len(trigger_signal) - 1)
|
|
129
155
|
end_indices = np.clip(crossings + post_buffer, 0, len(trigger_signal) - 1)
|
|
130
156
|
|
|
131
|
-
|
|
157
|
+
# Suppress retriggering within an active buffer period
|
|
158
|
+
suppressed_start_indices = []
|
|
159
|
+
suppressed_end_indices = []
|
|
160
|
+
|
|
161
|
+
last_end = -1
|
|
162
|
+
for start, end in zip(start_indices, end_indices):
|
|
163
|
+
if start > last_end: # Ensure no overlap with the last active buffer
|
|
164
|
+
suppressed_start_indices.append(start)
|
|
165
|
+
suppressed_end_indices.append(end)
|
|
166
|
+
last_end = end # Update the end of the current active buffer
|
|
167
|
+
|
|
168
|
+
return np.array(suppressed_start_indices), np.array(suppressed_end_indices)
|
|
132
169
|
|
|
133
170
|
def run_triggering(self,
|
|
134
171
|
threshold: units.Quantity,
|
|
@@ -137,22 +174,47 @@ class Acquisition:
|
|
|
137
174
|
post_buffer: int = 64,
|
|
138
175
|
max_triggers: int = None) -> None:
|
|
139
176
|
"""
|
|
140
|
-
|
|
177
|
+
Execute triggered acquisition analysis for signal data.
|
|
178
|
+
|
|
179
|
+
This method identifies segments of signal data based on a triggering threshold
|
|
180
|
+
and specified detector. It extracts segments of interest from the signal,
|
|
181
|
+
including a pre-trigger buffer and post-trigger buffer, and stores the results
|
|
182
|
+
in `self.data.triggered`.
|
|
141
183
|
|
|
142
184
|
Parameters
|
|
143
185
|
----------
|
|
144
186
|
threshold : units.Quantity
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
187
|
+
The threshold value for triggering. Only signal values exceeding this threshold
|
|
188
|
+
will be considered as trigger events.
|
|
189
|
+
trigger_detector_name : str
|
|
190
|
+
The name of the detector used for triggering. This determines which detector's
|
|
191
|
+
signal is analyzed for trigger events.
|
|
150
192
|
pre_buffer : int, optional
|
|
151
|
-
|
|
193
|
+
The number of points to include before the trigger point in each segment.
|
|
194
|
+
Default is 64.
|
|
152
195
|
post_buffer : int, optional
|
|
153
|
-
|
|
196
|
+
The number of points to include after the trigger point in each segment.
|
|
197
|
+
Default is 64.
|
|
154
198
|
max_triggers : int, optional
|
|
155
|
-
|
|
199
|
+
The maximum number of triggers to process. If None, all triggers will be processed.
|
|
200
|
+
Default is None.
|
|
201
|
+
|
|
202
|
+
Raises
|
|
203
|
+
------
|
|
204
|
+
ValueError
|
|
205
|
+
If the specified `trigger_detector_name` is not found in the dataset.
|
|
206
|
+
|
|
207
|
+
Warnings
|
|
208
|
+
--------
|
|
209
|
+
UserWarning
|
|
210
|
+
If no triggers are detected for the specified threshold, the method raises a warning
|
|
211
|
+
indicating that no signals met the criteria.
|
|
212
|
+
|
|
213
|
+
Notes
|
|
214
|
+
-----
|
|
215
|
+
- Triggered segments are stored in `self.data.triggered` as a pandas DataFrame with a hierarchical index on `['Detector', 'SegmentID']`.
|
|
216
|
+
- This method modifies `self.data.triggered` in place.
|
|
217
|
+
- The peak detection function `self.detect_peaks` is automatically called at the end of this method to analyze triggered segments.
|
|
156
218
|
"""
|
|
157
219
|
self.threshold = threshold
|
|
158
220
|
self.trigger_detector_name = trigger_detector_name
|
|
@@ -191,6 +253,37 @@ class Acquisition:
|
|
|
191
253
|
|
|
192
254
|
self.detect_peaks()
|
|
193
255
|
|
|
256
|
+
def classify_dataset(self, classifier: BaseClassifier, features: List[str], detectors: list[str]) -> None:
|
|
257
|
+
"""
|
|
258
|
+
Classify the dataset using the specified classifier and features.
|
|
259
|
+
|
|
260
|
+
This method applies a classification algorithm to the dataset by first unstacking
|
|
261
|
+
the "Detector" level of the DataFrame's index. It then uses the provided classifier
|
|
262
|
+
object to classify the dataset based on the specified features and detectors.
|
|
263
|
+
|
|
264
|
+
Parameters
|
|
265
|
+
----------
|
|
266
|
+
classifier : BaseClassifier
|
|
267
|
+
An object implementing a `run` method for classification.
|
|
268
|
+
features : List[str]
|
|
269
|
+
A list of column names corresponding to the features to be used for classification (e.g., 'Height', 'Width', 'Area').
|
|
270
|
+
detectors : list[str]
|
|
271
|
+
A list of detector names to filter the data before classification. Only data from these detectors will be included in the classification process.
|
|
272
|
+
|
|
273
|
+
Returns
|
|
274
|
+
-------
|
|
275
|
+
None
|
|
276
|
+
This method updates the `self.data.peaks` attribute in place with the classified data.
|
|
277
|
+
"""
|
|
278
|
+
self.data.peaks = self.data.peaks.unstack('Detector')
|
|
279
|
+
self.classifier = classifier
|
|
280
|
+
|
|
281
|
+
self.classifier.run(
|
|
282
|
+
dataframe=self.data.peaks,
|
|
283
|
+
features=features,
|
|
284
|
+
detectors=detectors
|
|
285
|
+
)
|
|
286
|
+
|
|
194
287
|
class LoggerInterface:
|
|
195
288
|
"""
|
|
196
289
|
A nested class for logging statistical information about the experiment.
|
|
@@ -419,7 +512,7 @@ class Acquisition:
|
|
|
419
512
|
zorder=0,
|
|
420
513
|
)
|
|
421
514
|
|
|
422
|
-
ax2.set_ylim(detector._saturation_levels)
|
|
515
|
+
ax2.set_ylim(detector._saturation_levels if detector._saturation_levels[0] != detector._saturation_levels[1] else None)
|
|
423
516
|
|
|
424
517
|
self._add_event_to_ax(ax=axes[-1], time_units=time_units)
|
|
425
518
|
|
|
@@ -443,7 +536,8 @@ class Acquisition:
|
|
|
443
536
|
|
|
444
537
|
ax.legend()
|
|
445
538
|
|
|
446
|
-
|
|
539
|
+
@helper.plot_sns
|
|
540
|
+
def coupling_distribution(self, x_detector: str, y_detector: str, bandwidth_adjust: float = 1) -> None:
|
|
447
541
|
"""
|
|
448
542
|
Plots the density distribution of optical coupling between two detector channels.
|
|
449
543
|
|
|
@@ -466,31 +560,17 @@ class Acquisition:
|
|
|
466
560
|
y = df[y_detector].pint.to(y_units)
|
|
467
561
|
|
|
468
562
|
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")
|
|
474
|
-
|
|
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)
|
|
563
|
+
grid = sns.jointplot(data=df, x=x, y=y, hue="Population", alpha=0.8, marginal_kws=dict(bw_adjust=bandwidth_adjust))
|
|
480
564
|
|
|
481
|
-
|
|
482
|
-
|
|
565
|
+
grid.ax_joint.set_xlabel(f"Signal {x_detector} [{x_units}]")
|
|
566
|
+
grid.ax_joint.set_ylabel(f"Signal {y_detector} [{y_units}]")
|
|
483
567
|
|
|
484
|
-
|
|
568
|
+
grid.figure.suptitle("Theoretical coupling distribution")
|
|
485
569
|
|
|
486
|
-
|
|
487
|
-
joint_plot.figure.savefig(save_path, dpi=300, bbox_inches="tight")
|
|
488
|
-
logging.info(f"Plot saved to {save_path}")
|
|
570
|
+
return grid
|
|
489
571
|
|
|
490
|
-
|
|
491
|
-
|
|
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:
|
|
572
|
+
@helper.plot_sns
|
|
573
|
+
def scatterer(self, alpha: float = 0.8, bandwidth_adjust: float = 1, color_palette: Optional[Union[str, dict]] = None) -> None:
|
|
494
574
|
"""
|
|
495
575
|
Visualizes the joint distribution of scatterer sizes and refractive indices using a Seaborn jointplot.
|
|
496
576
|
|
|
@@ -504,8 +584,6 @@ class Acquisition:
|
|
|
504
584
|
Transparency level for the scatter plot points, ranging from 0 (fully transparent) to 1 (fully opaque). Default is 0.8.
|
|
505
585
|
bandwidth_adjust : float, optional
|
|
506
586
|
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
587
|
color_palette : str or dict, optional
|
|
510
588
|
The color palette to use for the hue in the scatterplot. Can be a seaborn palette name
|
|
511
589
|
(e.g., 'viridis', 'coolwarm') or a dictionary mapping hue levels to specific colors. Default is None.
|
|
@@ -540,19 +618,13 @@ class Acquisition:
|
|
|
540
618
|
marginal_kws=dict(bw_adjust=bandwidth_adjust)
|
|
541
619
|
)
|
|
542
620
|
|
|
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')
|
|
621
|
+
grid.figure.suptitle("Scatterer sampling distribution")
|
|
550
622
|
|
|
551
|
-
|
|
623
|
+
grid.ax_joint.set_xlabel(f"Size [{x_unit}]")
|
|
552
624
|
|
|
553
|
-
|
|
554
|
-
plt.show()
|
|
625
|
+
return grid
|
|
555
626
|
|
|
627
|
+
@helper.plot_sns
|
|
556
628
|
def peaks(self, x_detector: str, y_detector: str, signal: str = 'Height', bandwidth_adjust: float = 0.8) -> None:
|
|
557
629
|
"""
|
|
558
630
|
Plot the joint KDE distribution of the specified signal between two detectors using seaborn,
|
|
@@ -582,15 +654,17 @@ class Acquisition:
|
|
|
582
654
|
joint_kws={'bw_adjust': bandwidth_adjust, 'alpha': 0.7}
|
|
583
655
|
)
|
|
584
656
|
|
|
657
|
+
grid.figure.suptitle("Peaks properties")
|
|
585
658
|
grid.ax_joint.scatter(x_data, y_data, color='C1', alpha=0.6)
|
|
586
659
|
|
|
587
660
|
grid.set_axis_labels(f"{signal} ({x_detector}) [{x_units}]", f"{signal} ({y_detector}) [{y_units}]", fontsize=12)
|
|
588
|
-
|
|
589
|
-
|
|
661
|
+
|
|
662
|
+
return grid
|
|
590
663
|
|
|
591
664
|
def trigger(self, show: bool = True) -> None:
|
|
592
665
|
"""Plot detected peaks on signal segments."""
|
|
593
666
|
n_plots = self.acquisition.n_detectors + 1
|
|
667
|
+
|
|
594
668
|
with plt.style.context(mps):
|
|
595
669
|
_, axes = plt.subplots(
|
|
596
670
|
nrows=n_plots,
|
|
@@ -603,12 +677,12 @@ class Acquisition:
|
|
|
603
677
|
|
|
604
678
|
time_units = self.acquisition.data.triggered['Time'].max().to_compact().units
|
|
605
679
|
|
|
606
|
-
for ax, (detector_name, group) in zip(axes, self.acquisition.data.triggered.groupby(level=
|
|
680
|
+
for ax, (detector_name, group) in zip(axes, self.acquisition.data.triggered.groupby(level='Detector')):
|
|
607
681
|
detector = self.get_detector(detector_name)
|
|
608
682
|
|
|
609
683
|
ax.set_ylabel(detector_name)
|
|
610
684
|
|
|
611
|
-
for _, sub_group in group.groupby(level=
|
|
685
|
+
for _, sub_group in group.groupby(level='SegmentID'):
|
|
612
686
|
x = sub_group['Time'].pint.to(time_units)
|
|
613
687
|
digitized = sub_group['DigitizedSignal']
|
|
614
688
|
ax.step(x, digitized, where='mid', linewidth=2)
|
|
@@ -636,7 +710,7 @@ class Acquisition:
|
|
|
636
710
|
ax2.legend()
|
|
637
711
|
|
|
638
712
|
|
|
639
|
-
for ax, (detector_name, group) in zip(axes, self.acquisition.data.peaks.groupby(level=
|
|
713
|
+
for ax, (detector_name, group) in zip(axes, self.acquisition.data.peaks.groupby(level='Detector')):
|
|
640
714
|
x = group['Time'].pint.to(time_units)
|
|
641
715
|
y = group['Height']
|
|
642
716
|
ax.scatter(x, y, color='C1')
|
|
@@ -646,6 +720,46 @@ class Acquisition:
|
|
|
646
720
|
if show:
|
|
647
721
|
plt.show()
|
|
648
722
|
|
|
723
|
+
@helper.plot_sns
|
|
724
|
+
def classifier(self, feature: str, x_detector: str, y_detector: str) -> None:
|
|
725
|
+
"""
|
|
726
|
+
Visualize the classification of peaks using a scatter plot.
|
|
727
|
+
|
|
728
|
+
Parameters
|
|
729
|
+
----------
|
|
730
|
+
feature : str
|
|
731
|
+
The feature to classify (e.g., 'Height', 'Width', 'Area').
|
|
732
|
+
x_detector : str
|
|
733
|
+
The detector to use for the x-axis.
|
|
734
|
+
y_detector : str
|
|
735
|
+
The detector to use for the y-axis.
|
|
736
|
+
|
|
737
|
+
Raises
|
|
738
|
+
------
|
|
739
|
+
ValueError
|
|
740
|
+
If the 'Label' column is missing in the data, suggesting that
|
|
741
|
+
the `classify_dataset` method must be called first.
|
|
742
|
+
"""
|
|
743
|
+
# Check if 'Label' exists in the dataset
|
|
744
|
+
if 'Label' not in self.acquisition.data.peaks.columns:
|
|
745
|
+
raise ValueError(
|
|
746
|
+
"The 'Label' column is missing. Ensure the dataset has been classified "
|
|
747
|
+
"by calling the `classify_dataset` method before using `classifier`."
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
# Set the plotting style
|
|
751
|
+
with plt.style.context(mps):
|
|
752
|
+
grid = sns.jointplot(
|
|
753
|
+
data=self.acquisition.data.peaks,
|
|
754
|
+
x=(feature, x_detector),
|
|
755
|
+
y=(feature, y_detector),
|
|
756
|
+
hue='Label',
|
|
757
|
+
)
|
|
758
|
+
|
|
759
|
+
grid.figure.suptitle('Event classification')
|
|
760
|
+
|
|
761
|
+
return grid
|
|
762
|
+
|
|
649
763
|
def get_detector(self, name: str):
|
|
650
764
|
for detector in self.acquisition.cytometer.detectors:
|
|
651
765
|
if detector.name == name:
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
from sklearn.cluster import KMeans
|
|
2
|
+
from sklearn.cluster import DBSCAN
|
|
3
|
+
from sklearn.mixture import GaussianMixture
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from typing import Dict, Tuple
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BaseClassifier:
|
|
9
|
+
def filter_dataframe(self, dataframe: pd.DataFrame, features: list, detectors: list = None) -> object:
|
|
10
|
+
"""
|
|
11
|
+
Filter the DataFrame based on the selected features and detectors.
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
features : list
|
|
16
|
+
List of features to use for filtering. Options include 'Heights', 'Widths', 'Areas'.
|
|
17
|
+
detectors : list, optional
|
|
18
|
+
List of detectors to use. If None, use all detectors.
|
|
19
|
+
|
|
20
|
+
Returns
|
|
21
|
+
-------
|
|
22
|
+
DataFrame
|
|
23
|
+
A filtered DataFrame containing only the selected detectors and features.
|
|
24
|
+
|
|
25
|
+
Raises
|
|
26
|
+
------
|
|
27
|
+
ValueError
|
|
28
|
+
If no matching features are found for the given detectors and features.
|
|
29
|
+
"""
|
|
30
|
+
# Determine detectors to use
|
|
31
|
+
|
|
32
|
+
if detectors is None:
|
|
33
|
+
detectors = dataframe.columns.get_level_values(1).unique().tolist()
|
|
34
|
+
|
|
35
|
+
return dataframe.loc[:, (features, detectors)]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class KmeansClassifier(BaseClassifier):
|
|
39
|
+
def __init__(self, number_of_cluster: int) -> None:
|
|
40
|
+
"""
|
|
41
|
+
Initialize the Classifier.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
dataframe : DataFrame
|
|
46
|
+
The input dataframe with multi-index columns.
|
|
47
|
+
"""
|
|
48
|
+
self.number_of_cluster = number_of_cluster
|
|
49
|
+
|
|
50
|
+
def run(self, dataframe: pd.DataFrame, features: list = ['Height'], detectors: list = None, random_state: int = 42) -> pd.DataFrame:
|
|
51
|
+
"""
|
|
52
|
+
Run KMeans clustering on the selected features and detectors.
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
dataframe : pd.DataFrame
|
|
57
|
+
The input DataFrame with multi-index (e.g., by 'Detector').
|
|
58
|
+
features : list
|
|
59
|
+
List of features to use for clustering. Options include 'Height', 'Width', 'Area'.
|
|
60
|
+
detectors : list, optional
|
|
61
|
+
List of detectors to use. If None, use all detectors.
|
|
62
|
+
random_state : int, optional
|
|
63
|
+
Random state for KMeans, by default 42.
|
|
64
|
+
|
|
65
|
+
Returns
|
|
66
|
+
-------
|
|
67
|
+
pd.DataFrame
|
|
68
|
+
DataFrame with clustering labels added.
|
|
69
|
+
"""
|
|
70
|
+
# Filter the DataFrame
|
|
71
|
+
sub_dataframe = self.filter_dataframe(dataframe=dataframe, features=features, detectors=detectors)
|
|
72
|
+
|
|
73
|
+
# Ensure data is dequantified if it uses Pint quantities
|
|
74
|
+
if hasattr(sub_dataframe, 'pint'):
|
|
75
|
+
sub_dataframe = sub_dataframe.pint.dequantify().droplevel('unit', axis=1)
|
|
76
|
+
|
|
77
|
+
# Run KMeans
|
|
78
|
+
kmeans = KMeans(n_clusters=self.number_of_cluster, random_state=random_state)
|
|
79
|
+
labels = kmeans.fit_predict(sub_dataframe)
|
|
80
|
+
|
|
81
|
+
dataframe['Label'] = labels
|
|
82
|
+
|
|
83
|
+
return labels
|
|
84
|
+
|
|
85
|
+
class GaussianMixtureClassifier(BaseClassifier):
|
|
86
|
+
def __init__(self, number_of_components: int) -> None:
|
|
87
|
+
"""
|
|
88
|
+
Initialize the Gaussian Mixture Classifier.
|
|
89
|
+
|
|
90
|
+
Parameters
|
|
91
|
+
----------
|
|
92
|
+
number_of_components : int
|
|
93
|
+
Number of Gaussian components (clusters) to use for the model.
|
|
94
|
+
"""
|
|
95
|
+
self.number_of_components = number_of_components
|
|
96
|
+
|
|
97
|
+
def run(self, dataframe: pd.DataFrame, features: list = ['Height'], detectors: list = None, random_state: int = 42) -> pd.DataFrame:
|
|
98
|
+
"""
|
|
99
|
+
Run Gaussian Mixture Model (GMM) clustering on the selected features and detectors.
|
|
100
|
+
|
|
101
|
+
Parameters
|
|
102
|
+
----------
|
|
103
|
+
dataframe : pd.DataFrame
|
|
104
|
+
The input DataFrame with multi-index (e.g., by 'Detector').
|
|
105
|
+
features : list
|
|
106
|
+
List of features to use for clustering. Options include 'Height', 'Width', 'Area'.
|
|
107
|
+
detectors : list, optional
|
|
108
|
+
List of detectors to use. If None, use all detectors.
|
|
109
|
+
random_state : int, optional
|
|
110
|
+
Random state for reproducibility, by default 42.
|
|
111
|
+
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
pd.DataFrame
|
|
115
|
+
DataFrame with clustering labels added.
|
|
116
|
+
"""
|
|
117
|
+
# Filter the DataFrame
|
|
118
|
+
sub_dataframe = self.filter_dataframe(dataframe=dataframe, features=features, detectors=detectors)
|
|
119
|
+
|
|
120
|
+
# Ensure data is dequantified if it uses Pint quantities
|
|
121
|
+
if hasattr(sub_dataframe, 'pint'):
|
|
122
|
+
sub_dataframe = sub_dataframe.pint.dequantify().droplevel('unit', axis=1)
|
|
123
|
+
|
|
124
|
+
# Run Gaussian Mixture Model
|
|
125
|
+
gmm = GaussianMixture(n_components=self.number_of_components, random_state=random_state)
|
|
126
|
+
labels = gmm.fit_predict(sub_dataframe)
|
|
127
|
+
|
|
128
|
+
# Add labels to the original DataFrame
|
|
129
|
+
dataframe['Label'] = labels
|
|
130
|
+
|
|
131
|
+
return labels
|
|
132
|
+
|
|
133
|
+
class DBSCANClassifier(BaseClassifier):
|
|
134
|
+
def __init__(self, epsilon: float = 0.5, min_samples: int = 5) -> None:
|
|
135
|
+
"""
|
|
136
|
+
Initialize the DBSCAN Classifier.
|
|
137
|
+
|
|
138
|
+
Parameters
|
|
139
|
+
----------
|
|
140
|
+
epsilon : float, optional
|
|
141
|
+
The maximum distance between two samples for them to be considered as neighbors.
|
|
142
|
+
Default is 0.5.
|
|
143
|
+
min_samples : int, optional
|
|
144
|
+
The number of samples in a neighborhood for a point to be considered a core point.
|
|
145
|
+
Default is 5.
|
|
146
|
+
"""
|
|
147
|
+
self.epsilon = epsilon
|
|
148
|
+
self.min_samples = min_samples
|
|
149
|
+
|
|
150
|
+
def run(self, dataframe: pd.DataFrame, features: list = ['Height'], detectors: list = None) -> pd.DataFrame:
|
|
151
|
+
"""
|
|
152
|
+
Run DBSCAN clustering on the selected features and detectors.
|
|
153
|
+
|
|
154
|
+
Parameters
|
|
155
|
+
----------
|
|
156
|
+
dataframe : pd.DataFrame
|
|
157
|
+
The input DataFrame with multi-index (e.g., by 'Detector').
|
|
158
|
+
features : list
|
|
159
|
+
List of features to use for clustering. Options include 'Height', 'Width', 'Area'.
|
|
160
|
+
detectors : list, optional
|
|
161
|
+
List of detectors to use. If None, use all detectors.
|
|
162
|
+
|
|
163
|
+
Returns
|
|
164
|
+
-------
|
|
165
|
+
pd.DataFrame
|
|
166
|
+
DataFrame with clustering labels added. Noise points are labeled as -1.
|
|
167
|
+
"""
|
|
168
|
+
# Filter the DataFrame
|
|
169
|
+
sub_dataframe = self.filter_dataframe(dataframe=dataframe, features=features, detectors=detectors)
|
|
170
|
+
|
|
171
|
+
# Ensure data is dequantified if it uses Pint quantities
|
|
172
|
+
if hasattr(sub_dataframe, 'pint'):
|
|
173
|
+
sub_dataframe = sub_dataframe.pint.dequantify().droplevel('unit', axis=1)
|
|
174
|
+
|
|
175
|
+
# Run DBSCAN
|
|
176
|
+
dbscan = DBSCAN(eps=self.epsilon, min_samples=self.min_samples)
|
|
177
|
+
labels = dbscan.fit_predict(sub_dataframe)
|
|
178
|
+
|
|
179
|
+
# Add labels to the original DataFrame
|
|
180
|
+
dataframe['Label'] = labels
|
|
181
|
+
|
|
182
|
+
return labels
|