diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..5528869 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,37 @@ +name: docs + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + build-deploy: + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # hatch-vcs needs full history for the version + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - run: pip install -e ".[docs]" + - run: mkdocs build + - uses: actions/configure-pages@v5 + - uses: actions/upload-pages-artifact@v3 + with: + path: site + - id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 674b9f2..13b8206 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,8 @@ data/*html !data/ref_data/Gibbs_motifs_mouse/motif/*.png output/* .DS_Store +# MkDocs build output +/site/ cli/*__pycache__ test_output poetry.lock diff --git a/README.md b/README.md index 43d329e..ced044d 100644 --- a/README.md +++ b/README.md @@ -110,3 +110,30 @@ src/mhc_tp/ tui/ Rich console banner, logging, results table tests/ pytest suite ``` + +--- + +## Citation + +If you use MHC-TP in your work, please cite: + +> Munday PR, Krishna SSG, Fehring J, Croft NP, Purcell AW, Li C, Braun A. +> Immunolyser 2.0: An advanced computational pipeline for comprehensive analysis +> of immunopeptidomic data. *Comput Struct Biotechnol J.* 2025;29:296–304. +> doi:[10.1016/j.csbj.2025.10.007](https://doi.org/10.1016/j.csbj.2025.10.007). +> PMID: [41209766](https://pubmed.ncbi.nlm.nih.gov/41209766/); PMCID: PMC12590289. + +```bibtex +@article{Munday2025Immunolyser2, + title = {Immunolyser 2.0: An advanced computational pipeline for comprehensive analysis of immunopeptidomic data}, + author = {Munday, Prithvi Raj and Krishna, Sanjay S. G. and Fehring, Joshua and Croft, Nathan P. and Purcell, Anthony W. and Li, Chen and Braun, Asolina}, + journal = {Computational and Structural Biotechnology Journal}, + volume = {29}, + pages = {296--304}, + year = {2025}, + doi = {10.1016/j.csbj.2025.10.007}, + pmid = {41209766}, + pmcid = {PMC12590289} +} +``` + diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..cdebec3 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,19 @@ +# API reference + +Auto-generated from the package docstrings. + +## Search engine + +::: mhc_tp.engine.search + +::: mhc_tp.engine.kernels + +## Reference data + +::: mhc_tp.refdata.fetch + +## Report + +::: mhc_tp.report.render + +::: mhc_tp.naming diff --git a/docs/example-report.html b/docs/example-report.html new file mode 100644 index 0000000..3cadad6 --- /dev/null +++ b/docs/example-report.html @@ -0,0 +1,2710 @@ + + + + + + +MHC-TP 2.0.3 + + + + + + + + + + +
+ + +
+
+

MHC-TP

+ MHC-TP v2.0.3 +
+

Peptide cluster to allotype motif correlation report.

+
+ + +
+

Gibbs Cluster and Reference Motif ComparisonsiEach GibbsCluster motif (left) is paired with its best-matching reference allotype motif (right), ranked by Pearson correlation (PCC). Pick a cluster solution from the dropdown, then use the arrows or dots to step through its clusters. Allele names match the reference logo titles.

+

+ Each cluster solution's GibbsCluster motifs paired with the best-matching + reference allotype motif. Use the arrows to step through clusters. +

+ +
+ + +
+ +
+
+

+ Results for 1 cluster + · 1 motif +

+ +
+ + + + +
+ +
+
+

+ Results for 2 clusters + · 2 motifs +

+ +
+ + + + +
+ + + + + +
+ +
+ +
+
+

+ Results for 3 clusters + · 3 motifs +

+ +
+ + + + +
+ + + + + + + +
+ +
+ +
+
+

+ Results for 4 clusters + · 4 motifs +

+ +
+ + + + +
+ + + + + + + + + +
+ +
+ +
+
+

+ Results for 5 clusters + · 4 motifs +

+ +
+ + + + +
+ + + + + + + + + +
+ +
+ +
+
+

+ Results for 6 clusters + · 5 motifs +

+ +
+ + + + +
+ + + + + + + + + + + +
+ +
+ + +
+ + +
+

Top 3 Allotype Matches per ClusteriFor each GibbsCluster motif, the top 3 reference allotypes scoring above the PCC threshold, all interleaved and ranked best first. PCC is the Pearson correlation between the cluster and reference position-weight matrices (1.0 = identical motif). KLD is the GibbsCluster Kullback–Leibler divergence (motif information content). Search by cluster id or allotype.

+

