bsvae 0.2.0__tar.gz → 0.3.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.
- bsvae-0.3.0/PKG-INFO +162 -0
- bsvae-0.3.0/README.md +130 -0
- {bsvae-0.2.0 → bsvae-0.3.0}/pyproject.toml +18 -10
- bsvae-0.3.0/src/bsvae/__init__.py +13 -0
- bsvae-0.3.0/src/bsvae/cli/__init__.py +1 -0
- bsvae-0.3.0/src/bsvae/cli/networks.py +287 -0
- bsvae-0.3.0/src/bsvae/cli/simulate.py +161 -0
- bsvae-0.3.0/src/bsvae/cli/train.py +226 -0
- bsvae-0.3.0/src/bsvae/hyperparam.ini +9 -0
- bsvae-0.3.0/src/bsvae/latent/__init__.py +89 -0
- {bsvae-0.2.0 → bsvae-0.3.0}/src/bsvae/latent/latent_analysis.py +31 -23
- {bsvae-0.2.0 → bsvae-0.3.0}/src/bsvae/main.py +132 -27
- bsvae-0.3.0/src/bsvae/models/__init__.py +5 -0
- bsvae-0.3.0/src/bsvae/models/decoder.py +58 -0
- bsvae-0.3.0/src/bsvae/models/encoder.py +85 -0
- bsvae-0.3.0/src/bsvae/models/gmm_prior.py +288 -0
- bsvae-0.3.0/src/bsvae/models/gmvae.py +133 -0
- bsvae-0.3.0/src/bsvae/models/losses.py +432 -0
- bsvae-0.3.0/src/bsvae/networks/__init__.py +2 -0
- bsvae-0.3.0/src/bsvae/networks/cli.py +689 -0
- bsvae-0.3.0/src/bsvae/networks/extract_networks.py +363 -0
- bsvae-0.3.0/src/bsvae/networks/module_extraction.py +1047 -0
- {bsvae-0.2.0 → bsvae-0.3.0}/src/bsvae/networks/utils.py +7 -2
- bsvae-0.3.0/src/bsvae/simulation/__init__.py +5 -0
- bsvae-0.3.0/src/bsvae/simulation/generate.py +116 -0
- bsvae-0.3.0/src/bsvae/simulation/metrics.py +154 -0
- bsvae-0.3.0/src/bsvae/utils/__init__.py +1 -0
- bsvae-0.3.0/src/bsvae/utils/datasets.py +305 -0
- {bsvae-0.2.0 → bsvae-0.3.0}/src/bsvae/utils/evaluate.py +14 -7
- bsvae-0.3.0/src/bsvae/utils/hierarchy.py +172 -0
- bsvae-0.3.0/src/bsvae/utils/modelIO.py +190 -0
- bsvae-0.3.0/src/bsvae/utils/training.py +365 -0
- bsvae-0.2.0/PKG-INFO +0 -240
- bsvae-0.2.0/README.md +0 -207
- bsvae-0.2.0/src/bsvae/__init__.py +0 -12
- bsvae-0.2.0/src/bsvae/hyperparam.ini +0 -82
- bsvae-0.2.0/src/bsvae/latent/__init__.py +0 -6
- bsvae-0.2.0/src/bsvae/models/__init__.py +0 -4
- bsvae-0.2.0/src/bsvae/models/decoder.py +0 -92
- bsvae-0.2.0/src/bsvae/models/encoder.py +0 -71
- bsvae-0.2.0/src/bsvae/models/losses.py +0 -116
- bsvae-0.2.0/src/bsvae/models/structured.py +0 -79
- bsvae-0.2.0/src/bsvae/networks/__init__.py +0 -13
- bsvae-0.2.0/src/bsvae/networks/cli.py +0 -318
- bsvae-0.2.0/src/bsvae/networks/extract_networks.py +0 -1229
- bsvae-0.2.0/src/bsvae/networks/module_extraction.py +0 -426
- bsvae-0.2.0/src/bsvae/ppi_cli.py +0 -51
- bsvae-0.2.0/src/bsvae/utils/__init__.py +0 -2
- bsvae-0.2.0/src/bsvae/utils/datasets.py +0 -187
- bsvae-0.2.0/src/bsvae/utils/mapping.py +0 -218
- bsvae-0.2.0/src/bsvae/utils/modelIO.py +0 -187
- bsvae-0.2.0/src/bsvae/utils/ppi.py +0 -158
- bsvae-0.2.0/src/bsvae/utils/training.py +0 -143
- {bsvae-0.2.0 → bsvae-0.3.0}/LICENSE +0 -0
- {bsvae-0.2.0 → bsvae-0.3.0}/src/bsvae/latent/latent_export.py +0 -0
- {bsvae-0.2.0 → bsvae-0.3.0}/src/bsvae/models/vae.py +0 -0
- {bsvae-0.2.0 → bsvae-0.3.0}/src/bsvae/utils/helpers.py +0 -0
- {bsvae-0.2.0 → bsvae-0.3.0}/src/bsvae/utils/initialization.py +0 -0
bsvae-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bsvae
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Gaussian Mixture VAE for multi-modal biological module discovery in omics data.
|
|
5
|
+
License: GPL-3.0-only
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: variational-autoencoder,gaussian-mixture-model,pytorch,bioinformatics,gene-expression,module-discovery,omics
|
|
8
|
+
Author: Kynon J Benjamin
|
|
9
|
+
Author-email: kj.benjamin90@gmail.com
|
|
10
|
+
Requires-Python: >=3.11,<3.14
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
19
|
+
Requires-Dist: faiss-cpu (>=1.7)
|
|
20
|
+
Requires-Dist: h5py (>=3.0)
|
|
21
|
+
Requires-Dist: numpy (<2.3)
|
|
22
|
+
Requires-Dist: pandas (>=2.3.2,<3.0.0)
|
|
23
|
+
Requires-Dist: scikit-learn (>=1.7.2,<2.0.0)
|
|
24
|
+
Requires-Dist: scipy (>=1.16.2,<2.0.0)
|
|
25
|
+
Requires-Dist: torch (>=2.8.0,<3.0.0)
|
|
26
|
+
Requires-Dist: tqdm (>=4.67.1,<5.0.0)
|
|
27
|
+
Project-URL: Bug Tracker, https://github.com/heart-gen/BSVAE/issues
|
|
28
|
+
Project-URL: homepage, https://bsvae.readthedocs.io/en/latest/
|
|
29
|
+
Project-URL: repository, https://github.com/heart-gen/BSVAE
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# BSVAE: Gaussian Mixture VAE for Module Discovery
|
|
33
|
+
|
|
34
|
+
[](https://pytorch.org/)
|
|
35
|
+
[](https://python.org/)
|
|
36
|
+
[](LICENSE)
|
|
37
|
+
[](https://bsvae.readthedocs.io/)
|
|
38
|
+
|
|
39
|
+
BSVAE is a PyTorch package centered on `GMMModuleVAE`, a Gaussian-mixture variational autoencoder for feature-level module discovery in omics data.
|
|
40
|
+
|
|
41
|
+
## What It Does
|
|
42
|
+
|
|
43
|
+
- Trains a two-phase GMM-VAE (`bsvae-train`)
|
|
44
|
+
- Extracts feature-feature networks from trained models (`bsvae-networks`)
|
|
45
|
+
- Extracts module assignments and optional eigengenes (`bsvae-networks`)
|
|
46
|
+
- Exports latents (`mu`, `logvar`, `gamma`) as `.npz`
|
|
47
|
+
- Simulates synthetic datasets and benchmarks module recovery (`bsvae-simulate`)
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
From PyPI:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install bsvae
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
From source:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
git clone https://github.com/heart-gen/BSVAE.git
|
|
61
|
+
cd BSVAE
|
|
62
|
+
pip install -e .
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## CLI Entry Points
|
|
66
|
+
|
|
67
|
+
- `bsvae-train`
|
|
68
|
+
- `bsvae-networks`
|
|
69
|
+
- `bsvae-simulate`
|
|
70
|
+
|
|
71
|
+
## Quickstart
|
|
72
|
+
|
|
73
|
+
For a full walkthrough (minimal run, production run, post-training analysis, simulation benchmark, troubleshooting, and migration), see `docs/tutorial.md`.
|
|
74
|
+
|
|
75
|
+
### 1. Train
|
|
76
|
+
|
|
77
|
+
Input matrix must be `features x samples` with feature IDs in row index and sample IDs in columns.
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
bsvae-train exp1 \
|
|
81
|
+
--dataset data/expression.csv \
|
|
82
|
+
--epochs 100 \
|
|
83
|
+
--n-modules 20 \
|
|
84
|
+
--latent-dim 32
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 2. Extract networks
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
bsvae-networks extract-networks \
|
|
91
|
+
--model-path results/exp1 \
|
|
92
|
+
--dataset data/expression.csv \
|
|
93
|
+
--output-dir results/exp1/networks \
|
|
94
|
+
--methods mu_cosine gamma_knn
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 3. Extract modules
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
bsvae-networks extract-modules \
|
|
101
|
+
--model-path results/exp1 \
|
|
102
|
+
--dataset data/expression.csv \
|
|
103
|
+
--output-dir results/exp1/modules
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 4. Export latents
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
bsvae-networks export-latents \
|
|
110
|
+
--model-path results/exp1 \
|
|
111
|
+
--dataset data/expression.csv \
|
|
112
|
+
--output results/exp1/latents.npz
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 5. Simulate and benchmark
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
bsvae-simulate generate \
|
|
119
|
+
--output data/sim_expr.csv \
|
|
120
|
+
--save-ground-truth data/sim_truth.csv
|
|
121
|
+
|
|
122
|
+
bsvae-simulate benchmark \
|
|
123
|
+
--dataset data/sim_expr.csv \
|
|
124
|
+
--ground-truth data/sim_truth.csv \
|
|
125
|
+
--model-path results/exp1 \
|
|
126
|
+
--output results/exp1/sim_metrics.json
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Training Outputs
|
|
130
|
+
|
|
131
|
+
`bsvae-train` writes to `results/<experiment>/`:
|
|
132
|
+
|
|
133
|
+
- `model.pt` (weights)
|
|
134
|
+
- `specs.json` (metadata and run args)
|
|
135
|
+
- `train_losses.csv` (epoch/component losses)
|
|
136
|
+
- `model-<epoch>.pt` checkpoints when `--checkpoint-every` is set
|
|
137
|
+
|
|
138
|
+
## Data Formats
|
|
139
|
+
|
|
140
|
+
The loader supports:
|
|
141
|
+
|
|
142
|
+
- `.csv` / `.csv.gz`
|
|
143
|
+
- `.tsv` / `.tsv.gz`
|
|
144
|
+
- `.h5` / `.hdf5`
|
|
145
|
+
- `.h5ad` (optional `anndata` dependency)
|
|
146
|
+
|
|
147
|
+
## Python API (Minimal)
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from bsvae.utils.modelIO import load_model
|
|
151
|
+
from bsvae.networks.extract_networks import create_dataloader_from_expression, run_extraction
|
|
152
|
+
|
|
153
|
+
model = load_model("results/exp1", is_gpu=False)
|
|
154
|
+
loader, feature_ids, _ = create_dataloader_from_expression("data/expression.csv", batch_size=128)
|
|
155
|
+
results = run_extraction(model, loader, feature_ids=feature_ids, methods=["mu_cosine"], top_k=50)
|
|
156
|
+
print(results[0].method, results[0].adjacency.shape)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
This project is licensed under the [GNU General Public License v3.0](LICENSE).
|
|
162
|
+
|
bsvae-0.3.0/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# BSVAE: Gaussian Mixture VAE for Module Discovery
|
|
2
|
+
|
|
3
|
+
[](https://pytorch.org/)
|
|
4
|
+
[](https://python.org/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://bsvae.readthedocs.io/)
|
|
7
|
+
|
|
8
|
+
BSVAE is a PyTorch package centered on `GMMModuleVAE`, a Gaussian-mixture variational autoencoder for feature-level module discovery in omics data.
|
|
9
|
+
|
|
10
|
+
## What It Does
|
|
11
|
+
|
|
12
|
+
- Trains a two-phase GMM-VAE (`bsvae-train`)
|
|
13
|
+
- Extracts feature-feature networks from trained models (`bsvae-networks`)
|
|
14
|
+
- Extracts module assignments and optional eigengenes (`bsvae-networks`)
|
|
15
|
+
- Exports latents (`mu`, `logvar`, `gamma`) as `.npz`
|
|
16
|
+
- Simulates synthetic datasets and benchmarks module recovery (`bsvae-simulate`)
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
From PyPI:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install bsvae
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
From source:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
git clone https://github.com/heart-gen/BSVAE.git
|
|
30
|
+
cd BSVAE
|
|
31
|
+
pip install -e .
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## CLI Entry Points
|
|
35
|
+
|
|
36
|
+
- `bsvae-train`
|
|
37
|
+
- `bsvae-networks`
|
|
38
|
+
- `bsvae-simulate`
|
|
39
|
+
|
|
40
|
+
## Quickstart
|
|
41
|
+
|
|
42
|
+
For a full walkthrough (minimal run, production run, post-training analysis, simulation benchmark, troubleshooting, and migration), see `docs/tutorial.md`.
|
|
43
|
+
|
|
44
|
+
### 1. Train
|
|
45
|
+
|
|
46
|
+
Input matrix must be `features x samples` with feature IDs in row index and sample IDs in columns.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
bsvae-train exp1 \
|
|
50
|
+
--dataset data/expression.csv \
|
|
51
|
+
--epochs 100 \
|
|
52
|
+
--n-modules 20 \
|
|
53
|
+
--latent-dim 32
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 2. Extract networks
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
bsvae-networks extract-networks \
|
|
60
|
+
--model-path results/exp1 \
|
|
61
|
+
--dataset data/expression.csv \
|
|
62
|
+
--output-dir results/exp1/networks \
|
|
63
|
+
--methods mu_cosine gamma_knn
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 3. Extract modules
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
bsvae-networks extract-modules \
|
|
70
|
+
--model-path results/exp1 \
|
|
71
|
+
--dataset data/expression.csv \
|
|
72
|
+
--output-dir results/exp1/modules
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 4. Export latents
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
bsvae-networks export-latents \
|
|
79
|
+
--model-path results/exp1 \
|
|
80
|
+
--dataset data/expression.csv \
|
|
81
|
+
--output results/exp1/latents.npz
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 5. Simulate and benchmark
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
bsvae-simulate generate \
|
|
88
|
+
--output data/sim_expr.csv \
|
|
89
|
+
--save-ground-truth data/sim_truth.csv
|
|
90
|
+
|
|
91
|
+
bsvae-simulate benchmark \
|
|
92
|
+
--dataset data/sim_expr.csv \
|
|
93
|
+
--ground-truth data/sim_truth.csv \
|
|
94
|
+
--model-path results/exp1 \
|
|
95
|
+
--output results/exp1/sim_metrics.json
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Training Outputs
|
|
99
|
+
|
|
100
|
+
`bsvae-train` writes to `results/<experiment>/`:
|
|
101
|
+
|
|
102
|
+
- `model.pt` (weights)
|
|
103
|
+
- `specs.json` (metadata and run args)
|
|
104
|
+
- `train_losses.csv` (epoch/component losses)
|
|
105
|
+
- `model-<epoch>.pt` checkpoints when `--checkpoint-every` is set
|
|
106
|
+
|
|
107
|
+
## Data Formats
|
|
108
|
+
|
|
109
|
+
The loader supports:
|
|
110
|
+
|
|
111
|
+
- `.csv` / `.csv.gz`
|
|
112
|
+
- `.tsv` / `.tsv.gz`
|
|
113
|
+
- `.h5` / `.hdf5`
|
|
114
|
+
- `.h5ad` (optional `anndata` dependency)
|
|
115
|
+
|
|
116
|
+
## Python API (Minimal)
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from bsvae.utils.modelIO import load_model
|
|
120
|
+
from bsvae.networks.extract_networks import create_dataloader_from_expression, run_extraction
|
|
121
|
+
|
|
122
|
+
model = load_model("results/exp1", is_gpu=False)
|
|
123
|
+
loader, feature_ids, _ = create_dataloader_from_expression("data/expression.csv", batch_size=128)
|
|
124
|
+
results = run_extraction(model, loader, feature_ids=feature_ids, methods=["mu_cosine"], top_k=50)
|
|
125
|
+
print(results[0].method, results[0].adjacency.shape)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
|
|
130
|
+
This project is licensed under the [GNU General Public License v3.0](LICENSE).
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "bsvae"
|
|
3
|
-
version = "0.
|
|
4
|
-
description = "
|
|
3
|
+
version = "0.3.0" # dynamically resolved by poetry-dynamic-versioning
|
|
4
|
+
description = "Gaussian Mixture VAE for multi-modal biological module discovery in omics data."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = ["Kynon J Benjamin <kj.benjamin90@gmail.com>"]
|
|
7
7
|
license = "GPL-3.0-only"
|
|
8
8
|
packages = [{ include = "bsvae", from = "src" }]
|
|
9
9
|
keywords = [
|
|
10
10
|
"variational-autoencoder",
|
|
11
|
-
"
|
|
11
|
+
"gaussian-mixture-model",
|
|
12
12
|
"pytorch",
|
|
13
13
|
"bioinformatics",
|
|
14
14
|
"gene-expression",
|
|
15
|
-
"
|
|
15
|
+
"module-discovery",
|
|
16
|
+
"omics",
|
|
16
17
|
]
|
|
17
18
|
classifiers = [
|
|
18
19
|
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
|
@@ -30,12 +31,15 @@ python = ">=3.11,<3.14"
|
|
|
30
31
|
torch = ">=2.8.0,<3.0.0"
|
|
31
32
|
numpy = "<2.3"
|
|
32
33
|
pandas = ">=2.3.2,<3.0.0"
|
|
33
|
-
pyarrow = ">=19.0.0,<20.0.0"
|
|
34
34
|
scikit-learn = ">=1.7.2,<2.0.0"
|
|
35
35
|
tqdm = ">=4.67.1,<5.0.0"
|
|
36
|
-
mygene = ">=3.2.2,<4.0.0"
|
|
37
|
-
networkx = ">=3.5,<4.0"
|
|
38
36
|
scipy = ">=1.16.2,<2.0.0"
|
|
37
|
+
h5py = ">=3.0"
|
|
38
|
+
faiss-cpu = ">=1.7"
|
|
39
|
+
|
|
40
|
+
[tool.poetry.dependencies.anndata]
|
|
41
|
+
version = ">=0.10"
|
|
42
|
+
optional = true
|
|
39
43
|
|
|
40
44
|
[tool.poetry.urls]
|
|
41
45
|
homepage = "https://bsvae.readthedocs.io/en/latest/"
|
|
@@ -43,9 +47,9 @@ repository = "https://github.com/heart-gen/BSVAE"
|
|
|
43
47
|
"Bug Tracker" = "https://github.com/heart-gen/BSVAE/issues"
|
|
44
48
|
|
|
45
49
|
[tool.poetry.scripts]
|
|
46
|
-
bsvae-train = "bsvae.
|
|
47
|
-
bsvae-
|
|
48
|
-
bsvae-
|
|
50
|
+
bsvae-train = "bsvae.cli.train:cli"
|
|
51
|
+
bsvae-networks = "bsvae.cli.networks:cli"
|
|
52
|
+
bsvae-simulate = "bsvae.cli.simulate:cli"
|
|
49
53
|
|
|
50
54
|
[tool.poetry.group.dev.dependencies]
|
|
51
55
|
pytest = ">=8.4.2,<9.0.0"
|
|
@@ -53,6 +57,10 @@ pytest-cov = ">=7.0.0,<8.0.0"
|
|
|
53
57
|
|
|
54
58
|
[tool.pytest.ini_options]
|
|
55
59
|
pythonpath = ["src"]
|
|
60
|
+
markers = [
|
|
61
|
+
"slow: marks tests as slow-running integration tests (deselect with '-m not slow')",
|
|
62
|
+
]
|
|
63
|
+
addopts = ["-m", "not slow"]
|
|
56
64
|
|
|
57
65
|
[tool.poetry-dynamic-versioning]
|
|
58
66
|
enable = false
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""BSVAE — Gaussian Mixture VAE for multi-modal biological module discovery."""
|
|
2
|
+
|
|
3
|
+
from importlib import import_module
|
|
4
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
5
|
+
|
|
6
|
+
from . import latent, networks, simulation
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
__version__ = version(__name__)
|
|
10
|
+
except PackageNotFoundError:
|
|
11
|
+
__version__ = "0.0.0"
|
|
12
|
+
|
|
13
|
+
__all__ = ["networks", "latent", "simulation", "__version__"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CLI entry points for bsvae."""
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""
|
|
2
|
+
bsvae-networks — Network extraction, module assignment, latent export.
|
|
3
|
+
|
|
4
|
+
Subcommands
|
|
5
|
+
-----------
|
|
6
|
+
extract-networks Method A (μ cosine) or Method B (FAISS γ-kNN)
|
|
7
|
+
extract-modules GMM soft/hard assignments + soft-weighted eigengenes
|
|
8
|
+
export-latents Save μ, logσ², γ as NPZ
|
|
9
|
+
latent-analysis UMAP, clustering, covariate correlation
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import logging
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import List, Optional
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def setup_logging(level: str = "INFO") -> logging.Logger:
|
|
22
|
+
logging.basicConfig(
|
|
23
|
+
level=getattr(logging, level.upper(), logging.INFO),
|
|
24
|
+
format="%(asctime)s %(levelname)s: %(message)s",
|
|
25
|
+
)
|
|
26
|
+
return logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
30
|
+
parser = argparse.ArgumentParser(
|
|
31
|
+
prog="bsvae-networks",
|
|
32
|
+
description="Network extraction and latent analysis for GMMModuleVAE.",
|
|
33
|
+
)
|
|
34
|
+
subs = parser.add_subparsers(dest="command", required=True)
|
|
35
|
+
|
|
36
|
+
# ------------------------------------------------------------------
|
|
37
|
+
# extract-networks
|
|
38
|
+
# ------------------------------------------------------------------
|
|
39
|
+
ep = subs.add_parser("extract-networks", help="Build feature-feature networks from trained model.")
|
|
40
|
+
ep.add_argument("--model-path", required=True)
|
|
41
|
+
ep.add_argument("--dataset", required=True)
|
|
42
|
+
ep.add_argument("--output-dir", required=True)
|
|
43
|
+
ep.add_argument(
|
|
44
|
+
"--methods", nargs="+", default=["mu_cosine"],
|
|
45
|
+
choices=["mu_cosine", "gamma_knn"],
|
|
46
|
+
help="Methods to run (default: mu_cosine).",
|
|
47
|
+
)
|
|
48
|
+
ep.add_argument("--top-k", type=int, default=50, help="Top-K edges per feature (default: 50).")
|
|
49
|
+
ep.add_argument("--batch-size", type=int, default=128)
|
|
50
|
+
ep.add_argument("--no-cuda", action="store_true", default=False)
|
|
51
|
+
|
|
52
|
+
# ------------------------------------------------------------------
|
|
53
|
+
# extract-modules
|
|
54
|
+
# ------------------------------------------------------------------
|
|
55
|
+
mp = subs.add_parser("extract-modules", help="Extract GMM module assignments.")
|
|
56
|
+
mp.add_argument("--model-path", required=True)
|
|
57
|
+
mp.add_argument("--dataset", required=True)
|
|
58
|
+
mp.add_argument("--output-dir", required=True)
|
|
59
|
+
mp.add_argument("--batch-size", type=int, default=128)
|
|
60
|
+
mp.add_argument("--no-cuda", action="store_true", default=False)
|
|
61
|
+
mp.add_argument("--expr", help="Expression matrix for eigengene computation (features × samples).")
|
|
62
|
+
mp.add_argument("--soft-eigengenes", action="store_true",
|
|
63
|
+
help="Compute soft-weighted eigengenes (requires --expr).")
|
|
64
|
+
mp.add_argument("--use-leiden", action="store_true",
|
|
65
|
+
help="Also run Leiden clustering as fallback comparison.")
|
|
66
|
+
mp.add_argument("--leiden-resolution", type=float, default=1.0)
|
|
67
|
+
|
|
68
|
+
# ------------------------------------------------------------------
|
|
69
|
+
# export-latents
|
|
70
|
+
# ------------------------------------------------------------------
|
|
71
|
+
lp = subs.add_parser("export-latents", help="Export μ, logσ², γ for all features.")
|
|
72
|
+
lp.add_argument("--model-path", required=True)
|
|
73
|
+
lp.add_argument("--dataset", required=True)
|
|
74
|
+
lp.add_argument("--output", required=True, help="Output path (.npz).")
|
|
75
|
+
lp.add_argument("--batch-size", type=int, default=128)
|
|
76
|
+
lp.add_argument("--no-cuda", action="store_true", default=False)
|
|
77
|
+
|
|
78
|
+
# ------------------------------------------------------------------
|
|
79
|
+
# latent-analysis
|
|
80
|
+
# ------------------------------------------------------------------
|
|
81
|
+
la = subs.add_parser("latent-analysis", help="UMAP, clustering, covariate correlation on μ.")
|
|
82
|
+
la.add_argument("--model-path", required=True)
|
|
83
|
+
la.add_argument("--dataset", required=True)
|
|
84
|
+
la.add_argument("--output-dir", required=True)
|
|
85
|
+
la.add_argument("--batch-size", type=int, default=128)
|
|
86
|
+
la.add_argument("--no-cuda", action="store_true", default=False)
|
|
87
|
+
la.add_argument("--covariates", help="TSV/CSV with sample covariates.")
|
|
88
|
+
la.add_argument("--kmeans-k", type=int, default=0)
|
|
89
|
+
la.add_argument("--gmm-k", type=int, default=0)
|
|
90
|
+
la.add_argument("--umap", action="store_true")
|
|
91
|
+
la.add_argument("--tsne", action="store_true")
|
|
92
|
+
la.add_argument("--tsne-perplexity", type=float, default=30.0)
|
|
93
|
+
|
|
94
|
+
return parser
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# ------------------------------------------------------------------
|
|
98
|
+
# Handlers
|
|
99
|
+
# ------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
def handle_extract_networks(args, logger: logging.Logger) -> None:
|
|
102
|
+
import torch
|
|
103
|
+
from bsvae.utils.modelIO import load_model
|
|
104
|
+
from bsvae.networks.extract_networks import run_extraction, create_dataloader_from_expression
|
|
105
|
+
|
|
106
|
+
device = torch.device("cuda" if torch.cuda.is_available() and not args.no_cuda else "cpu")
|
|
107
|
+
model = load_model(args.model_path, is_gpu=not args.no_cuda)
|
|
108
|
+
dataloader, feature_ids, _ = create_dataloader_from_expression(
|
|
109
|
+
args.dataset, batch_size=args.batch_size
|
|
110
|
+
)
|
|
111
|
+
logger.info("Running methods: %s | top_k=%d", args.methods, args.top_k)
|
|
112
|
+
run_extraction(
|
|
113
|
+
model=model,
|
|
114
|
+
dataloader=dataloader,
|
|
115
|
+
feature_ids=feature_ids,
|
|
116
|
+
methods=args.methods,
|
|
117
|
+
top_k=args.top_k,
|
|
118
|
+
output_dir=args.output_dir,
|
|
119
|
+
)
|
|
120
|
+
logger.info("Network extraction complete → %s", args.output_dir)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def handle_extract_modules(args, logger: logging.Logger) -> None:
|
|
124
|
+
import numpy as np
|
|
125
|
+
from bsvae.utils.modelIO import load_model
|
|
126
|
+
from bsvae.networks.extract_networks import create_dataloader_from_expression
|
|
127
|
+
from bsvae.networks.module_extraction import (
|
|
128
|
+
extract_gmm_modules,
|
|
129
|
+
compute_module_eigengenes_from_soft,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
model = load_model(args.model_path, is_gpu=not args.no_cuda)
|
|
133
|
+
dataloader, feature_ids, _ = create_dataloader_from_expression(
|
|
134
|
+
args.dataset, batch_size=args.batch_size
|
|
135
|
+
)
|
|
136
|
+
result = extract_gmm_modules(
|
|
137
|
+
model=model,
|
|
138
|
+
dataloader=dataloader,
|
|
139
|
+
feature_ids=feature_ids,
|
|
140
|
+
output_dir=args.output_dir,
|
|
141
|
+
)
|
|
142
|
+
logger.info(
|
|
143
|
+
"Extracted %d-module GMM assignments for %d features",
|
|
144
|
+
model.n_modules, len(feature_ids),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
if args.soft_eigengenes and args.expr:
|
|
148
|
+
import pandas as pd
|
|
149
|
+
expr = pd.read_csv(args.expr, index_col=0, sep="\t" if args.expr.endswith(".tsv") else ",")
|
|
150
|
+
eigengenes = compute_module_eigengenes_from_soft(
|
|
151
|
+
expr, result["gamma"], feature_ids
|
|
152
|
+
)
|
|
153
|
+
out = Path(args.output_dir) / "soft_eigengenes.csv"
|
|
154
|
+
eigengenes.to_csv(out)
|
|
155
|
+
logger.info("Soft-weighted eigengenes saved to %s", out)
|
|
156
|
+
|
|
157
|
+
if args.use_leiden:
|
|
158
|
+
from bsvae.networks.module_extraction import leiden_modules, save_modules
|
|
159
|
+
from bsvae.networks.extract_networks import (
|
|
160
|
+
method_a_cosine,
|
|
161
|
+
extract_mu_gamma,
|
|
162
|
+
)
|
|
163
|
+
device = None
|
|
164
|
+
mu, gamma, _ = extract_mu_gamma(model, dataloader, device=device)
|
|
165
|
+
adj = method_a_cosine(mu, top_k=50)
|
|
166
|
+
import pandas as pd
|
|
167
|
+
adj_df = pd.DataFrame(
|
|
168
|
+
adj.toarray(), index=feature_ids, columns=feature_ids
|
|
169
|
+
)
|
|
170
|
+
modules = leiden_modules(adj_df, resolution=args.leiden_resolution)
|
|
171
|
+
save_modules(modules, Path(args.output_dir) / "leiden_modules.csv")
|
|
172
|
+
logger.info("Leiden fallback modules saved.")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def handle_export_latents(args, logger: logging.Logger) -> None:
|
|
176
|
+
import numpy as np
|
|
177
|
+
from bsvae.utils.modelIO import load_model
|
|
178
|
+
from bsvae.networks.extract_networks import extract_mu_gamma, create_dataloader_from_expression
|
|
179
|
+
|
|
180
|
+
model = load_model(args.model_path, is_gpu=not args.no_cuda)
|
|
181
|
+
dataloader, feature_ids, _ = create_dataloader_from_expression(
|
|
182
|
+
args.dataset, batch_size=args.batch_size
|
|
183
|
+
)
|
|
184
|
+
mu, gamma, extracted_ids = extract_mu_gamma(model, dataloader)
|
|
185
|
+
if not feature_ids:
|
|
186
|
+
feature_ids = extracted_ids
|
|
187
|
+
|
|
188
|
+
# Also get logvar
|
|
189
|
+
import torch
|
|
190
|
+
device = next(model.parameters()).device
|
|
191
|
+
model.eval()
|
|
192
|
+
logvar_list = []
|
|
193
|
+
with torch.no_grad():
|
|
194
|
+
for batch in dataloader:
|
|
195
|
+
x = batch[0] if isinstance(batch, (list, tuple)) else batch
|
|
196
|
+
x = x.to(device)
|
|
197
|
+
_, logvar = model.encode(x)
|
|
198
|
+
logvar_list.append(logvar.cpu().numpy())
|
|
199
|
+
import numpy as np
|
|
200
|
+
logvar = np.concatenate(logvar_list, axis=0).astype(np.float32)
|
|
201
|
+
|
|
202
|
+
out = args.output
|
|
203
|
+
if not out.endswith(".npz"):
|
|
204
|
+
out = out + ".npz"
|
|
205
|
+
np.savez_compressed(
|
|
206
|
+
out,
|
|
207
|
+
mu=mu,
|
|
208
|
+
logvar=logvar,
|
|
209
|
+
gamma=gamma,
|
|
210
|
+
feature_ids=np.array(feature_ids, dtype=object),
|
|
211
|
+
)
|
|
212
|
+
logger.info("Latents saved to %s", out)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def handle_latent_analysis(args, logger: logging.Logger) -> None:
|
|
216
|
+
from bsvae.utils.modelIO import load_model
|
|
217
|
+
from bsvae.networks.extract_networks import create_dataloader_from_expression
|
|
218
|
+
from bsvae.latent.latent_analysis import (
|
|
219
|
+
extract_latents,
|
|
220
|
+
kmeans_on_mu,
|
|
221
|
+
gmm_on_z,
|
|
222
|
+
umap_mu,
|
|
223
|
+
tsne_mu,
|
|
224
|
+
correlate_with_covariates,
|
|
225
|
+
save_latent_results,
|
|
226
|
+
)
|
|
227
|
+
import pandas as pd
|
|
228
|
+
import numpy as np
|
|
229
|
+
|
|
230
|
+
model = load_model(args.model_path, is_gpu=not args.no_cuda)
|
|
231
|
+
dataloader, feature_ids, _ = create_dataloader_from_expression(
|
|
232
|
+
args.dataset, batch_size=args.batch_size
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
mu, logvar, z = extract_latents(model, dataloader)
|
|
236
|
+
|
|
237
|
+
clusters = None
|
|
238
|
+
if args.kmeans_k:
|
|
239
|
+
clusters = kmeans_on_mu(mu, k=args.kmeans_k)
|
|
240
|
+
elif args.gmm_k:
|
|
241
|
+
clusters, _ = gmm_on_z(z, n_components=args.gmm_k)
|
|
242
|
+
|
|
243
|
+
embedding = None
|
|
244
|
+
if args.umap:
|
|
245
|
+
embedding = umap_mu(mu)
|
|
246
|
+
elif args.tsne:
|
|
247
|
+
embedding = tsne_mu(mu, perplexity=args.tsne_perplexity)
|
|
248
|
+
|
|
249
|
+
correlation_df = None
|
|
250
|
+
if args.covariates:
|
|
251
|
+
sep = "\t" if args.covariates.endswith(".tsv") else ","
|
|
252
|
+
cov_df = pd.read_csv(args.covariates, sep=sep, index_col=0)
|
|
253
|
+
cov_df = cov_df.reindex(feature_ids)
|
|
254
|
+
if not cov_df.empty:
|
|
255
|
+
correlation_df = correlate_with_covariates(
|
|
256
|
+
pd.DataFrame(mu, index=feature_ids), cov_df
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
save_latent_results(
|
|
260
|
+
mu=mu, logvar=logvar, row_ids=feature_ids,
|
|
261
|
+
output_dir=args.output_dir,
|
|
262
|
+
cluster_labels=clusters,
|
|
263
|
+
embedding=embedding,
|
|
264
|
+
correlation_df=correlation_df,
|
|
265
|
+
)
|
|
266
|
+
logger.info("Latent analysis saved to %s", args.output_dir)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def cli(argv=None):
|
|
270
|
+
parser = build_parser()
|
|
271
|
+
args = parser.parse_args(argv)
|
|
272
|
+
logger = setup_logging()
|
|
273
|
+
|
|
274
|
+
if args.command == "extract-networks":
|
|
275
|
+
handle_extract_networks(args, logger)
|
|
276
|
+
elif args.command == "extract-modules":
|
|
277
|
+
handle_extract_modules(args, logger)
|
|
278
|
+
elif args.command == "export-latents":
|
|
279
|
+
handle_export_latents(args, logger)
|
|
280
|
+
elif args.command == "latent-analysis":
|
|
281
|
+
handle_latent_analysis(args, logger)
|
|
282
|
+
else:
|
|
283
|
+
parser.error(f"Unknown command: {args.command}")
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
if __name__ == "__main__":
|
|
287
|
+
cli()
|