Skip to content

Commit 0bedc41

Browse files
rcannoodghar1821
andauthored
Describe the peak calling in ratio_consistent_peaks as implemented (#132)
* describe the peak calling in ratio_consistent_peaks as implemented * Document the standardisation step and the actual height/prominence thresholds * Drop persistent_peak_count() and the scikit-tda dependency, it was never called * Only import matplotlib and seaborn when actually plotting * Say case 2 where the code means case 2 * update changelog --------- Co-authored-by: Givanna Putri <givanna.h@gmail.com>
1 parent d803136 commit 0bedc41

4 files changed

Lines changed: 17 additions & 51 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,9 @@
241241
* Update flowsom mapping similarity so we subset to just markers to correct, and lisi to remove control samples
242242
and unlabelled cells (PR #119).
243243

244+
* Update the `ratio_consistent_peaks` description to match the implemented peak calling
245+
(standardisation step, `height` 0.1 and `prominence` 0.01), and remove the unused
246+
`persistent_peak_count()` helper together with the `scikit-tda` dependency (PR #132).
244247
* Point `scripts/run_benchmark/wehi_hpc/run_full_hpc.sh` at `build/main` instead of
245248
`build/update_ilisi`, and label the seqera full run as `full` instead of `test_subset` (PR #129).
246249

@@ -249,7 +252,6 @@
249252

250253
* Clean up stale mock parameters and dead code (PR #133).
251254

252-
253255
* Fix bug in `average_batch_r2` where the R2 was computed on all cell types of a donor at once
254256
instead of on each cell type separately (PR #127).
255257

src/metrics/ratio_consistent_peaks/config.vsh.yaml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,13 @@ info:
2020
description: |
2121
The metric compares the number of cell type specific marker expression peaks between unintegrated and batch integrated data.
2222
The number of peaks is calculated using the `scipy.signal.find_peaks` function.
23-
The (cell type) marker expression profiles are first smoothed using kernel density estimation (KDE) (`scipy.stats.gaussian_kde`),
24-
and then peaks are identified using the `scipy.signal.find_peaks` function.
25-
For peak calling, the `prominence` parameter is set to 0.1 and the `height` parameter is set to 0.05*max_density.
23+
The (cell type) marker expression values of the two splits are first standardised together,
24+
using the pooled mean and standard deviation of the two splits, so that the peak calling
25+
thresholds mean the same thing for every marker.
26+
They are then smoothed using kernel density estimation (KDE) (`scipy.stats.gaussian_kde`,
27+
evaluated on a grid of 100 points), and peaks are identified using the
28+
`scipy.signal.find_peaks` function.
29+
For peak calling, the `height` parameter is set to 0.1 and the `prominence` parameter is set to 0.01.
2630
2731
Case Definitions:
2832
- Case 1: Consistent peaks in both unintegrated AND integrated data (ideal outcome).
@@ -75,10 +79,6 @@ engines:
7579
# Specifications for the Docker image for this component.
7680
- type: docker
7781
image: openproblems/base_python:1
78-
setup:
79-
- type: python
80-
packages:
81-
- scikit-tda
8282

8383
runners:
8484
# This platform allows running the component natively

src/metrics/ratio_consistent_peaks/helper.py

Lines changed: 4 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
import matplotlib.pyplot as plt
21
import numpy as np
3-
import seaborn as sns
4-
from ripser import ripser
52
from scipy.signal import find_peaks
63
from scipy.stats import gaussian_kde
74

@@ -65,6 +62,10 @@ def get_kde_density(expression_array, return_xgrid=False, plot=False):
6562
x_grid = np.concatenate([[min_val - step], x_grid])
6663

6764
if plot:
65+
# only needed when debugging locally, so don't import at module level
66+
import matplotlib.pyplot as plt
67+
import seaborn as sns
68+
6869
fig, ax = plt.subplots()
6970
sns.scatterplot(x=x_grid, y=density, ax=ax)
7071
ax.set_title("KDE Density Estimation")
@@ -98,40 +99,3 @@ def call_peaks(density):
9899
num_peaks = len(peaks)
99100

100101
return num_peaks
101-
102-
103-
def persistent_peak_count(ys, persistence_cutoff=0.08):
104-
"""
105-
Counts robust peaks in a 1D dataset using persistent homology.
106-
107-
Args:
108-
ys (np.ndarray): KDE of a marker expression (1D array)
109-
persistence_cutoff (float): a threshold that decides which peaks are “significant enough” to count.
110-
A large persistence peak survives over many levels of smoothing (i.e. a strong, real peak).
111-
A small persistence peak quickly merges into a neighbor — likely noise.
112-
0.01: very low threshold counts even weak bumps as peaks
113-
0.05: moderate (default) counts clearly separated peaks
114-
0.1–0.2: high threshold counts only strong, dominant peaks
115-
Default to 0.08 to biased towards strong peaks but not overly.
116-
117-
Returns:
118-
int: number of significant peaks
119-
"""
120-
121-
y = np.asarray(ys)
122-
if y.size == 0:
123-
return 0
124-
125-
# Shift if max is at the first bin
126-
if y.size > 1 and np.argmax(y) == 0:
127-
y = np.concatenate([[0.0], y[:-1]])
128-
129-
# Invert to turn peaks into "holes" for 0D persistence
130-
Y = -ys.reshape(-1, 1)
131-
diagram = ripser(Y, maxdim=0)["dgms"][0]
132-
persistence = diagram[:, 1] - diagram[:, 0]
133-
134-
# Define significance threshold relative to data range
135-
threshold = persistence_cutoff * np.ptp(ys)
136-
n_peaks = np.sum(persistence > threshold)
137-
return n_peaks

src/metrics/ratio_consistent_peaks/script.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@
6464
print("Compute metric (per cell type)", flush=True)
6565

6666
# case 1 = consistent peaks in unintegrated and also in integrated
67-
# case 3 = consistent peaks in unintegrated but inconsistent in integrated
68-
# not recording case 2 or 4 where unintegrated is inconsistent
67+
# case 2 = consistent peaks in unintegrated but inconsistent in integrated
68+
# case NGT = unintegrated is inconsistent, recorded but excluded from the ratio
6969
n_case1 = 0
7070
n_case2 = 0
7171

@@ -180,7 +180,7 @@
180180
np.std(s2_unscaled) == 0 and np.std(u_s2_unscaled) != 0
181181
):
182182
print(
183-
f"WARNING: Marker {marker}, donor {donor}, cell type {celltype}: has no variance either before or after integration. Automatic to case 3.",
183+
f"WARNING: Marker {marker}, donor {donor}, cell type {celltype}: has no variance after integration, but does before. Automatic to case 2.",
184184
flush=True,
185185
)
186186
n_case2 += 1

0 commit comments

Comments
 (0)