+ Each cluster's 3 best-matching allotypes above the PCC threshold, ranked overall. +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#ClusterBest HLA/MHCPCCKLD
12of5HLA-B39:124 + 0.893 + 1.842
22of5HLA-B3942 + 0.890 + 1.842
32of5HLA-B39:82 + 0.889 + 1.842
44of6HLA-B39:124 + 0.888 + 1.372
54of6HLA-B39:52 + 0.883 + 1.372
64of6HLA-B3942 + 0.882 + 1.372
72of4HLA-B39:124 + 0.876 + 1.960
82of4HLA-B39:47 + 0.872 + 1.960
92of4HLA-B39:52 + 0.871 + 1.960
101of3HLA-B39:92 + 0.871 + 2.366
111of3HLA-B39:66 + 0.867 + 2.366
122of3HLA-B5309 + 0.867 + 3.771
131of3HLA-B39:69 + 0.863 + 2.366
142of3HLA-B53:40 + 0.859 + 3.771
152of3HLA-B1513 + 0.852 + 3.771
163of4HLA-B44:257 + 0.842 + 1.893
173of4HLA-B44:103 + 0.838 + 1.893
183of4HLA-B44:188 + 0.838 + 1.893
191of6HLA-B44:65 + 0.838 + 1.704
201of6HLA-B44:332 + 0.837 + 1.704
213of5HLA-B44:332 + 0.837 + 1.767
223of5HLA-B44:65 + 0.836 + 1.767
231of6HLA-B44:285 + 0.835 + 1.704
243of5HLA-B44:300 + 0.835 + 1.767
251of5HLA-A68:45 + 0.827 + 1.764
261of5HLA-A68:117 + 0.827 + 1.764
271of5HLA-A6829 + 0.815 + 1.764
284of4HLA-B15:418 + 0.806 + 2.540
294of4HLA-B1513 + 0.806 + 2.540
304of4HLA-B1589 + 0.801 + 2.540
315of6HLA-A25:32 + 0.792 + 1.944
325of6HLA-A25:10 + 0.792 + 1.944
335of6HLA-A25:13 + 0.792 + 1.944
346of6HLA-A68:165 + 0.786 + 1.469
356of6HLA-A68:85 + 0.784 + 1.469
366of6HLA-A68:83 + 0.783 + 1.469
372of2HLA-B1589 + 0.777 + 4.446
385of5HLA-A25:07 + 0.776 + 1.996
395of5HLA-A25:48 + 0.776 + 1.996
405of5HLA-A25:47 + 0.776 + 1.996
412of2HLA-B5309 + 0.775 + 4.446
422of2HLA-B15:418 + 0.773 + 4.446
431of2HLA-B3919 + 0.773 + 4.108
441of2HLA-B39:121 + 0.768 + 4.108
451of2HLA-B39:69 + 0.759 + 4.108
461of4HLA-A6807 + 0.747 + 2.447
471of1HLA-A25:08 + 0.745 + 7.577
481of1HLA-A25:09 + 0.745 + 7.577
491of1HLA-A25:10 + 0.745 + 7.577
503of6HLA-C12:183 + 0.745 + 1.015
513of6HLA-C12:90 + 0.736 + 1.015
523of6HLA-C12:213 + 0.731 + 1.015
533of3HLA-A6807 + 0.728 + 2.738
541of4HLA-A68:105 + 0.726 + 2.447
551of4HLA-A6806 + 0.725 + 2.447
563of3HLA-A68:88 + 0.715 + 2.738
573of3HLA-A6817 + 0.712 + 2.738
+
+
+
+ + +
+

Correlation AnalysisiInteractive views of every cluster↔allotype correlation. Drag the PCC threshold to hide weaker matches. The Heatmap colours each pair by PCC; the Network links clusters to their correlated allotypes. The Combined tab stacks both.

+ +
+ + + 0.70 +
+ +
+ + + + +
+ + + +
+ +
+
+
+
HeatmapiColour encodes the PCC (0–1) for each cluster × allotype pair above the threshold. Darker = stronger match. Hover a cell for exact values.
+
+
+
+
NetworkiForce-directed graph: each cluster (large coloured node) links to the allotypes it correlates with; edge width scales with PCC. Drag nodes, scroll to zoom, hover for details.
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+ + +
+ + + + \ No newline at end of file diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index bbc817c..0000000 --- a/docs/index.html +++ /dev/null @@ -1,1818 +0,0 @@ - - - - - - -MHC-TP 2.0.0 - - - - - -
- - -
-
-

MHC-TP

- MHC-TP v2.0.0 -
-

Peptide cluster to allotype motif correlation report.

-
- - -
-

Results

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#ClusterBest HLA/MHCPCCKLD
12of5HLA_B39124 - 0.893 - 1.842
22of5HLA_B3942 - 0.890 - 1.842
32of5HLA_B3982 - 0.889 - 1.842
44of6HLA_B39124 - 0.888 - 1.372
54of6HLA_B3952 - 0.883 - 1.372
64of6HLA_B3942 - 0.882 - 1.372
72of4HLA_B39124 - 0.876 - 1.960
82of4HLA_B3947 - 0.872 - 1.960
92of4HLA_B3952 - 0.871 - 1.960
101of3HLA_B3992 - 0.871 - 2.366
111of3HLA_B3966 - 0.867 - 2.366
122of3HLA_B5309 - 0.867 - 3.771
131of3HLA_B3969 - 0.863 - 2.366
142of3HLA_B5340 - 0.859 - 3.771
152of3HLA_B1513 - 0.852 - 3.771
163of4HLA_B44257 - 0.842 - 1.893
173of4HLA_B44103 - 0.838 - 1.893
183of4HLA_B44188 - 0.838 - 1.893
191of6HLA_B4465 - 0.838 - 1.704
201of6HLA_B44332 - 0.837 - 1.704
213of5HLA_B44332 - 0.837 - 1.767
223of5HLA_B4465 - 0.836 - 1.767
231of6HLA_B44285 - 0.835 - 1.704
243of5HLA_B44300 - 0.835 - 1.767
251of5HLA_A6845 - 0.827 - 1.764
261of5HLA_A68117 - 0.827 - 1.764
271of5HLA_A6829 - 0.815 - 1.764
284of4HLA_B15418 - 0.806 - 2.540
294of4HLA_B1513 - 0.806 - 2.540
304of4HLA_B1589 - 0.801 - 2.540
315of6HLA_A2532 - 0.792 - 1.944
325of6HLA_A2510 - 0.792 - 1.944
335of6HLA_A2513 - 0.792 - 1.944
346of6HLA_A68165 - 0.786 - 1.469
356of6HLA_A6885 - 0.784 - 1.469
366of6HLA_A6883 - 0.783 - 1.469
372of2HLA_B1589 - 0.777 - 4.446
385of5HLA_A2507 - 0.776 - 1.996
395of5HLA_A2548 - 0.776 - 1.996
405of5HLA_A2547 - 0.776 - 1.996
412of2HLA_B5309 - 0.775 - 4.446
422of2HLA_B15418 - 0.773 - 4.446
431of2HLA_B3919 - 0.773 - 4.108
441of2HLA_B39121 - 0.768 - 4.108
451of2HLA_B3969 - 0.759 - 4.108
461of4HLA_A6807 - 0.747 - 2.447
471of1HLA_A2508 - 0.745 - 7.577
481of1HLA_A2509 - 0.745 - 7.577
491of1HLA_A2510 - 0.745 - 7.577
503of6HLA_C12183 - 0.745 - 1.015
513of6HLA_C1290 - 0.736 - 1.015
523of6HLA_C12213 - 0.731 - 1.015
533of3HLA_A6807 - 0.728 - 2.738
541of4HLA_A68105 - 0.726 - 2.447
551of4HLA_A6806 - 0.725 - 2.447
563of3HLA_A6888 - 0.715 - 2.738
573of3HLA_A6817 - 0.712 - 2.738
-
-
- - -
-

