DeepPeak 0.0.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.
- deeppeak-0.0.1/.flake8 +11 -0
- deeppeak-0.0.1/.github/dependabot.yml +11 -0
- deeppeak-0.0.1/.github/workflows/deploy_PyPi.yml +20 -0
- deeppeak-0.0.1/.github/workflows/deploy_anaconda.yml +21 -0
- deeppeak-0.0.1/.github/workflows/deploy_coverage.yml +25 -0
- deeppeak-0.0.1/.github/workflows/deploy_documentation.yml +24 -0
- deeppeak-0.0.1/.gitignore +28 -0
- deeppeak-0.0.1/DeepPeak/__init__.py +5 -0
- deeppeak-0.0.1/DeepPeak/_version.py +34 -0
- deeppeak-0.0.1/DeepPeak/algorithms/__init__.py +1 -0
- deeppeak-0.0.1/DeepPeak/algorithms/non_maximum_suppression.py +330 -0
- deeppeak-0.0.1/DeepPeak/dataset.py +54 -0
- deeppeak-0.0.1/DeepPeak/directories.py +38 -0
- deeppeak-0.0.1/DeepPeak/helper.py +50 -0
- deeppeak-0.0.1/DeepPeak/machine_learning/__init__.py +0 -0
- deeppeak-0.0.1/DeepPeak/machine_learning/classifier/__init__.py +3 -0
- deeppeak-0.0.1/DeepPeak/machine_learning/classifier/autoencoder.py +90 -0
- deeppeak-0.0.1/DeepPeak/machine_learning/classifier/base.py +219 -0
- deeppeak-0.0.1/DeepPeak/machine_learning/classifier/dense.py +63 -0
- deeppeak-0.0.1/DeepPeak/machine_learning/classifier/utils.py +250 -0
- deeppeak-0.0.1/DeepPeak/machine_learning/classifier/wavenet.py +88 -0
- deeppeak-0.0.1/DeepPeak/machine_learning/weights/ROI_128.keras +0 -0
- deeppeak-0.0.1/DeepPeak/machine_learning/weights/__init__.py +0 -0
- deeppeak-0.0.1/DeepPeak/machine_learning/weights/temporary.keras +0 -0
- deeppeak-0.0.1/DeepPeak/signals.py +345 -0
- deeppeak-0.0.1/DeepPeak/utils.py +169 -0
- deeppeak-0.0.1/DeepPeak/visualization.py +767 -0
- deeppeak-0.0.1/DeepPeak.egg-info/PKG-INFO +74 -0
- deeppeak-0.0.1/DeepPeak.egg-info/SOURCES.txt +63 -0
- deeppeak-0.0.1/DeepPeak.egg-info/dependency_links.txt +1 -0
- deeppeak-0.0.1/DeepPeak.egg-info/requires.txt +23 -0
- deeppeak-0.0.1/DeepPeak.egg-info/top_level.txt +1 -0
- deeppeak-0.0.1/LICENSE +21 -0
- deeppeak-0.0.1/PKG-INFO +74 -0
- deeppeak-0.0.1/README.rst +11 -0
- deeppeak-0.0.1/development/confidence.py +183 -0
- deeppeak-0.0.1/development/dev_0.py +107 -0
- deeppeak-0.0.1/development/dev_1.py +70 -0
- deeppeak-0.0.1/development/train_roi_model.py +113 -0
- deeppeak-0.0.1/development/use_roi_model.py +53 -0
- deeppeak-0.0.1/docs/Makefile +19 -0
- deeppeak-0.0.1/docs/examples/README.rst +137 -0
- deeppeak-0.0.1/docs/examples/classifier_autoencoder.py +94 -0
- deeppeak-0.0.1/docs/examples/classifier_dense.py +92 -0
- deeppeak-0.0.1/docs/examples/classifier_wavenet.py +92 -0
- deeppeak-0.0.1/docs/examples/data_generation.py +50 -0
- deeppeak-0.0.1/docs/examples/non_maximum_suppression.py +49 -0
- deeppeak-0.0.1/docs/images/logo.png +0 -0
- deeppeak-0.0.1/docs/make.bat +35 -0
- deeppeak-0.0.1/docs/source/_static/default.css +19 -0
- deeppeak-0.0.1/docs/source/_static/logo.png +0 -0
- deeppeak-0.0.1/docs/source/_static/thumbnail.png +0 -0
- deeppeak-0.0.1/docs/source/code.rst +4 -0
- deeppeak-0.0.1/docs/source/conf.py +175 -0
- deeppeak-0.0.1/docs/source/examples.rst +12 -0
- deeppeak-0.0.1/docs/source/gallery/index.rst +256 -0
- deeppeak-0.0.1/docs/source/index.rst +13 -0
- deeppeak-0.0.1/docs/source/references.rst +3 -0
- deeppeak-0.0.1/docs/source/sg_execution_times.rst +49 -0
- deeppeak-0.0.1/docs/source/theory.rst +2 -0
- deeppeak-0.0.1/meta.yaml +57 -0
- deeppeak-0.0.1/pyproject.toml +99 -0
- deeppeak-0.0.1/setup.cfg +4 -0
- deeppeak-0.0.1/tests/__init__.py +0 -0
- deeppeak-0.0.1/tests/classifiers.py +67 -0
deeppeak-0.0.1/.flake8
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: Deploy to PyPi
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ "master" ]
|
|
6
|
+
tags:
|
|
7
|
+
- '*'
|
|
8
|
+
pull_request:
|
|
9
|
+
branches: [ "master" ]
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: write
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
pure_package:
|
|
16
|
+
uses: MartinPdeS/MPSActions/.github/workflows/publish_pure_package_to_PyPi.yml@master
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.11"
|
|
19
|
+
secrets:
|
|
20
|
+
PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: Deploy Conda Packages
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ "master" ]
|
|
6
|
+
tags:
|
|
7
|
+
- '*'
|
|
8
|
+
pull_request:
|
|
9
|
+
branches: [ "master" ]
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: write
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
ManyLinux_x86_64:
|
|
16
|
+
uses: MartinPdeS/MPSActions/.github/workflows/publish_compiled_package_to_anaconda.yml@master
|
|
17
|
+
with:
|
|
18
|
+
python-versions: '["3.11", "3.12"]'
|
|
19
|
+
secrets:
|
|
20
|
+
ANACONDA_API_TOKEN: ${{ secrets.ANACONDA_API_TOKEN }}
|
|
21
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Simple workflow for deploying static content to GitHub Pages
|
|
2
|
+
name: Deploy coverage
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
branches: [ "master" ]
|
|
7
|
+
tags:
|
|
8
|
+
- '*'
|
|
9
|
+
pull_request:
|
|
10
|
+
branches: [ "master" ]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
|
14
|
+
permissions:
|
|
15
|
+
contents: write
|
|
16
|
+
pages: write
|
|
17
|
+
id-token: write
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
ManyLinux_x86_64:
|
|
21
|
+
uses: MartinPdeS/MPSActions/.github/workflows/publish_coverage.yml@master
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.11"
|
|
24
|
+
secrets:
|
|
25
|
+
_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Simple workflow for deploying static content to GitHub Pages
|
|
2
|
+
name: Deploy documentation to github page
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
branches: [ "master" ]
|
|
7
|
+
tags:
|
|
8
|
+
- '*'
|
|
9
|
+
pull_request:
|
|
10
|
+
branches: [ "master" ]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
|
14
|
+
permissions:
|
|
15
|
+
contents: write
|
|
16
|
+
pages: write
|
|
17
|
+
id-token: write
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
ManyLinux_x86_64:
|
|
21
|
+
uses: MartinPdeS/MPSActions/.github/workflows/publish_documentation.yml@master
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.11"
|
|
24
|
+
package-name: "DeepPeak"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# General patterns
|
|
2
|
+
*.DS_store
|
|
3
|
+
*.ipynb_checkpoints*
|
|
4
|
+
*__pycache__*
|
|
5
|
+
*.so
|
|
6
|
+
*.a
|
|
7
|
+
.coverage*
|
|
8
|
+
*.pytest_cache
|
|
9
|
+
htmlcov
|
|
10
|
+
dist
|
|
11
|
+
build
|
|
12
|
+
*.egg*
|
|
13
|
+
*.whl
|
|
14
|
+
CMakeCache.txt
|
|
15
|
+
CMakeFiles
|
|
16
|
+
install_manifest.txt
|
|
17
|
+
cmake_install.cmake
|
|
18
|
+
wheelhouse
|
|
19
|
+
|
|
20
|
+
# Docs-specific patterns
|
|
21
|
+
docs/source/gallery/*
|
|
22
|
+
!docs/source/gallery/index.rst
|
|
23
|
+
PyOptik/data/**/test*
|
|
24
|
+
PyOptik/data/**/example*
|
|
25
|
+
|
|
26
|
+
# Override for documentation_build
|
|
27
|
+
!documentation_build
|
|
28
|
+
!documentation_build/**
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
TYPE_CHECKING = False
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from typing import Tuple
|
|
16
|
+
from typing import Union
|
|
17
|
+
|
|
18
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
20
|
+
else:
|
|
21
|
+
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
23
|
+
|
|
24
|
+
version: str
|
|
25
|
+
__version__: str
|
|
26
|
+
__version_tuple__: VERSION_TUPLE
|
|
27
|
+
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
30
|
+
|
|
31
|
+
__version__ = version = '0.0.1'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 0, 1)
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = 'g39ce84fdb'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .non_maximum_suppression import NonMaximumSuppression
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from numpy.typing import NDArray
|
|
5
|
+
import matplotlib.pyplot as plt
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class NonMaximumSuppression:
|
|
9
|
+
r"""
|
|
10
|
+
Detect up to three equal-width Gaussian pulses in a one-dimensional signal.
|
|
11
|
+
|
|
12
|
+
The detector operates in two stages:
|
|
13
|
+
|
|
14
|
+
1. **Matched filtering**
|
|
15
|
+
The input signal is correlated with a unit-energy Gaussian kernel.
|
|
16
|
+
|
|
17
|
+
2. **Non-maximum suppression**
|
|
18
|
+
Candidate peaks are selected as local maxima above a threshold.
|
|
19
|
+
|
|
20
|
+
.. math::
|
|
21
|
+
|
|
22
|
+
y(t) = \sum_{k=1}^A a_k \exp\!\left(-\frac{(t - \mu_k)^2}{2\sigma^2}\right) + \eta(t)
|
|
23
|
+
|
|
24
|
+
where all pulses share the same width :math:`\sigma`.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
gaussian_sigma: float,
|
|
30
|
+
*,
|
|
31
|
+
threshold: float | str = "auto",
|
|
32
|
+
minimum_separation: float | None = None,
|
|
33
|
+
maximum_number_of_pulses: int = 3,
|
|
34
|
+
kernel_truncation_radius_in_sigmas: float = 3.5,
|
|
35
|
+
) -> None:
|
|
36
|
+
r"""
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
gaussian_sigma : float
|
|
40
|
+
The known common Gaussian standard deviation :math:`\sigma`.
|
|
41
|
+
threshold : float | "auto"
|
|
42
|
+
Threshold on the matched-filter output.
|
|
43
|
+
If ``"auto"``, it is set to :math:`4.5 \,\hat\sigma_n` where
|
|
44
|
+
:math:`\hat\sigma_n` is a robust noise estimate.
|
|
45
|
+
minimum_separation : float | None
|
|
46
|
+
Minimum allowed peak separation in time units.
|
|
47
|
+
Defaults to :math:`\sigma` if None.
|
|
48
|
+
maximum_number_of_pulses : int
|
|
49
|
+
Maximum number of pulses to return (1-N).
|
|
50
|
+
kernel_truncation_radius_in_sigmas : float
|
|
51
|
+
Radius of the Gaussian FIR kernel in multiples of :math:`\sigma`.
|
|
52
|
+
"""
|
|
53
|
+
self.gaussian_sigma = float(gaussian_sigma)
|
|
54
|
+
self.threshold = threshold
|
|
55
|
+
self.minimum_separation = minimum_separation
|
|
56
|
+
self.maximum_number_of_pulses = int(maximum_number_of_pulses)
|
|
57
|
+
self.kernel_truncation_radius_in_sigmas = float(kernel_truncation_radius_in_sigmas)
|
|
58
|
+
|
|
59
|
+
# Results after detection (coarse, no quadratic refinement)
|
|
60
|
+
self.gaussian_kernel_: NDArray[np.float64] | None = None
|
|
61
|
+
self.matched_filter_output_: NDArray[np.float64] | None = None
|
|
62
|
+
self.peak_indices_: NDArray[np.int_] | None = None
|
|
63
|
+
self.peak_times_: NDArray[np.float64] | None = None
|
|
64
|
+
self.peak_heights_: NDArray[np.float64] | None = None
|
|
65
|
+
self.threshold_used_: float | None = None
|
|
66
|
+
self.suppression_half_window_in_samples_: int | None = None
|
|
67
|
+
self.results: dict[str, object] | None = None
|
|
68
|
+
|
|
69
|
+
def run(self, time_samples: NDArray[np.float64], signal: NDArray[np.float64]) -> dict[str, object]:
|
|
70
|
+
r"""
|
|
71
|
+
Run the detection pipeline (matched filter + non-maximum suppression).
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
time_samples : array
|
|
76
|
+
Uniform sample times :math:`t[n]` with spacing :math:`\Delta t`.
|
|
77
|
+
signal : array
|
|
78
|
+
Input signal samples :math:`y[n]`.
|
|
79
|
+
|
|
80
|
+
Returns
|
|
81
|
+
-------
|
|
82
|
+
dict
|
|
83
|
+
Results including coarse peak times (sample-aligned), their heights,
|
|
84
|
+
the matched filter output, and configuration used.
|
|
85
|
+
|
|
86
|
+
Notes
|
|
87
|
+
-----
|
|
88
|
+
Matched filter correlation:
|
|
89
|
+
|
|
90
|
+
.. math::
|
|
91
|
+
|
|
92
|
+
r[n] = \sum_m y[m] \, g[m-n]
|
|
93
|
+
|
|
94
|
+
Non-maximum suppression keeps peaks :math:`n` such that
|
|
95
|
+
|
|
96
|
+
.. math::
|
|
97
|
+
|
|
98
|
+
r[n] = \max_{|k-n|\leq W} r[k], \qquad r[n] \ge \tau.
|
|
99
|
+
"""
|
|
100
|
+
assert signal.ndim == 1 and time_samples.ndim == 1 and len(signal) == len(time_samples), \
|
|
101
|
+
"signal and time_samples must be one-dimensional arrays of the same length"
|
|
102
|
+
|
|
103
|
+
sample_interval = float(time_samples[1] - time_samples[0])
|
|
104
|
+
|
|
105
|
+
# 1) Build unit-energy Gaussian kernel and apply matched filter (correlation)
|
|
106
|
+
gaussian_kernel = self._build_gaussian_kernel(
|
|
107
|
+
sample_interval=sample_interval,
|
|
108
|
+
gaussian_sigma=self.gaussian_sigma,
|
|
109
|
+
truncation_radius_in_sigmas=self.kernel_truncation_radius_in_sigmas,
|
|
110
|
+
)
|
|
111
|
+
matched_filter_output = self._correlate(signal, gaussian_kernel)
|
|
112
|
+
|
|
113
|
+
# 2) Determine suppression window and threshold
|
|
114
|
+
if self.minimum_separation is None:
|
|
115
|
+
minimum_separation = self.gaussian_sigma
|
|
116
|
+
else:
|
|
117
|
+
minimum_separation = self.minimum_separation
|
|
118
|
+
|
|
119
|
+
suppression_half_window_in_samples = int(max(1, np.round(minimum_separation / sample_interval / 2.0)))
|
|
120
|
+
|
|
121
|
+
if self.threshold == "auto":
|
|
122
|
+
noise_sigma = self._estimate_noise_std(matched_filter_output)
|
|
123
|
+
threshold_value = 4.5 * noise_sigma
|
|
124
|
+
else:
|
|
125
|
+
threshold_value = float(self.threshold)
|
|
126
|
+
|
|
127
|
+
# 3) Non-maximum suppression (coarse indices only)
|
|
128
|
+
peak_indices = self._non_maximum_suppression(
|
|
129
|
+
values=matched_filter_output,
|
|
130
|
+
half_window=suppression_half_window_in_samples,
|
|
131
|
+
threshold=threshold_value,
|
|
132
|
+
max_peaks=self.maximum_number_of_pulses,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Coarse peak times and heights (no sub-sample refinement)
|
|
136
|
+
peak_times = time_samples[peak_indices] if peak_indices.size else np.empty(0)
|
|
137
|
+
peak_heights = matched_filter_output[peak_indices] if peak_indices.size else np.empty(0)
|
|
138
|
+
|
|
139
|
+
# Save results on the instance
|
|
140
|
+
self.gaussian_kernel_ = gaussian_kernel
|
|
141
|
+
self.matched_filter_output_ = matched_filter_output
|
|
142
|
+
self.peak_indices_ = np.sort(peak_indices)
|
|
143
|
+
order = np.argsort(peak_times)
|
|
144
|
+
self.peak_times_ = peak_times[order]
|
|
145
|
+
self.peak_heights_ = peak_heights[order]
|
|
146
|
+
self.threshold_used_ = float(threshold_value)
|
|
147
|
+
self.suppression_half_window_in_samples_ = int(suppression_half_window_in_samples)
|
|
148
|
+
|
|
149
|
+
self.results = {
|
|
150
|
+
"signal": signal,
|
|
151
|
+
"time_samples": time_samples,
|
|
152
|
+
"peak_indices": self.peak_indices_,
|
|
153
|
+
"peak_times": self.peak_times_,
|
|
154
|
+
"peak_heights": self.peak_heights_,
|
|
155
|
+
"matched_filter_output": matched_filter_output,
|
|
156
|
+
"gaussian_kernel": gaussian_kernel,
|
|
157
|
+
"threshold_used": float(threshold_value),
|
|
158
|
+
"suppression_half_window_in_samples": int(suppression_half_window_in_samples),
|
|
159
|
+
}
|
|
160
|
+
return self.results
|
|
161
|
+
|
|
162
|
+
def plot(self) -> None:
|
|
163
|
+
r"""
|
|
164
|
+
Plot the input signal, matched-filter output, and coarse peak locations.
|
|
165
|
+
"""
|
|
166
|
+
from MPSPlots.styles import mps as plot_style
|
|
167
|
+
if not getattr(self, "results", None):
|
|
168
|
+
raise ValueError("Computation not done yet, use .run() first.")
|
|
169
|
+
|
|
170
|
+
with plt.style.context(plot_style):
|
|
171
|
+
t = self.results["time_samples"]
|
|
172
|
+
y = self.results["signal"]
|
|
173
|
+
r = self.results["matched_filter_output"]
|
|
174
|
+
peaks_t = self.results["peak_times"]
|
|
175
|
+
|
|
176
|
+
plt.figure()
|
|
177
|
+
plt.plot(t, y, label="signal y(t)")
|
|
178
|
+
plt.plot(t, r, label="matched filter output r(t)")
|
|
179
|
+
for m in peaks_t:
|
|
180
|
+
plt.axvline(m, linestyle="--", alpha=0.6, label=None)
|
|
181
|
+
plt.title("Equal-width Gaussian pulse detection (coarse)")
|
|
182
|
+
plt.xlabel("t")
|
|
183
|
+
plt.ylabel("amplitude")
|
|
184
|
+
plt.legend()
|
|
185
|
+
plt.show()
|
|
186
|
+
|
|
187
|
+
# ---------------- Static helper methods ----------------
|
|
188
|
+
|
|
189
|
+
@staticmethod
|
|
190
|
+
def full_width_half_maximum_to_sigma(fwhm: float) -> float:
|
|
191
|
+
r"""
|
|
192
|
+
Convert full width at half maximum (FWHM) to Gaussian standard deviation.
|
|
193
|
+
|
|
194
|
+
.. math::
|
|
195
|
+
|
|
196
|
+
\text{FWHM} = 2 \sqrt{2 \ln 2} \,\sigma \;\;\Rightarrow\;\;
|
|
197
|
+
\sigma = \frac{\text{FWHM}}{2\sqrt{2\ln 2}}
|
|
198
|
+
"""
|
|
199
|
+
return fwhm / (2.0 * np.sqrt(2.0 * np.log(2.0)))
|
|
200
|
+
|
|
201
|
+
# ---------------- Private methods (implementation) ----------------
|
|
202
|
+
|
|
203
|
+
@staticmethod
|
|
204
|
+
def _build_gaussian_kernel(
|
|
205
|
+
sample_interval: float, gaussian_sigma: float, truncation_radius_in_sigmas: float
|
|
206
|
+
) -> NDArray[np.float64]:
|
|
207
|
+
r"""
|
|
208
|
+
Construct a discrete Gaussian kernel and normalize to unit energy.
|
|
209
|
+
|
|
210
|
+
.. math::
|
|
211
|
+
|
|
212
|
+
g[k] = \exp\!\left(-\tfrac{1}{2} \left(\frac{k \,\Delta t}{\sigma}\right)^2\right),
|
|
213
|
+
\quad k = -L, \dots, L,
|
|
214
|
+
|
|
215
|
+
where :math:`L = \left\lceil \dfrac{\text{radius}\,\sigma}{\Delta t} \right\rceil`
|
|
216
|
+
and the discrete energy satisfies :math:`\sum_k g[k]^2 = 1`.
|
|
217
|
+
"""
|
|
218
|
+
half_length = int(np.ceil(truncation_radius_in_sigmas * gaussian_sigma / sample_interval))
|
|
219
|
+
time_axis = np.arange(-half_length, half_length + 1, dtype=float) * sample_interval
|
|
220
|
+
kernel = np.exp(-0.5 * (time_axis / gaussian_sigma) ** 2)
|
|
221
|
+
kernel /= np.sqrt(np.sum(kernel ** 2))
|
|
222
|
+
return kernel
|
|
223
|
+
|
|
224
|
+
@staticmethod
|
|
225
|
+
def _correlate(signal: NDArray[np.float64], kernel: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
226
|
+
r"""
|
|
227
|
+
Discrete correlation (matched filter):
|
|
228
|
+
|
|
229
|
+
.. math::
|
|
230
|
+
|
|
231
|
+
r[n] = \sum_m y[m] \, g[m-n].
|
|
232
|
+
|
|
233
|
+
Implemented as convolution with the reversed kernel.
|
|
234
|
+
"""
|
|
235
|
+
return np.convolve(signal, kernel[::-1], mode="same")
|
|
236
|
+
|
|
237
|
+
@staticmethod
|
|
238
|
+
def _non_maximum_suppression(
|
|
239
|
+
values: NDArray[np.float64],
|
|
240
|
+
half_window: int,
|
|
241
|
+
threshold: float,
|
|
242
|
+
max_peaks: int,
|
|
243
|
+
) -> NDArray[np.int_]:
|
|
244
|
+
r"""
|
|
245
|
+
Non-maximum suppression.
|
|
246
|
+
|
|
247
|
+
Keep index :math:`n` if
|
|
248
|
+
|
|
249
|
+
.. math::
|
|
250
|
+
|
|
251
|
+
r[n] = \max_{|k-n|\leq W} r[k], \quad r[n] \ge \tau,
|
|
252
|
+
|
|
253
|
+
where :math:`W` is the half-window and :math:`\tau` is the threshold.
|
|
254
|
+
|
|
255
|
+
Returns at most ``max_peaks`` indices with the largest responses.
|
|
256
|
+
"""
|
|
257
|
+
if half_window < 1:
|
|
258
|
+
core = (values[1:-1] > values[:-2]) & (values[1:-1] >= values[2:]) & (values[1:-1] >= threshold)
|
|
259
|
+
idx = np.where(core)[0] + 1
|
|
260
|
+
else:
|
|
261
|
+
window_len = 2 * half_window + 1
|
|
262
|
+
padded = np.pad(values, (half_window, half_window), mode="edge")
|
|
263
|
+
windows = NonMaximumSuppression._sliding_window_view_1d(padded, window_len)
|
|
264
|
+
local_max = windows.max(axis=1)
|
|
265
|
+
idx = np.where((values >= local_max) & (values >= threshold))[0]
|
|
266
|
+
|
|
267
|
+
if idx.size > max_peaks:
|
|
268
|
+
keep = np.argpartition(values[idx], -max_peaks)[-max_peaks:]
|
|
269
|
+
idx = idx[keep]
|
|
270
|
+
idx = idx[np.argsort(values[idx])]
|
|
271
|
+
|
|
272
|
+
return np.sort(idx)
|
|
273
|
+
|
|
274
|
+
@staticmethod
|
|
275
|
+
def _estimate_noise_std(values: NDArray[np.float64]) -> float:
|
|
276
|
+
r"""
|
|
277
|
+
Estimate noise standard deviation from median absolute deviation (MAD).
|
|
278
|
+
|
|
279
|
+
.. math::
|
|
280
|
+
|
|
281
|
+
m = \text{median}(x), \quad
|
|
282
|
+
MAD = \text{median}(|x-m|), \quad
|
|
283
|
+
\hat\sigma_n \approx 1.4826 \, MAD
|
|
284
|
+
"""
|
|
285
|
+
m = np.median(values)
|
|
286
|
+
mad = np.median(np.abs(values - m))
|
|
287
|
+
return 1.4826 * mad
|
|
288
|
+
|
|
289
|
+
# ---------------- Local replacement for sliding_window_view ----------------
|
|
290
|
+
|
|
291
|
+
@staticmethod
|
|
292
|
+
def _sliding_window_view_1d(
|
|
293
|
+
array: NDArray[np.float64],
|
|
294
|
+
window_length: int,
|
|
295
|
+
) -> NDArray[np.float64]:
|
|
296
|
+
r"""
|
|
297
|
+
Create a 2D strided view of 1D ``array`` with a moving window of length ``window_length``.
|
|
298
|
+
|
|
299
|
+
The returned view has shape :math:`(N - L + 1, L)` where
|
|
300
|
+
:math:`N` is the length of ``array`` and :math:`L` is ``window_length``.
|
|
301
|
+
No data is copied.
|
|
302
|
+
|
|
303
|
+
This function replicates the essential behavior of
|
|
304
|
+
:code:`numpy.lib.stride_tricks.sliding_window_view` for the 1D case,
|
|
305
|
+
without importing it directly.
|
|
306
|
+
|
|
307
|
+
Parameters
|
|
308
|
+
----------
|
|
309
|
+
array : array
|
|
310
|
+
One-dimensional input array.
|
|
311
|
+
window_length : int
|
|
312
|
+
Length :math:`L` of each sliding window (must satisfy :math:`1 \le L \le N`).
|
|
313
|
+
|
|
314
|
+
Returns
|
|
315
|
+
-------
|
|
316
|
+
array
|
|
317
|
+
A read-only view of shape :math:`(N-L+1, L)`.
|
|
318
|
+
"""
|
|
319
|
+
if array.ndim != 1:
|
|
320
|
+
raise ValueError("array must be one-dimensional")
|
|
321
|
+
if not (1 <= window_length <= array.shape[0]):
|
|
322
|
+
raise ValueError("window_length must satisfy 1 <= L <= len(array)")
|
|
323
|
+
|
|
324
|
+
N = array.shape[0]
|
|
325
|
+
stride = array.strides[0]
|
|
326
|
+
shape = (N - window_length + 1, window_length)
|
|
327
|
+
strides = (stride, stride)
|
|
328
|
+
view = np.lib.stride_tricks.as_strided(array, shape=shape, strides=strides)
|
|
329
|
+
view.setflags(write=False)
|
|
330
|
+
return view
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import matplotlib.pyplot as plt
|
|
2
|
+
from MPSPlots.styles import mps as plot_style
|
|
3
|
+
|
|
4
|
+
class DataSet:
|
|
5
|
+
"""
|
|
6
|
+
A simple container class for datasets.
|
|
7
|
+
|
|
8
|
+
This class dynamically sets attributes based on the provided keyword arguments,
|
|
9
|
+
allowing for flexible storage of various dataset components.
|
|
10
|
+
|
|
11
|
+
Parameters
|
|
12
|
+
----------
|
|
13
|
+
**kwargs : dict
|
|
14
|
+
Keyword arguments to be set as attributes of the instance.
|
|
15
|
+
"""
|
|
16
|
+
def __init__(self, **kwargs):
|
|
17
|
+
for key, value in kwargs.items():
|
|
18
|
+
setattr(self, key, value)
|
|
19
|
+
|
|
20
|
+
def plot(self, number_of_samples: int | None = None, ax: plt.Axes = None, show: bool = True):
|
|
21
|
+
sample_count = self.signals.shape[0]
|
|
22
|
+
|
|
23
|
+
if number_of_samples is None:
|
|
24
|
+
number_of_samples = sample_count
|
|
25
|
+
|
|
26
|
+
with plt.style.context(plot_style):
|
|
27
|
+
figure, axes = plt.subplots(nrows=number_of_samples, ncols=1, figsize=(8, 3 * number_of_samples), squeeze=False)
|
|
28
|
+
|
|
29
|
+
for idx, ax in enumerate(axes.flatten()):
|
|
30
|
+
ax.plot(self.x_values, self.signals[idx], label='signal')
|
|
31
|
+
ax.vlines(self.positions[idx], ymin=0, ymax=1, transform=ax.get_xaxis_transform(), color='red', linestyle='--', linewidth=1, label='positions')
|
|
32
|
+
|
|
33
|
+
if self.region_of_interest is not None:
|
|
34
|
+
roi_patch = ax.fill_between(self.x_values, y1=0, y2=1,
|
|
35
|
+
where=(self.region_of_interest[idx] != 0),
|
|
36
|
+
color='lightblue',
|
|
37
|
+
alpha=1.0,
|
|
38
|
+
transform=ax.get_xaxis_transform(),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# build legend (add custom entry for ROI if present)
|
|
42
|
+
handles, labels = ax.get_legend_handles_labels()
|
|
43
|
+
if self.region_of_interest is not None:
|
|
44
|
+
handles.append(roi_patch)
|
|
45
|
+
labels.append("ROI")
|
|
46
|
+
|
|
47
|
+
by_label = {}
|
|
48
|
+
for h, l in zip(handles, labels):
|
|
49
|
+
if l and not l.startswith("_") and l not in by_label:
|
|
50
|
+
by_label[l] = h
|
|
51
|
+
ax.legend(by_label.values(), by_label.keys())
|
|
52
|
+
|
|
53
|
+
if show:
|
|
54
|
+
plt.show()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import DeepPeak
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
'root_path',
|
|
10
|
+
'project_path',
|
|
11
|
+
'doc_path',
|
|
12
|
+
'doc_css_path',
|
|
13
|
+
'logo_path'
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
root_path = Path(DeepPeak.__path__[0])
|
|
17
|
+
|
|
18
|
+
project_path = root_path.parents[0]
|
|
19
|
+
|
|
20
|
+
weights_path = root_path.joinpath('weights')
|
|
21
|
+
|
|
22
|
+
example_directory = root_path.joinpath('examples')
|
|
23
|
+
|
|
24
|
+
doc_path = project_path.joinpath('docs')
|
|
25
|
+
|
|
26
|
+
doc_css_path = doc_path.joinpath('source/_static/default.css')
|
|
27
|
+
|
|
28
|
+
logo_path = doc_path.joinpath('images/logo.png')
|
|
29
|
+
|
|
30
|
+
examples_path = root_path.joinpath('examples')
|
|
31
|
+
|
|
32
|
+
if __name__ == '__main__':
|
|
33
|
+
for path_name in __all__:
|
|
34
|
+
path = locals()[path_name]
|
|
35
|
+
print(path)
|
|
36
|
+
assert path.exists(), f"Path {path_name} do not exists"
|
|
37
|
+
|
|
38
|
+
# -
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
from functools import wraps
|
|
4
|
+
from MPSPlots.styles import mps as plot_style
|
|
5
|
+
import matplotlib.pyplot as plt
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def mpl_plot(function):
|
|
9
|
+
@wraps(function)
|
|
10
|
+
def wrapper(self, show: bool = True, save_as: str = None, **kwargs):
|
|
11
|
+
"""
|
|
12
|
+
Decorator to set the plot style and handle figure saving and showing.
|
|
13
|
+
This decorator applies a specific plot style, saves the figure if a filename is provided,
|
|
14
|
+
and shows the plot if requested.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
function : callable
|
|
19
|
+
The plotting function to be wrapped.
|
|
20
|
+
show : bool, optional
|
|
21
|
+
Whether to display the plot (default: True).
|
|
22
|
+
save_as : str, optional
|
|
23
|
+
If provided, the figure is saved to this filename.
|
|
24
|
+
**kwargs : dict
|
|
25
|
+
Additional keyword arguments to pass to the plotting function.
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
plt.Figure
|
|
30
|
+
The figure containing the plot.
|
|
31
|
+
"""
|
|
32
|
+
# Create plot
|
|
33
|
+
with plt.style.context(plot_style):
|
|
34
|
+
figure, ax = plt.subplots(1, 1, figsize=(12, 5))
|
|
35
|
+
|
|
36
|
+
with plt.style.context(plot_style):
|
|
37
|
+
function(self, figure=figure, ax=ax, **kwargs)
|
|
38
|
+
|
|
39
|
+
figure.tight_layout()
|
|
40
|
+
|
|
41
|
+
if save_as is not None:
|
|
42
|
+
plt.savefig(save_as)
|
|
43
|
+
print(f"Figure saved as {save_as}")
|
|
44
|
+
|
|
45
|
+
if show:
|
|
46
|
+
plt.show()
|
|
47
|
+
|
|
48
|
+
return figure
|
|
49
|
+
|
|
50
|
+
return wrapper
|
|
File without changes
|