scbamqc 0.1.0__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.
- scbamqc-0.1.0/LICENSE +29 -0
- scbamqc-0.1.0/PKG-INFO +182 -0
- scbamqc-0.1.0/README.md +144 -0
- scbamqc-0.1.0/pyproject.toml +67 -0
- scbamqc-0.1.0/src/scbamqc/__init__.py +30 -0
- scbamqc-0.1.0/src/scbamqc/_report.py +79 -0
- scbamqc-0.1.0/src/scbamqc/bam.py +564 -0
- scbamqc-0.1.0/src/scbamqc/cli.py +264 -0
- scbamqc-0.1.0/src/scbamqc/io.py +57 -0
- scbamqc-0.1.0/src/scbamqc/pl.py +226 -0
- scbamqc-0.1.0/src/scbamqc/py.typed +0 -0
- scbamqc-0.1.0/src/scbamqc/qc.py +368 -0
scbamqc-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Argenis Arriojas
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
scbamqc-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: scbamqc
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Per-barcode BAM alignment QC and alignment-based cell filtering for single-cell RNA-seq
|
|
5
|
+
Author-email: Argenis Arriojas <ArriojasMaldonado001@umb.edu>
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: cyclopts>=3
|
|
17
|
+
Requires-Dist: numpy
|
|
18
|
+
Requires-Dist: pandas
|
|
19
|
+
Requires-Dist: rich
|
|
20
|
+
Requires-Dist: scikit-learn
|
|
21
|
+
Requires-Dist: scipy
|
|
22
|
+
Requires-Dist: scbamqc[anndata, bam, plot] ; extra == "all"
|
|
23
|
+
Requires-Dist: anndata>=0.10 ; extra == "anndata"
|
|
24
|
+
Requires-Dist: h5py ; extra == "anndata"
|
|
25
|
+
Requires-Dist: pysam>=0.22 ; extra == "bam"
|
|
26
|
+
Requires-Dist: matplotlib ; extra == "plot"
|
|
27
|
+
Requires-Dist: pytest>=8.3.4 ; extra == "test"
|
|
28
|
+
Project-URL: Bug Tracker, https://github.com/umbibio/scbamqc/issues
|
|
29
|
+
Project-URL: Changelog, https://github.com/umbibio/scbamqc/blob/main/CHANGELOG.md
|
|
30
|
+
Project-URL: Homepage, https://github.com/umbibio/scbamqc
|
|
31
|
+
Project-URL: Repository, https://github.com/umbibio/scbamqc.git
|
|
32
|
+
Provides-Extra: all
|
|
33
|
+
Provides-Extra: anndata
|
|
34
|
+
Provides-Extra: bam
|
|
35
|
+
Provides-Extra: plot
|
|
36
|
+
Provides-Extra: test
|
|
37
|
+
|
|
38
|
+
# scbamqc
|
|
39
|
+
|
|
40
|
+
Alignment QC for single-cell BAM files, and cell selection from that QC.
|
|
41
|
+
|
|
42
|
+
- `scbamqc bam` writes one row of alignment statistics for each cell barcode.
|
|
43
|
+
- `scbamqc.qc` calculates a quality score for each barcode and selects cells.
|
|
44
|
+
- `scbamqc.pl` plots the statistics and the score.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install scbamqc
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Add extras for more functions:
|
|
53
|
+
|
|
54
|
+
| Extra | Adds | Use it for |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| `bam` | pysam | `scbamqc bam` and `scbamqc tags` (Linux, macOS) |
|
|
57
|
+
| `anndata` | anndata, h5py | `scbamqc.io.read_10x_h5` |
|
|
58
|
+
| `plot` | matplotlib | `scbamqc.pl` and `scbamqc bam --plots` |
|
|
59
|
+
| `all` | all of the above | |
|
|
60
|
+
|
|
61
|
+
For example: `pip install "scbamqc[bam,plot]"`.
|
|
62
|
+
|
|
63
|
+
## Command line
|
|
64
|
+
|
|
65
|
+
The BAM file must be sorted by coordinate and indexed (`samtools index`).
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Cell Ranger
|
|
69
|
+
scbamqc bam possorted_genome_bam.bam -j 16 --out-prefix S1
|
|
70
|
+
|
|
71
|
+
# STARsolo SmartSeq: one read group for each cell
|
|
72
|
+
scbamqc bam Aligned.sortedByCoord.out.bam --barcode-tag RG
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The tool writes `S1_per_cell.csv.gz`, with one row for each barcode. To also
|
|
76
|
+
write histograms to `S1_plots.png`, add `--plots`.
|
|
77
|
+
|
|
78
|
+
For all options, run `scbamqc bam --help`.
|
|
79
|
+
|
|
80
|
+
### Other pipelines
|
|
81
|
+
|
|
82
|
+
Different pipelines write the barcode and the mismatch count in different BAM
|
|
83
|
+
tags. Find the tags before you run `bam`:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
scbamqc tags sample.bam
|
|
87
|
+
scbamqc bam sample.bam --barcode-tag CB --nm-tag NM
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`bam` shows a warning if most reads do not have the tags you set.
|
|
91
|
+
|
|
92
|
+
> [!WARNING]
|
|
93
|
+
> Without a mismatch tag, `mean_nm` is empty (`NaN`). Then no barcode gets a
|
|
94
|
+
> quality score.
|
|
95
|
+
|
|
96
|
+
## Python
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
import scbamqc as sbq
|
|
100
|
+
from scbamqc.io import read_10x_h5
|
|
101
|
+
|
|
102
|
+
adata = read_10x_h5("raw_feature_bc_matrix.h5")
|
|
103
|
+
adata = sbq.add_bam_qc(adata, "S1_per_cell.csv.gz")
|
|
104
|
+
adata = sbq.basic_qc(adata)
|
|
105
|
+
sbq.qc_score(adata, min_reads=150)
|
|
106
|
+
sbq.select_cells(adata)
|
|
107
|
+
|
|
108
|
+
sbq.pl.qc_score_scatter(adata, min_score=0, min_counts=100)
|
|
109
|
+
sbq.pl.qc_score_hist(adata, threshold=0.9)
|
|
110
|
+
|
|
111
|
+
cells = adata[adata.obs["selected"] & (adata.obs["qc_score"] > 0.9)].copy()
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
| Step | Result |
|
|
115
|
+
|---|---|
|
|
116
|
+
| `add_bam_qc` | Keeps the barcodes in the BAM table. Adds the table columns to `obs`. |
|
|
117
|
+
| `basic_qc` | Removes barcodes with fewer than 5 genes and genes in fewer than 5 barcodes. Adds `total_counts`. |
|
|
118
|
+
| `qc_score` | Adds `obs["qc_score"]`, from 0 to 1. |
|
|
119
|
+
| `select_cells` | Adds `obs["gmm_label"]` and `obs["selected"]`. |
|
|
120
|
+
|
|
121
|
+
### The quality score
|
|
122
|
+
|
|
123
|
+
1. A barcode gets a score only if `n_reads > min_reads` and `mean_nm < 1`.
|
|
124
|
+
Other barcodes get `NaN`.
|
|
125
|
+
2. A kernel density is fitted to three values of these barcodes:
|
|
126
|
+
`mean_clip_frac`, `std_clip_frac` and `mean_base_qual` divided by its
|
|
127
|
+
median.
|
|
128
|
+
3. The score is `log10(density + 1)`, scaled from 0 to 1.
|
|
129
|
+
|
|
130
|
+
A high score shows that the barcode aligns like most other barcodes.
|
|
131
|
+
|
|
132
|
+
Set `min_reads` for each library. The paper used values from 50 to 3,000.
|
|
133
|
+
|
|
134
|
+
`base_qual_scale` sets the divisor of `mean_base_qual`. The default is the
|
|
135
|
+
median of the scored barcodes. The divisor does not change the order of the
|
|
136
|
+
barcodes. It can change the score values only when the densities are near 1.
|
|
137
|
+
|
|
138
|
+
To use the same settings as the TgRN paper:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
sbq.qc_score(adata, min_reads=150, base_qual_scale=sbq.PAPER_BASE_QUAL_SCALE)
|
|
142
|
+
sbq.select_cells(adata, tol=sbq.PAPER_GMM_TOL)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Cell selection
|
|
146
|
+
|
|
147
|
+
`select_cells` fits a Gaussian mixture with two components to
|
|
148
|
+
`log10(total_counts)` and `qc_score`. A barcode is selected if:
|
|
149
|
+
|
|
150
|
+
- it is in the component with the higher counts,
|
|
151
|
+
- `total_counts > min_counts` (default 100),
|
|
152
|
+
- `qc_score > min_score` (default 0).
|
|
153
|
+
|
|
154
|
+
The mixture fit runs until it converges. The paper stopped the fit early
|
|
155
|
+
(`tol=PAPER_GMM_TOL`). On the paper sample, the converged fit selects 15,536
|
|
156
|
+
cells and the paper fit selects 16,120. All cells of the converged fit are in
|
|
157
|
+
the paper set.
|
|
158
|
+
|
|
159
|
+
## R
|
|
160
|
+
|
|
161
|
+
The R package in [`r/`](r/README.md) does the same steps for Seurat objects.
|
|
162
|
+
It reads the table that `scbamqc bam` writes.
|
|
163
|
+
|
|
164
|
+
```r
|
|
165
|
+
remotes::install_github("umbibio/scbamqc", subdir = "r")
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Development
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
uv sync
|
|
172
|
+
uv run pytest
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The tests in `tests/test_paper.py` compare the results with the paper. They
|
|
176
|
+
need the data in `../../data`, or in the folder set by `SCBAMQC_TEST_DATA`.
|
|
177
|
+
Without the data, pytest skips them.
|
|
178
|
+
|
|
179
|
+
## License
|
|
180
|
+
|
|
181
|
+
BSD 3-Clause.
|
|
182
|
+
|
scbamqc-0.1.0/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# scbamqc
|
|
2
|
+
|
|
3
|
+
Alignment QC for single-cell BAM files, and cell selection from that QC.
|
|
4
|
+
|
|
5
|
+
- `scbamqc bam` writes one row of alignment statistics for each cell barcode.
|
|
6
|
+
- `scbamqc.qc` calculates a quality score for each barcode and selects cells.
|
|
7
|
+
- `scbamqc.pl` plots the statistics and the score.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install scbamqc
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Add extras for more functions:
|
|
16
|
+
|
|
17
|
+
| Extra | Adds | Use it for |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| `bam` | pysam | `scbamqc bam` and `scbamqc tags` (Linux, macOS) |
|
|
20
|
+
| `anndata` | anndata, h5py | `scbamqc.io.read_10x_h5` |
|
|
21
|
+
| `plot` | matplotlib | `scbamqc.pl` and `scbamqc bam --plots` |
|
|
22
|
+
| `all` | all of the above | |
|
|
23
|
+
|
|
24
|
+
For example: `pip install "scbamqc[bam,plot]"`.
|
|
25
|
+
|
|
26
|
+
## Command line
|
|
27
|
+
|
|
28
|
+
The BAM file must be sorted by coordinate and indexed (`samtools index`).
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Cell Ranger
|
|
32
|
+
scbamqc bam possorted_genome_bam.bam -j 16 --out-prefix S1
|
|
33
|
+
|
|
34
|
+
# STARsolo SmartSeq: one read group for each cell
|
|
35
|
+
scbamqc bam Aligned.sortedByCoord.out.bam --barcode-tag RG
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The tool writes `S1_per_cell.csv.gz`, with one row for each barcode. To also
|
|
39
|
+
write histograms to `S1_plots.png`, add `--plots`.
|
|
40
|
+
|
|
41
|
+
For all options, run `scbamqc bam --help`.
|
|
42
|
+
|
|
43
|
+
### Other pipelines
|
|
44
|
+
|
|
45
|
+
Different pipelines write the barcode and the mismatch count in different BAM
|
|
46
|
+
tags. Find the tags before you run `bam`:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
scbamqc tags sample.bam
|
|
50
|
+
scbamqc bam sample.bam --barcode-tag CB --nm-tag NM
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`bam` shows a warning if most reads do not have the tags you set.
|
|
54
|
+
|
|
55
|
+
> [!WARNING]
|
|
56
|
+
> Without a mismatch tag, `mean_nm` is empty (`NaN`). Then no barcode gets a
|
|
57
|
+
> quality score.
|
|
58
|
+
|
|
59
|
+
## Python
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
import scbamqc as sbq
|
|
63
|
+
from scbamqc.io import read_10x_h5
|
|
64
|
+
|
|
65
|
+
adata = read_10x_h5("raw_feature_bc_matrix.h5")
|
|
66
|
+
adata = sbq.add_bam_qc(adata, "S1_per_cell.csv.gz")
|
|
67
|
+
adata = sbq.basic_qc(adata)
|
|
68
|
+
sbq.qc_score(adata, min_reads=150)
|
|
69
|
+
sbq.select_cells(adata)
|
|
70
|
+
|
|
71
|
+
sbq.pl.qc_score_scatter(adata, min_score=0, min_counts=100)
|
|
72
|
+
sbq.pl.qc_score_hist(adata, threshold=0.9)
|
|
73
|
+
|
|
74
|
+
cells = adata[adata.obs["selected"] & (adata.obs["qc_score"] > 0.9)].copy()
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
| Step | Result |
|
|
78
|
+
|---|---|
|
|
79
|
+
| `add_bam_qc` | Keeps the barcodes in the BAM table. Adds the table columns to `obs`. |
|
|
80
|
+
| `basic_qc` | Removes barcodes with fewer than 5 genes and genes in fewer than 5 barcodes. Adds `total_counts`. |
|
|
81
|
+
| `qc_score` | Adds `obs["qc_score"]`, from 0 to 1. |
|
|
82
|
+
| `select_cells` | Adds `obs["gmm_label"]` and `obs["selected"]`. |
|
|
83
|
+
|
|
84
|
+
### The quality score
|
|
85
|
+
|
|
86
|
+
1. A barcode gets a score only if `n_reads > min_reads` and `mean_nm < 1`.
|
|
87
|
+
Other barcodes get `NaN`.
|
|
88
|
+
2. A kernel density is fitted to three values of these barcodes:
|
|
89
|
+
`mean_clip_frac`, `std_clip_frac` and `mean_base_qual` divided by its
|
|
90
|
+
median.
|
|
91
|
+
3. The score is `log10(density + 1)`, scaled from 0 to 1.
|
|
92
|
+
|
|
93
|
+
A high score shows that the barcode aligns like most other barcodes.
|
|
94
|
+
|
|
95
|
+
Set `min_reads` for each library. The paper used values from 50 to 3,000.
|
|
96
|
+
|
|
97
|
+
`base_qual_scale` sets the divisor of `mean_base_qual`. The default is the
|
|
98
|
+
median of the scored barcodes. The divisor does not change the order of the
|
|
99
|
+
barcodes. It can change the score values only when the densities are near 1.
|
|
100
|
+
|
|
101
|
+
To use the same settings as the TgRN paper:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
sbq.qc_score(adata, min_reads=150, base_qual_scale=sbq.PAPER_BASE_QUAL_SCALE)
|
|
105
|
+
sbq.select_cells(adata, tol=sbq.PAPER_GMM_TOL)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Cell selection
|
|
109
|
+
|
|
110
|
+
`select_cells` fits a Gaussian mixture with two components to
|
|
111
|
+
`log10(total_counts)` and `qc_score`. A barcode is selected if:
|
|
112
|
+
|
|
113
|
+
- it is in the component with the higher counts,
|
|
114
|
+
- `total_counts > min_counts` (default 100),
|
|
115
|
+
- `qc_score > min_score` (default 0).
|
|
116
|
+
|
|
117
|
+
The mixture fit runs until it converges. The paper stopped the fit early
|
|
118
|
+
(`tol=PAPER_GMM_TOL`). On the paper sample, the converged fit selects 15,536
|
|
119
|
+
cells and the paper fit selects 16,120. All cells of the converged fit are in
|
|
120
|
+
the paper set.
|
|
121
|
+
|
|
122
|
+
## R
|
|
123
|
+
|
|
124
|
+
The R package in [`r/`](r/README.md) does the same steps for Seurat objects.
|
|
125
|
+
It reads the table that `scbamqc bam` writes.
|
|
126
|
+
|
|
127
|
+
```r
|
|
128
|
+
remotes::install_github("umbibio/scbamqc", subdir = "r")
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Development
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
uv sync
|
|
135
|
+
uv run pytest
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The tests in `tests/test_paper.py` compare the results with the paper. They
|
|
139
|
+
need the data in `../../data`, or in the folder set by `SCBAMQC_TEST_DATA`.
|
|
140
|
+
Without the data, pytest skips them.
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
BSD 3-Clause.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "scbamqc"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Per-barcode BAM alignment QC and alignment-based cell filtering for single-cell RNA-seq"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { file = "LICENSE" }
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Argenis Arriojas", email = "ArriojasMaldonado001@umb.edu" },
|
|
9
|
+
]
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
classifiers = [
|
|
12
|
+
"License :: OSI Approved :: BSD License",
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Programming Language :: Python :: 3.11",
|
|
15
|
+
"Programming Language :: Python :: 3.12",
|
|
16
|
+
"Programming Language :: Python :: 3.13",
|
|
17
|
+
"Programming Language :: Python :: 3.14",
|
|
18
|
+
"Topic :: Scientific/Engineering :: Bio-Informatics",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"cyclopts>=3",
|
|
22
|
+
"numpy",
|
|
23
|
+
"pandas",
|
|
24
|
+
"rich",
|
|
25
|
+
"scikit-learn",
|
|
26
|
+
"scipy",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
anndata = ["anndata>=0.10", "h5py"]
|
|
31
|
+
bam = ["pysam>=0.22"]
|
|
32
|
+
plot = ["matplotlib"]
|
|
33
|
+
all = ["scbamqc[anndata,bam,plot]"]
|
|
34
|
+
test = ["pytest>=8.3.4"]
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
scbamqc = "scbamqc.cli:main"
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
"Homepage" = "https://github.com/umbibio/scbamqc"
|
|
41
|
+
"Repository" = "https://github.com/umbibio/scbamqc.git"
|
|
42
|
+
"Bug Tracker" = "https://github.com/umbibio/scbamqc/issues"
|
|
43
|
+
"Changelog" = "https://github.com/umbibio/scbamqc/blob/main/CHANGELOG.md"
|
|
44
|
+
|
|
45
|
+
[build-system]
|
|
46
|
+
requires = ["flit_core>=3.2,<4"]
|
|
47
|
+
build-backend = "flit_core.buildapi"
|
|
48
|
+
|
|
49
|
+
[tool.flit.module]
|
|
50
|
+
name = "scbamqc"
|
|
51
|
+
|
|
52
|
+
[tool.flit.sdist]
|
|
53
|
+
exclude = ["r/"]
|
|
54
|
+
|
|
55
|
+
[dependency-groups]
|
|
56
|
+
dev = [
|
|
57
|
+
"bump-my-version>=0.31.1",
|
|
58
|
+
"pre-commit>=4.1.0",
|
|
59
|
+
"ruff>=0.9.4",
|
|
60
|
+
"scbamqc[test,all]",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[tool.pytest.ini_options]
|
|
64
|
+
testpaths = ["tests"]
|
|
65
|
+
markers = [
|
|
66
|
+
"paper: regression tests against the paper's local data (skipped when absent)",
|
|
67
|
+
]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Per-barcode BAM alignment QC and alignment-based cell filtering."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
|
|
5
|
+
from . import pl, qc
|
|
6
|
+
from .qc import (
|
|
7
|
+
DEFAULT_FEATURES,
|
|
8
|
+
PAPER_BASE_QUAL_SCALE,
|
|
9
|
+
PAPER_GMM_TOL,
|
|
10
|
+
add_bam_qc,
|
|
11
|
+
basic_qc,
|
|
12
|
+
gmm_labels,
|
|
13
|
+
qc_score,
|
|
14
|
+
read_bam_qc,
|
|
15
|
+
select_cells,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"DEFAULT_FEATURES",
|
|
20
|
+
"PAPER_BASE_QUAL_SCALE",
|
|
21
|
+
"PAPER_GMM_TOL",
|
|
22
|
+
"add_bam_qc",
|
|
23
|
+
"basic_qc",
|
|
24
|
+
"gmm_labels",
|
|
25
|
+
"pl",
|
|
26
|
+
"qc",
|
|
27
|
+
"qc_score",
|
|
28
|
+
"read_bam_qc",
|
|
29
|
+
"select_cells",
|
|
30
|
+
]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Terminal summary of a BAM-QC run (``rich``)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.panel import Panel
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
from .bam import NO_BARCODE, CellStats, StatsKey
|
|
11
|
+
from .pl import _id_label
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _pct(part: int, total: int) -> str:
|
|
15
|
+
return f"{100 * part / total:.1f}%" if total else "n/a"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def print_summary(
|
|
19
|
+
console: Console,
|
|
20
|
+
df: pd.DataFrame,
|
|
21
|
+
stats: dict[StatsKey, CellStats],
|
|
22
|
+
barcode_tag: str = "CB",
|
|
23
|
+
group_by: str = "cell",
|
|
24
|
+
) -> None:
|
|
25
|
+
total_reads = sum(cs.n_reads for cs in stats.values())
|
|
26
|
+
no_cb_reads = sum(cs.n_reads for (cb, _), cs in stats.items() if cb == NO_BARCODE)
|
|
27
|
+
cb_reads = total_reads - no_cb_reads
|
|
28
|
+
|
|
29
|
+
hdr = Table.grid(padding=(0, 2))
|
|
30
|
+
hdr.add_column(style="bold cyan")
|
|
31
|
+
hdr.add_column()
|
|
32
|
+
hdr.add_row("total mapped reads", f"{total_reads:>14,}")
|
|
33
|
+
hdr.add_row(
|
|
34
|
+
f"reads with {barcode_tag} tag",
|
|
35
|
+
f"{cb_reads:>14,} ({_pct(cb_reads, total_reads)})",
|
|
36
|
+
)
|
|
37
|
+
hdr.add_row(
|
|
38
|
+
f"reads without {barcode_tag} tag",
|
|
39
|
+
f"{no_cb_reads:>14,} ({_pct(no_cb_reads, total_reads)})",
|
|
40
|
+
)
|
|
41
|
+
hdr.add_row(f"unique {_id_label(barcode_tag)}", f"{df['cb'].nunique():>14,}")
|
|
42
|
+
if group_by == "cell-contig":
|
|
43
|
+
hdr.add_row("contigs with reads", f"{df['contig'].nunique():>14,}")
|
|
44
|
+
hdr.add_row("rows (cell × contig)", f"{len(df):>14,}")
|
|
45
|
+
else:
|
|
46
|
+
hdr.add_row("rows (per cell)", f"{len(df):>14,}")
|
|
47
|
+
|
|
48
|
+
console.print(
|
|
49
|
+
Panel(
|
|
50
|
+
hdr,
|
|
51
|
+
title="[bold]BAM QC summary[/bold]",
|
|
52
|
+
border_style="cyan",
|
|
53
|
+
padding=(1, 2),
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
if df.empty:
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
def _metric_table(title: str, col: str, digits: int) -> Table:
|
|
60
|
+
t = Table(
|
|
61
|
+
title=title,
|
|
62
|
+
title_style="bold",
|
|
63
|
+
header_style="bold cyan",
|
|
64
|
+
border_style="dim",
|
|
65
|
+
padding=(0, 1),
|
|
66
|
+
)
|
|
67
|
+
t.add_column("metric", style="cyan", no_wrap=True)
|
|
68
|
+
t.add_column("value", justify="right")
|
|
69
|
+
for k, v in df[col].describe().round(digits).items():
|
|
70
|
+
t.add_row(str(k), str(v))
|
|
71
|
+
return t
|
|
72
|
+
|
|
73
|
+
console.print()
|
|
74
|
+
console.print(
|
|
75
|
+
_metric_table("MAPQ (per-cell means)", "mean_mapq", 2),
|
|
76
|
+
_metric_table("Base quality (per-cell means)", "mean_base_qual", 2),
|
|
77
|
+
_metric_table("Clip fraction (per-cell means)", "mean_clip_frac", 4),
|
|
78
|
+
)
|
|
79
|
+
console.print()
|