Correlation Analysis

- -
- - - 0.70 -
- -
- - - - -
- - - -
- -
-
-
-
Heatmap
-
-
-
-
Network
-
-
-
-
- -
-
-
-
-
- -
-
-
-
-
-
-
- - - -
-

Results for 1 clusters

-
- -
-
- Best matched allotype is HLA_A2508 - · PCC 0.74 - · KLD 7.58 -
-
-
-
Gibbs Cluster
- cluster logo -
-
-
Reference Motif logo of HLA_A2508
- reference logo -
-
-
- -
-
- -
-

Results for 2 clusters

-
- -
-
- Best matched allotype is HLA_B3919 - · PCC 0.77 - · KLD 4.11 -
-
-
-
Gibbs Cluster
- cluster logo -
-
-
Reference Motif logo of HLA_B3919
- reference logo -
-
-
- -
-
- Best matched allotype is HLA_B1589 - · PCC 0.78 - · KLD 4.45 -
-
-
-
Gibbs Cluster
- cluster logo -
-
-
Reference Motif logo of HLA_B1589
- reference logo -
-
-
- -
-
- -
-

Results for 3 clusters

-
- -
-
- Best matched allotype is HLA_B3992 - · PCC 0.87 - · KLD 2.37 -
-
-
-
Gibbs Cluster
- cluster logo -
-
-
Reference Motif logo of HLA_B3992
- reference logo -
-
-
- -
-
- Best matched allotype is HLA_B5309 - · PCC 0.87 - · KLD 3.77 -
-
-
-
Gibbs Cluster
- cluster logo -
-
-
Reference Motif logo of HLA_B5309
- reference logo -
-
-
- -
-
- Best matched allotype is HLA_A6807 - · PCC 0.73 - · KLD 2.74 -
-
-
-
Gibbs Cluster
- cluster logo -
-
-
Reference Motif logo of HLA_A6807
- reference logo -
-
-
- -
-
- -
-

Results for 4 clusters

-
- -
-
- Best matched allotype is HLA_A6807 - · PCC 0.75 - · KLD 2.45 -
-
-
-
Gibbs Cluster
- cluster logo -
-
-
Reference Motif logo of HLA_A6807
- reference logo -
-
-
- -
-
- Best matched allotype is HLA_B39124 - · PCC 0.88 - · KLD 1.96 -
-
-
-
Gibbs Cluster
- cluster logo -
-
-
Reference Motif logo of HLA_B39124
- reference logo -
-
-
- -
-
- Best matched allotype is HLA_B44257 - · PCC 0.84 - · KLD 1.89 -
-
-
-
Gibbs Cluster
- cluster logo -
-
-
Reference Motif logo of HLA_B44257
- reference logo -
-
-
- -
-
- Best matched allotype is HLA_B15418 - · PCC 0.81 - · KLD 2.54 -
-
-
-
Gibbs Cluster
- cluster logo -
-
-
Reference Motif logo of HLA_B15418
- reference logo -
-
-
- -
-
- -
-

Results for 5 clusters

-
- -
-
- Best matched allotype is HLA_A6845 - · PCC 0.83 - · KLD 1.76 -
-
-
-
Gibbs Cluster
- cluster logo -
-
-
Reference Motif logo of HLA_A6845
- reference logo -
-
-
- -
-
- Best matched allotype is HLA_B39124 - · PCC 0.89 - · KLD 1.84 -
-
-
-
Gibbs Cluster
- cluster logo -
-
-
Reference Motif logo of HLA_B39124
- reference logo -
-
-
- -
-
- Best matched allotype is HLA_B44332 - · PCC 0.84 - · KLD 1.77 -
-
-
-
Gibbs Cluster
- cluster logo -
-
-
Reference Motif logo of HLA_B44332
- reference logo -
-
-
- -
-
- Best matched allotype is HLA_A2507 - · PCC 0.78 - · KLD 2.00 -
-
-
-
Gibbs Cluster
- cluster logo -
-
-
Reference Motif logo of HLA_A2507
- reference logo -
-
-
- -
-
- -
-

Results for 6 clusters

-
- -
-
- Best matched allotype is HLA_B4465 - · PCC 0.84 - · KLD 1.70 -
-
-
-
Gibbs Cluster
- cluster logo -
-
-
Reference Motif logo of HLA_B4465
- reference logo -
-
-
- -
-
- Best matched allotype is HLA_C12183 - · PCC 0.74 - · KLD 1.02 -
-
-
-
Gibbs Cluster
- cluster logo -
-
-
Reference Motif logo of HLA_C12183
- reference logo -
-
-
- -
-
- Best matched allotype is HLA_B39124 - · PCC 0.89 - · KLD 1.37 -
-
-
-
Gibbs Cluster
- cluster logo -
-
-
Reference Motif logo of HLA_B39124
- reference logo -
-
-
- -
-
- Best matched allotype is HLA_A2532 - · PCC 0.79 - · KLD 1.94 -
-
-
-
Gibbs Cluster
- cluster logo -
-
-
Reference Motif logo of HLA_A2532
- reference logo -
-
-
- -
-
- Best matched allotype is HLA_A68165 - · PCC 0.79 - · KLD 1.47 -
-
-
-
Gibbs Cluster
- cluster logo -
-
-
Reference Motif logo of HLA_A68165
- reference logo -
-
-
- -
-
- - - -
- - - - \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..a19115b --- /dev/null +++ b/docs/index.md @@ -0,0 +1,47 @@ +# MHC-TP + +Cluster immunopeptidomics peptides by their **HLA/MHC binding motif** and get a +ranked table plus a standalone interactive HTML report. + +`mhc-tp` takes a **GibbsCluster** output folder, correlates each cluster's +position-specific scoring matrix against a reference of HLA/MHC **class I + II** +binding motifs (human & mouse), and writes the best allele match per cluster. + +[Get started](usage.md){ .md-button .md-button--primary } +[View an example report](example-report.html){ .md-button } + +## Install + +```bash +git clone https://github.com/PurcellLab/MHC-TP.git +cd MHC-TP +pip install -e . +``` + +Then fetch the reference motifs (once) and run a search: + +```bash +mhc-tp fetch -s human +mhc-tp search -s human -o results/ +``` + +See the [Usage](usage.md) guide for all options and the +[API reference](api.md) for the search engine internals (including the +correlation maths). + +## How it works + +For each GibbsCluster motif, every reference allotype motif is scored by the +**Pearson correlation** of their flattened position-weight matrices, computed +only over the informative cells of the cluster motif. Per cluster the allotypes +are ranked by this correlation (PCC); see +[`mhc_tp.engine.search`](api.md#mhc_tp.engine.search) for the full method. + +## Citation + +If you use MHC-TP, please cite: + +> Munday PR, Krishna SSG, Fehring J, Croft NP, Purcell AW, Li C, Braun A. +> *Immunolyser 2.0: An advanced computational pipeline for comprehensive +> analysis of immunopeptidomic data.* Comput Struct Biotechnol J. 2025;29:296–304. +> doi:[10.1016/j.csbj.2025.10.007](https://doi.org/10.1016/j.csbj.2025.10.007). diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 0000000..f2fa844 --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,78 @@ +# Usage + +## Install + +**Requirements:** Python 3.9–3.11. + +```bash +git clone https://github.com/PurcellLab/MHC-TP.git +cd MHC-TP +pip install -e . +``` + +> One-liner without cloning: `pip install git+https://github.com/PurcellLab/MHC-TP.git` +> A virtual environment is recommended. + +## Download the reference data (once) + +The reference motifs are fetched from the GitHub release, not bundled: + +```bash +mhc-tp fetch -s human # or: mouse | all +``` + +## Run a search + +```bash +mhc-tp search -s human -o results/ +``` + +`` is a GibbsCluster run folder (it must contain a +`matrices/` subdirectory). + +**Outputs** land in `results/clust_result/`: + +| file | what it is | +|------|------------| +| `correlations.csv` | every cluster→allele match (`hla` = display name, `formatted` = raw key, `correlation` = PCC) | +| `mhc-tp-result.html` | standalone interactive report — open it in any browser | + +## Options + +| flag | meaning | default | +|------|---------|---------| +| `-s, --species` | `human` or `mouse` | `human` | +| `-c, --class` | restrict the reference to MHC class `I`, `II`, or `all` | `all` | +| `-r, --reference` | path to a `.parquet` (otherwise the fetched one is used) | auto | +| `-t, --threshold` | minimum Pearson correlation to report | `0.70` | +| `--topNHits` | number of allotype matches to keep per cluster | `3` | +| `--always-top-n` | keep each cluster's top-N even if below `--threshold` (flagged in the report) | off | +| `-o, --output` | output directory | `output` | +| `--threads` | max CPU threads (also `$MHC_TP_THREADS`) | `4` | +| `--no-html` | write only the CSV | off | +| `-l, --log` | also save the coloured session log | off | + +Run `mhc-tp search --help` for the full list. + +### Examples + +Restrict to MHC class I and keep the top 5 per cluster: + +```bash +mhc-tp search runs/sampleA -s human -c I --topNHits 5 -o results/ +``` + +Guarantee a top-3 for **every** cluster, even weak ones (sub-threshold matches +are tagged *below cutoff* in the report): + +```bash +mhc-tp search runs/sampleA -s human --always-top-n -o results/ +``` + +## How matches are selected + +Per cluster, all reference allotypes are ranked by PCC. By default a hit must +score `>= --threshold`, so a cluster can yield fewer than `--topNHits` rows (or +none). With `--always-top-n`, every cluster returns its best `--topNHits` +regardless of threshold; the threshold then only annotates confidence. See the +[API reference](api.md#mhc_tp.engine.search) for the maths. diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..3c98bb5 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,60 @@ +site_name: MHC-TP +site_description: Cluster immunopeptidomics peptides by HLA/MHC binding motif, with an interactive HTML report +site_url: https://purcelllab.github.io/MHC-TP/ +repo_url: https://github.com/PurcellLab/MHC-TP +repo_name: PurcellLab/MHC-TP +edit_uri: "" +docs_dir: docs + +theme: + name: material + palette: + - scheme: default + primary: teal + accent: teal + toggle: + icon: material/weather-night + name: Switch to dark mode + - scheme: slate + primary: teal + accent: teal + toggle: + icon: material/weather-sunny + name: Switch to light mode + features: + - navigation.sections + - navigation.top + - navigation.tracking + - content.code.copy + - search.suggest + - toc.follow + +nav: + - Home: index.md + - Usage: usage.md + - API reference: api.md + - Example report: example-report.html + +plugins: + - search + - mkdocstrings: + handlers: + python: + paths: [src] + options: + show_root_heading: true + show_source: true + members_order: source + separate_signature: true + docstring_section_style: list + +markdown_extensions: + - admonition + - attr_list + - tables + - toc: + permalink: true + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.superfences + - pymdownx.inlinehilite diff --git a/pyproject.toml b/pyproject.toml index 93dff97..250f0e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,6 +52,12 @@ dependencies = [ [project.optional-dependencies] dev = ["pytest>=8", "ruff>=0.6", "mypy>=1.10"] +docs = [ + "mkdocs>=1.6", + "mkdocs-material>=9.5", + "mkdocstrings[python]>=0.26", + "ruff>=0.6", # mkdocstrings uses it to format rendered signatures +] [project.scripts] mhc-tp = "mhc_tp.cli:main" diff --git a/src/mhc_tp/cli.py b/src/mhc_tp/cli.py index 1dc3672..da56d58 100644 --- a/src/mhc_tp/cli.py +++ b/src/mhc_tp/cli.py @@ -55,6 +55,8 @@ def run_search( log_level="info", log_to_file=False, threads=None, + always_top_n=False, + mhc_class="all", ): """Run the search; write correlations.csv and (default) the HTML report.""" # Lazy import: pulls numba (~1.4s) only when a search actually runs, so @@ -71,10 +73,16 @@ def run_search( log.info("[bold]Stage 1/4[/bold] loading reference …") # Load matrices + metadata only (skip the heavy logo blob column) for the search. ref = read_reference(reference, columns=COLUMNS) + if mhc_class and mhc_class != "all": + ref = ref[ref["mhc_class"] == mhc_class].reset_index(drop=True) + if ref.empty: + raise SystemExit(f"no class {mhc_class} allotypes in {reference}") + class_label = {"I": "class I", "II": "class II"}.get(mhc_class, "class I+II") log.info( - "Reference: [bold]%d[/bold] %s allotypes (class I+II) from %s", + "Reference: [bold]%d[/bold] %s %s allotypes from %s", len(ref), species, + class_label, reference, ) @@ -85,8 +93,18 @@ def run_search( log.info( "[bold]Stage 3/4[/bold] numba correlation search (threshold %.2f) …", threshold ) - cd = search(ref, gibbs, threshold=threshold, top_n=top_n, hla_filter=hla_filter) - log.info("[green]Found %d matches above threshold[/green]", len(cd)) + cd = search( + ref, + gibbs, + threshold=threshold, + top_n=top_n, + hla_filter=hla_filter, + always_top_n=always_top_n, + ) + if always_top_n: + log.info("[green]Found %d matches (top-%d per cluster)[/green]", len(cd), top_n) + else: + log.info("[green]Found %d matches above threshold[/green]", len(cd)) # Map the raw `formatted` key to the Immunolyser display name; keep both so # the CSV stays joinable on `formatted` while showing pretty `hla`. @@ -135,6 +153,9 @@ def run_search( gibbs_dir=gibbs_dir, logo_map=logo_map, name_map=name_map, + top_n=top_n, + threshold=threshold, + always_top_n=always_top_n, ) log.info("HTML report → %s", out_dir / "mhc-tp-result.html") log.info("[green]Done. Results in %s[/green]", out_dir) @@ -168,8 +189,23 @@ def main(argv=None): help="path to .parquet (default: fetched data dir; see `fetch`)", ) s.add_argument("-s", "--species", default="human") + s.add_argument( + "-c", + "--class", + dest="mhc_class", + default="all", + choices=["I", "II", "all"], + help="restrict the reference to MHC class I, II, or all (default: all)", + ) s.add_argument("-t", "--threshold", type=float, default=0.70) s.add_argument("--topNHits", type=int, default=3) + s.add_argument( + "--always-top-n", + dest="always_top_n", + action="store_true", + help="return each cluster's top-N matches even if some fall below " + "--threshold (threshold then only annotates confidence; no cluster is dropped)", + ) s.add_argument("-o", "--output", default="output") s.add_argument( "--no-html", action="store_true", help="skip the HTML report (CSV only)" @@ -335,6 +371,8 @@ def main(argv=None): log_level=args.log_level, log_to_file=args.log, threads=args.threads, + always_top_n=args.always_top_n, + mhc_class=args.mhc_class, ) return parser.print_help() diff --git a/src/mhc_tp/engine/kernels.py b/src/mhc_tp/engine/kernels.py index 71d35b8..9ffaeef 100644 --- a/src/mhc_tp/engine/kernels.py +++ b/src/mhc_tp/engine/kernels.py @@ -8,11 +8,34 @@ # np.isnan() masking below (NaN cells must be excluded, per the docstring). @jit(nopython=True, parallel=True, cache=True) def compute_all_correlations(gibbs_matrices, ref_matrices, hla_mask, threshold): - """All-pairs flattened Pearson correlation, parallel over Gibbs matrices. + r"""All-pairs flattened Pearson correlation, parallel over Gibbs matrices. - Correlates over the cells that are non-zero and non-NaN in the Gibbs - matrix. Stores a correlation only when it is >= threshold; otherwise the - cell keeps the -1.0 sentinel. Returns (correlations, invalid_flags). + Each PSSM is flattened to a vector. Only the cells that are informative in + the Gibbs matrix are scored: the valid set is + + V = { k : g_k != 0 and g_k is not NaN } + + Restricted to those cells, the score for a (Gibbs g, reference r) pair is + the Pearson correlation coefficient + + (1/|V|) * Σ_{k in V} (g_k - ḡ)(r_k - r̄) + PCC(g,r) = ---------------------------------------- + σ_g · σ_r + + where ḡ, r̄ are the means and σ_g, σ_r the population standard deviations + taken over V: + + ḡ = (1/|V|) Σ g_k , σ_g = sqrt( (1/|V|) Σ (g_k - ḡ)^2 ) + r̄ = (1/|V|) Σ r_k , σ_r = sqrt( (1/|V|) Σ (r_k - r̄)^2 ) + + PCC lies in [-1, 1] (1 = identical motif shape) and is scale/offset + invariant, so it measures the *pattern* of position preferences rather than + absolute weight magnitudes. + + Guards: a Gibbs matrix with |V| < 10 or σ_g = 0 is flagged invalid (its row + is skipped); a reference with σ_r = 0 is skipped for that pair. A score is + stored only when PCC >= ``threshold``; otherwise the cell keeps the -1.0 + sentinel. Returns (correlations, invalid_flags). """ n_gibbs = gibbs_matrices.shape[0] n_refs = ref_matrices.shape[0] diff --git a/src/mhc_tp/engine/search.py b/src/mhc_tp/engine/search.py index 5f54b2f..83e5cf5 100644 --- a/src/mhc_tp/engine/search.py +++ b/src/mhc_tp/engine/search.py @@ -1,4 +1,28 @@ -"""Search Gibbs cluster matrices against the reference array.""" +"""Search Gibbs cluster matrices against the reference array. + +Method (how a cluster is matched to an allotype) +------------------------------------------------ +1. Each GibbsCluster motif and each reference allotype is represented as a + position-specific scoring matrix (PSSM): ``n_positions x 20`` amino-acid + weights. Gibbs matrices are zero-padded to a common position count so they + can be batched. +2. For every (cluster, allotype) pair the two PSSMs are compared by **Pearson + correlation** of their flattened weights, computed only over the cells + ``V = {k : g_k != 0 and g_k not NaN}`` that are informative in the *cluster* + matrix (so padding and empty positions do not dilute the score):: + + PCC(g, r) = Σ_{k∈V}(g_k - ḡ)(r_k - r̄) / ( |V| · σ_g · σ_r ) ∈ [-1, 1] + + with means/std taken over ``V``. It is scale- and offset-invariant, so it + scores motif *shape*, not absolute magnitudes. Full derivation and numerical + guards: :func:`mhc_tp.engine.kernels.compute_all_correlations`. +3. Per cluster the allotypes are ranked by correlation (PCC, ``-1..1``; + ``1.0`` = identical motif). Selection is then either threshold-gated + (default) or pure top-N (``always_top_n``); see :func:`search`. + +The correlation is a *motif-shape* similarity: it rewards matching the relative +preference pattern across positions, not the absolute weight magnitudes. +""" from __future__ import annotations @@ -16,8 +40,16 @@ def search( threshold: float = 0.70, top_n: int = 3, hla_filter: list[str] | None = None, + always_top_n: bool = False, ) -> dict[tuple[str, str], float]: - """Return ``{(gibbs_name, ref_formatted): correlation}`` for top-N hits.""" + """Return ``{(gibbs_name, ref_formatted): correlation}`` for top-N hits. + + By default a hit must score ``>= threshold`` to be returned, so a cluster + may yield fewer than ``top_n`` rows (or none). When ``always_top_n`` is set, + every cluster returns its ``top_n`` best matches regardless of threshold — + the threshold then only annotates confidence downstream, it never drops a + row. + """ ref_arr, max_positions = build_reference_array(reference) names = list(gibbs_matrices.keys()) @@ -30,8 +62,11 @@ def search( if hla_filter: mask = reference["formatted"].isin(hla_filter).to_numpy() + # In always-top-N mode, store every valid correlation (kernel keeps a -1.0 + # sentinel for cells below its threshold), then rank in Python. + kernel_threshold = -2.0 if always_top_n else threshold corr, _invalid = compute_all_correlations( - padded, ref_arr.astype(np.float32), mask, threshold + padded, ref_arr.astype(np.float32), mask, kernel_threshold ) formatted = reference["formatted"].to_numpy() @@ -39,7 +74,11 @@ def search( for i, name in enumerate(names): row = corr[i, :] order = np.argsort(row)[::-1] - hits = [j for j in order if row[j] >= threshold][:top_n] + if always_top_n: + # Top-N among computed (non-sentinel, unmasked) cells, any score. + hits = [j for j in order if mask[j] and row[j] > -1.0][:top_n] + else: + hits = [j for j in order if row[j] >= threshold][:top_n] for j in hits: out[(name, str(formatted[j]))] = float(row[j]) return out diff --git a/src/mhc_tp/naming.py b/src/mhc_tp/naming.py index 97b55a5..68a25bf 100644 --- a/src/mhc_tp/naming.py +++ b/src/mhc_tp/naming.py @@ -1,33 +1,25 @@ -"""Display formatting for allele names (Immunolyser report style).""" +"""Display formatting for allele names. -from __future__ import annotations - -import re +The reference parquet stores each allotype in the very string that was burned +into its Seq2Logo motif title (e.g. ``HLA-A25:08``, ``HLA-A3301``, +``DRB1_0101``, ``H-2-IAb``). To keep the report's text labels consistent with +those embedded reference logo titles, the canonical display name is the source +allotype verbatim. +""" -_MOUSE_PREFIX = re.compile(r"^H-?2[-_]?", re.IGNORECASE) -_HUMAN_PREFIX = re.compile(r"^HLA[-_]?", re.IGNORECASE) -_NON_ALNUM = re.compile(r"[^A-Za-z0-9]") +from __future__ import annotations def pretty_allele(allotype: str | None) -> str: - """Render a source allotype in the Immunolyser display style. - - The species prefix and field separators are normalised while the locus and - allele-field digits are preserved verbatim (no field-split guessing): + """Canonical display name for an allotype, matching its reference logo title. - ``HLA-B39:124`` -> ``HLA_B39124`` - ``HLA-B3942`` -> ``HLA_B3942`` - ``DRB1_0101`` -> ``HLA_DRB10101`` - ``H-2-Kb`` -> ``H2_Kb`` - ``H-2-IAb`` -> ``H2_IAb`` + The source allotype is already the name shown on the embedded Seq2Logo + motif (``HLA-A25:08``, ``HLA-A3301``, ``DRB1_0101`` ...), so it is returned + verbatim apart from surrounding whitespace. - Already-formatted display names are stable (idempotent). + ``HLA-A25:08`` -> ``HLA-A25:08`` + ``HLA-A3301`` -> ``HLA-A3301`` + ``DRB1_0101`` -> ``DRB1_0101`` + ``H-2-IAb`` -> ``H-2-IAb`` """ - n = (allotype or "").strip() - if not n: - return "" - if re.match(r"^H-?2", n, re.IGNORECASE): - core = _MOUSE_PREFIX.sub("", n) - return "H2_" + _NON_ALNUM.sub("", core) - core = _HUMAN_PREFIX.sub("", n) - return "HLA_" + _NON_ALNUM.sub("", core) + return (allotype or "").strip() diff --git a/src/mhc_tp/report/data.py b/src/mhc_tp/report/data.py index 5234dd9..c503e14 100644 --- a/src/mhc_tp/report/data.py +++ b/src/mhc_tp/report/data.py @@ -44,10 +44,13 @@ def datatable_rows( correlation_dict, kld_df: pd.DataFrame | None, name_map: dict[str, str] | None = None, + threshold: float = 0.70, ) -> list[dict]: """Rows for the results DataTable, sorted by correlation desc, with KLD. ``name_map`` ({formatted: display}) supplies pretty allele labels. + ``below`` flags rows whose correlation is under ``threshold`` (only possible + when the search ran in always-top-N mode). """ name_map = name_map or {} rows = [] @@ -59,6 +62,7 @@ def datatable_rows( "cluster": cid, "hla": name_map.get(hla, hla), "correlation": round(float(corr), 4), + "below": float(corr) < threshold, "kld": kld, } ) diff --git a/src/mhc_tp/report/render.py b/src/mhc_tp/report/render.py index 76a63ac..8e52808 100644 --- a/src/mhc_tp/report/render.py +++ b/src/mhc_tp/report/render.py @@ -34,6 +34,9 @@ def render_report( gibbs_dir: str | None = None, logo_map: dict | None = None, name_map: dict | None = None, + top_n: int = 3, + threshold: float = 0.70, + always_top_n: bool = False, ) -> str: """Write /clust_result/mhc-tp-result.html and return its path. @@ -45,7 +48,7 @@ def render_report( name_map = name_map or {} ref_by_fmt = {r.formatted: r for r in reference_df.itertuples()} - table_rows = datatable_rows(correlation_dict, kld_df, name_map) + table_rows = datatable_rows(correlation_dict, kld_df, name_map, threshold) pcc_json = json.dumps(pcc_records(correlation_dict, name_map)) # Best HLA per cluster id, grouped by the number of clusters N, with both @@ -88,6 +91,7 @@ def render_report( "group": group, "hla": hla_display, "correlation": round(float(corr), 3), + "below": float(corr) < threshold, "kld": _kld(kld_df, group, nclust), "ref_logo": ref_logo, "cluster_logo": cluster_logo, @@ -112,6 +116,9 @@ def render_report( pcc_json=pcc_json, table_rows=table_rows, cluster_sections=cluster_sections, + top_n=top_n, + threshold=threshold, + always_top_n=always_top_n, ) out_dir = Path(output_dir) / "clust_result" diff --git a/src/mhc_tp/report/templates/report.html.j2 b/src/mhc_tp/report/templates/report.html.j2 index a80d362..f8a1afd 100644 --- a/src/mhc_tp/report/templates/report.html.j2 +++ b/src/mhc_tp/report/templates/report.html.j2 @@ -5,6 +5,9 @@ MHC-TP {{ version }} + + + + +
@@ -295,9 +612,100 @@

Peptide cluster to allotype motif correlation report.

- -
-

Results

+ +
+

Gibbs Cluster and Reference Motif ComparisonsiEach GibbsCluster motif (left) is paired with its best-matching reference allotype motif (right), ranked by Pearson correlation (PCC). Pick a cluster solution from the dropdown, then use the arrows or dots to step through its clusters. Allele names match the reference logo titles.

+

+ Each cluster solution's GibbsCluster motifs paired with the best-matching + reference allotype motif. Use the arrows to step through clusters. +

+ {% if cluster_sections %} +
+ + +
+ {% for sec in cluster_sections %} +
+
+

+ Results for {{ sec.n_clusters }} cluster{{ 's' if sec.n_clusters != 1 }} + · {{ sec.groups|length }} motif{{ 's' if sec.groups|length != 1 }} +

+ +
+ + + + {% if sec.groups|length > 1 %} +
+ {% for c in sec.groups %} + + {% endfor %} +
+ {% endif %} +
+ {% endfor %} + {% else %} +
No cluster comparisons available.
+ {% endif %} +
+ + +
+

Top {{ top_n }} Allotype Matches per ClusteriFor each GibbsCluster motif, the top {{ top_n }} reference allotypes scoring above the PCC threshold, all interleaved and ranked best first. PCC is the Pearson correlation between the cluster and reference position-weight matrices (1.0 = identical motif). KLD is the GibbsCluster Kullback–Leibler divergence (motif information content). Search by cluster id or allotype.

+

+ {% if always_top_n %}Each cluster's {{ top_n }} best-matching allotypes, ranked overall; matches below the + {{ '%.2f'|format(threshold) }} PCC threshold are tagged below cutoff. + {% else %}Each cluster's {{ top_n }} best-matching allotypes above the PCC threshold, ranked overall.{% endif %} +

+
@@ -307,13 +715,13 @@ {% for r in table_rows %} - + @@ -321,11 +729,12 @@
{{ loop.index }} {{ r.cluster }} {{ r.hla }} {{ '%.3f'|format(r.correlation) }} + style="background: {% if r.correlation >= 0.7 %}#4d8076{% elif r.correlation >= 0.5 %}#b08968{% else %}#b0b0b0{% endif %};">{{ '%.3f'|format(r.correlation) }}{% if r.below %} below cutoff{% endif %} {{ '%.3f'|format(r.kld) if r.kld is not none else 'NA' }}
+
-
-

Correlation Analysis

+
+

Correlation AnalysisiInteractive views of every cluster↔allotype correlation. Drag the PCC threshold to hide weaker matches. The Heatmap colours each pair by PCC; the Network links clusters to their correlated allotypes. The Combined tab stacks both.

@@ -347,11 +756,11 @@
-
Heatmap
+
HeatmapiColour encodes the PCC (0–1) for each cluster × allotype pair above the threshold. Darker = stronger match. Hover a cell for exact values.
-
Network
+
NetworkiForce-directed graph: each cluster (large coloured node) links to the allotypes it correlates with; edge width scales with PCC. Drag nodes, scroll to zoom, hover for details.
@@ -371,41 +780,184 @@
- - {% for sec in cluster_sections %} -
-

Results for {{ sec.n_clusters }} clusters

-
- {% for c in sec.groups %} -
-
- Best matched allotype is {{ c.hla }} - · PCC {{ '%.2f'|format(c.correlation) }} - {% if c.kld is not none %}· KLD {{ '%.2f'|format(c.kld) }}{% endif %} -
-
-
-
Gibbs Cluster
- {% if c.cluster_logo %}cluster logo{% endif %} -
-
-
Reference Motif logo of {{ c.hla }}
- reference logo -
-
-
- {% endfor %} -
-
- {% endfor %} -
- MHC-TP v{{ version }} · Li Lab / Purcell Lab, Monash University - · references: NetMHCpan-4.2 / NetMHCIIpan-4.3, GibbsCluster, MHCMotifAtlas +
+
How to cite
+

+ Munday PR, Krishna SSG, Fehring J, Croft NP, Purcell AW, Li C, Braun A. + Immunolyser 2.0: An advanced computational pipeline for comprehensive + analysis of immunopeptidomic data. + Comput Struct Biotechnol J. 2025;29:296–304. + doi:10.1016/j.csbj.2025.10.007. +

+

+ Software: + github.com/PurcellLab/MHC-TP +

+
+