cytocommunity2 0.1.2__tar.gz → 0.1.4__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.
- {cytocommunity2-0.1.2/src/cytocommunity2.egg-info → cytocommunity2-0.1.4}/PKG-INFO +2 -3
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/README.md +0 -1
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/pyproject.toml +2 -2
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/__init__.py +9 -9
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/config.py +7 -6
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/learning/api.py +67 -50
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/learning/dataset.py +9 -7
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/learning/selection.py +173 -235
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/learning/training.py +15 -10
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4/src/cytocommunity2.egg-info}/PKG-INFO +2 -3
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2.egg-info/requires.txt +1 -1
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/LICENSE +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/setup.cfg +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/colors.py +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/__init__.py +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/coherence.py +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/communication/__init__.py +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/communication/between.py +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/communication/cca.py +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/communication/utils.py +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/communication/within.py +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/composition.py +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/moran.py +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/plotting/__init__.py +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/plotting/cn_selection.py +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/plotting/coherence.py +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/plotting/common.py +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/plotting/communication.py +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/plotting/composition.py +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/plotting/dominant.py +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/plotting/dotplots.py +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/plotting/recurrence.py +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/runtime.py +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/ensemble.py +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/learning/__init__.py +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/learning/model.py +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/paths.py +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/visualization/__init__.py +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/visualization/spatial.py +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2.egg-info/SOURCES.txt +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2.egg-info/dependency_links.txt +0 -0
- {cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cytocommunity2
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Weakly supervised cellular-neighborhood learning for spatial omics
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -10,7 +10,7 @@ Requires-Dist: numpy<2,>=1.23
|
|
|
10
10
|
Requires-Dist: pandas<3,>=1.5
|
|
11
11
|
Requires-Dist: sci-palettes
|
|
12
12
|
Requires-Dist: scikit-learn>=1.1
|
|
13
|
-
Requires-Dist: scipy>=1.
|
|
13
|
+
Requires-Dist: scipy>=1.11
|
|
14
14
|
Requires-Dist: seaborn>=0.12
|
|
15
15
|
Requires-Dist: torch-geometric>=2.0
|
|
16
16
|
Provides-Extra: downstream
|
|
@@ -81,7 +81,6 @@ conda create -n cytocommunity2 python=3.10
|
|
|
81
81
|
conda activate cytocommunity2
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
Install PyTorch for your compute platform before installing CytoCommunity2.
|
|
85
84
|
For an NVIDIA GPU on Windows or Linux, first install the appropriate
|
|
86
85
|
CUDA-enabled PyTorch build from the
|
|
87
86
|
[official PyTorch installation page](https://pytorch.org/get-started/locally/).
|
|
@@ -60,7 +60,6 @@ conda create -n cytocommunity2 python=3.10
|
|
|
60
60
|
conda activate cytocommunity2
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
Install PyTorch for your compute platform before installing CytoCommunity2.
|
|
64
63
|
For an NVIDIA GPU on Windows or Linux, first install the appropriate
|
|
65
64
|
CUDA-enabled PyTorch build from the
|
|
66
65
|
[official PyTorch installation page](https://pytorch.org/get-started/locally/).
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cytocommunity2"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.4"
|
|
8
8
|
description = "Weakly supervised cellular-neighborhood learning for spatial omics"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -14,7 +14,7 @@ dependencies = [
|
|
|
14
14
|
"pandas>=1.5,<3",
|
|
15
15
|
"sci-palettes",
|
|
16
16
|
"scikit-learn>=1.1",
|
|
17
|
-
"scipy>=1.
|
|
17
|
+
"scipy>=1.11",
|
|
18
18
|
"seaborn>=0.12",
|
|
19
19
|
"torch-geometric>=2.0",
|
|
20
20
|
]
|
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
from .config import CytoCommunityConfig
|
|
4
4
|
from .learning import (
|
|
5
|
-
LearningResult,
|
|
6
|
-
SpatialGraphDataset,
|
|
7
|
-
build_spatial_graphs,
|
|
8
|
-
load_learning_result,
|
|
9
|
-
run_learning,
|
|
5
|
+
LearningResult,
|
|
6
|
+
SpatialGraphDataset,
|
|
7
|
+
build_spatial_graphs,
|
|
8
|
+
load_learning_result,
|
|
9
|
+
run_learning,
|
|
10
10
|
)
|
|
11
11
|
from .visualization import visualize_results
|
|
12
12
|
|
|
13
|
-
__version__ = "0.1.
|
|
13
|
+
__version__ = "0.1.4"
|
|
14
14
|
|
|
15
15
|
__all__ = [
|
|
16
16
|
"CytoCommunityConfig",
|
|
17
17
|
"LearningResult",
|
|
18
18
|
"SpatialGraphDataset",
|
|
19
|
-
"build_spatial_graphs",
|
|
20
|
-
"load_learning_result",
|
|
21
|
-
"run_learning",
|
|
19
|
+
"build_spatial_graphs",
|
|
20
|
+
"load_learning_result",
|
|
21
|
+
"run_learning",
|
|
22
22
|
"visualize_results",
|
|
23
23
|
]
|
|
@@ -8,7 +8,6 @@ from typing import Any, Mapping
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
DEFAULT_CONFIG_FILE = "hyperparameters.json"
|
|
11
|
-
INPUT_DIR_NAME = "TNBC_Input_pCR"
|
|
12
11
|
|
|
13
12
|
# Established analysis rules stay stable across runs; only experimental choices
|
|
14
13
|
# belong in hyperparameters.json.
|
|
@@ -94,13 +93,15 @@ def _require_keys(values, required, section):
|
|
|
94
93
|
|
|
95
94
|
|
|
96
95
|
def _load_config(config_file=None, config_class=CytoCommunityConfig):
|
|
97
|
-
"""Load one configuration file and
|
|
98
|
-
config_file = Path(config_file or DEFAULT_CONFIG_FILE).expanduser().resolve()
|
|
99
|
-
raw = json.loads(config_file.read_text(encoding="utf-8"))
|
|
100
|
-
_require_keys(raw, ["random_seed", "main", "downstream"], "root")
|
|
96
|
+
"""Load one configuration file and resolve its configured input folder."""
|
|
97
|
+
config_file = Path(config_file or DEFAULT_CONFIG_FILE).expanduser().resolve()
|
|
98
|
+
raw = json.loads(config_file.read_text(encoding="utf-8"))
|
|
99
|
+
_require_keys(raw, ["input_dir", "random_seed", "main", "downstream"], "root")
|
|
101
100
|
|
|
102
101
|
project_root = config_file.parent
|
|
103
|
-
input_dir =
|
|
102
|
+
input_dir = Path(raw["input_dir"]).expanduser()
|
|
103
|
+
if not input_dir.is_absolute():
|
|
104
|
+
input_dir = project_root / input_dir
|
|
104
105
|
|
|
105
106
|
main = MainConfig(**raw["main"])
|
|
106
107
|
enrichment = EnrichmentConfig()
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
import datetime
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
import shutil
|
|
7
|
-
|
|
8
|
-
import
|
|
6
|
+
import shutil
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pandas as pd
|
|
9
10
|
|
|
10
|
-
from ..config import CytoCommunityConfig
|
|
11
|
-
from ..ensemble import ensemble_cn
|
|
11
|
+
from ..config import CytoCommunityConfig
|
|
12
|
+
from ..ensemble import ensemble_cn
|
|
13
|
+
from ..paths import build_paths
|
|
12
14
|
from .dataset import SpatialGraphDataset
|
|
13
15
|
from .model import TrainingConfig
|
|
14
16
|
from .selection import evaluate_cn_num, select_cn_num
|
|
@@ -33,11 +35,9 @@ class LearningResult:
|
|
|
33
35
|
def load_learning_result(
|
|
34
36
|
dataset: SpatialGraphDataset,
|
|
35
37
|
config: CytoCommunityConfig,
|
|
36
|
-
*,
|
|
37
|
-
output_dir,
|
|
38
38
|
) -> LearningResult:
|
|
39
39
|
"""Restore a :class:`LearningResult` from a completed learning run."""
|
|
40
|
-
output_dir =
|
|
40
|
+
output_dir = build_paths(config).learning_output
|
|
41
41
|
paths = {
|
|
42
42
|
"selected CN number": output_dir / "SelectedCNNum.txt",
|
|
43
43
|
"cell-type list": output_dir / "UniqueCellTypeList.txt",
|
|
@@ -86,13 +86,11 @@ def _training_config(config, num_cn, num_class):
|
|
|
86
86
|
|
|
87
87
|
|
|
88
88
|
def run_learning(
|
|
89
|
-
dataset: SpatialGraphDataset,
|
|
90
|
-
config: CytoCommunityConfig,
|
|
91
|
-
*,
|
|
92
|
-
output_dir,
|
|
89
|
+
dataset: SpatialGraphDataset,
|
|
90
|
+
config: CytoCommunityConfig,
|
|
93
91
|
) -> LearningResult:
|
|
94
92
|
"""Select the CN number, train final runs, and export consensus labels."""
|
|
95
|
-
output_dir =
|
|
93
|
+
output_dir = build_paths(config).learning_output
|
|
96
94
|
input_dir = config.input_dir
|
|
97
95
|
unique_celltypes_file = output_dir / "UniqueCellTypeList.txt"
|
|
98
96
|
selected_model_dir = output_dir / "SelectedModel"
|
|
@@ -136,7 +134,11 @@ def run_learning(
|
|
|
136
134
|
collect_hard_assignments=True,
|
|
137
135
|
)
|
|
138
136
|
|
|
139
|
-
|
|
137
|
+
cn_labels_by_sample = {
|
|
138
|
+
sample: hard_assignments[sample_index]
|
|
139
|
+
for sample_index, sample in enumerate(dataset.sample_names)
|
|
140
|
+
}
|
|
141
|
+
result, cn_pvalues = evaluate_cn_num(
|
|
140
142
|
num_cn=num_cn,
|
|
141
143
|
dataset=input_dir,
|
|
142
144
|
celltype_file=unique_celltypes_file,
|
|
@@ -146,30 +148,35 @@ def run_learning(
|
|
|
146
148
|
recurrence_p_threshold=config.recurrence.recurrence_p_threshold,
|
|
147
149
|
n_random=config.recurrence.null_draws,
|
|
148
150
|
seed=config.random_seed,
|
|
149
|
-
|
|
150
|
-
)
|
|
151
|
-
del hard_assignments
|
|
151
|
+
cn_labels_by_sample=cn_labels_by_sample,
|
|
152
|
+
)
|
|
153
|
+
del hard_assignments, cn_labels_by_sample
|
|
152
154
|
selection_results.append(result)
|
|
153
|
-
cn_pvalues = cn_pvalues[
|
|
154
|
-
[
|
|
155
|
-
"CN",
|
|
156
|
-
"
|
|
157
|
-
"
|
|
158
|
-
"
|
|
155
|
+
cn_pvalues = cn_pvalues[
|
|
156
|
+
[
|
|
157
|
+
"CN",
|
|
158
|
+
"n_real",
|
|
159
|
+
"n_background",
|
|
160
|
+
"real_mean",
|
|
161
|
+
"background_mean",
|
|
162
|
+
"delta_mean",
|
|
163
|
+
"KSStatistic",
|
|
164
|
+
"KSLocation",
|
|
165
|
+
"p_value_one_sided",
|
|
166
|
+
"p_adjusted_bh",
|
|
167
|
+
"RecurrenceScore",
|
|
159
168
|
"SignificantRecurrence",
|
|
160
169
|
]
|
|
161
170
|
].copy()
|
|
162
171
|
cn_pvalues.insert(0, "NumCN", num_cn)
|
|
163
|
-
cn_pvalue_tables.append(cn_pvalues)
|
|
164
|
-
print(
|
|
165
|
-
f"K={num_cn}:
|
|
166
|
-
f"significantFraction={result['SignificantFraction']:.6f}, "
|
|
167
|
-
f"
|
|
172
|
+
cn_pvalue_tables.append(cn_pvalues)
|
|
173
|
+
print(
|
|
174
|
+
f"K={num_cn}: Ksig={result['Ksig']}, "
|
|
175
|
+
f"significantFraction={result['SignificantFraction']:.6f}, "
|
|
176
|
+
f"MeanRS={result['MeanRS']:.6f}, CRI={result['CRI']:.6f}"
|
|
168
177
|
)
|
|
169
178
|
|
|
170
|
-
pd.DataFrame(selection_results).
|
|
171
|
-
selection_summary_file, index=False
|
|
172
|
-
)
|
|
179
|
+
pd.DataFrame(selection_results).to_csv(selection_summary_file, index=False)
|
|
173
180
|
pd.concat(cn_pvalue_tables, ignore_index=True).to_csv(
|
|
174
181
|
cn_pvalues_file, index=False
|
|
175
182
|
)
|
|
@@ -178,9 +185,9 @@ def run_learning(
|
|
|
178
185
|
(output_dir / "SelectedCNNum.txt").write_text(
|
|
179
186
|
f"{selected_cn}\n", encoding="utf-8"
|
|
180
187
|
)
|
|
181
|
-
print(
|
|
182
|
-
f"\nSelected K={selected_cn} with
|
|
183
|
-
f"Training a fresh set of Runs 1-{config.main.final_num_runs}."
|
|
188
|
+
print(
|
|
189
|
+
f"\nSelected K={selected_cn} with CRI={selected['CRI']:.6f}. "
|
|
190
|
+
f"Training a fresh set of Runs 1-{config.main.final_num_runs}."
|
|
184
191
|
)
|
|
185
192
|
|
|
186
193
|
selected_config = _training_config(
|
|
@@ -188,22 +195,32 @@ def run_learning(
|
|
|
188
195
|
)
|
|
189
196
|
for run_index in range(1, config.main.final_num_runs + 1):
|
|
190
197
|
print(f"\n=== Selected K={selected_cn}: Run {run_index} ===")
|
|
191
|
-
train_one_run(
|
|
192
|
-
dataset,
|
|
193
|
-
selected_config,
|
|
194
|
-
run_index=run_index,
|
|
195
|
-
output_folder=selected_model_dir / f"Run{run_index}",
|
|
196
|
-
dynamic_pseudo=dataset.dynamic_pseudo,
|
|
197
|
-
base_seed=config.random_seed,
|
|
198
|
-
save_soft_assignments=True,
|
|
199
|
-
collect_hard_assignments=
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
198
|
+
_, hard_assignments = train_one_run(
|
|
199
|
+
dataset,
|
|
200
|
+
selected_config,
|
|
201
|
+
run_index=run_index,
|
|
202
|
+
output_folder=selected_model_dir / f"Run{run_index}",
|
|
203
|
+
dynamic_pseudo=dataset.dynamic_pseudo,
|
|
204
|
+
base_seed=config.random_seed,
|
|
205
|
+
save_soft_assignments=True,
|
|
206
|
+
collect_hard_assignments=config.main.final_num_runs == 1,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
if config.main.final_num_runs == 1:
|
|
210
|
+
hard_assignment_dir.mkdir()
|
|
211
|
+
for sample_index, sample in enumerate(dataset.sample_names):
|
|
212
|
+
np.savetxt(
|
|
213
|
+
hard_assignment_dir / f"{sample}_CNLabel_MajorityVoting.csv",
|
|
214
|
+
hard_assignments[sample_index],
|
|
215
|
+
delimiter=",",
|
|
216
|
+
fmt="%d",
|
|
217
|
+
)
|
|
218
|
+
else:
|
|
219
|
+
ensemble_cn(
|
|
220
|
+
input_dir=input_dir,
|
|
221
|
+
selected_model_dir=selected_model_dir,
|
|
222
|
+
hard_assignment_dir=hard_assignment_dir,
|
|
223
|
+
)
|
|
207
224
|
return LearningResult(
|
|
208
225
|
selected_cn=selected_cn,
|
|
209
226
|
dataset=dataset,
|
|
@@ -5,9 +5,11 @@ from pathlib import Path
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
from scipy.sparse import csr_matrix
|
|
7
7
|
from sklearn.neighbors import NearestNeighbors
|
|
8
|
-
import torch
|
|
9
|
-
from torch_geometric.data import Data
|
|
10
|
-
|
|
8
|
+
import torch
|
|
9
|
+
from torch_geometric.data import Data
|
|
10
|
+
|
|
11
|
+
from ..config import CytoCommunityConfig
|
|
12
|
+
|
|
11
13
|
|
|
12
14
|
class SpatialGraphDataset(torch.utils.data.Dataset):
|
|
13
15
|
"""In-memory collection of real spatial graphs."""
|
|
@@ -64,9 +66,9 @@ def _build_knn_edge_index(coordinates, knn_k):
|
|
|
64
66
|
).contiguous()
|
|
65
67
|
|
|
66
68
|
|
|
67
|
-
def build_spatial_graphs(
|
|
68
|
-
"""
|
|
69
|
-
input_folder =
|
|
69
|
+
def build_spatial_graphs(config: CytoCommunityConfig):
|
|
70
|
+
"""Build spatial graphs from the input folder associated with ``config``."""
|
|
71
|
+
input_folder = config.input_dir
|
|
70
72
|
sample_names = _read_lines(input_folder / "ImageNameList.txt")
|
|
71
73
|
labels_by_sample = {
|
|
72
74
|
sample: _read_lines(input_folder / f"{sample}_CellTypeLabel.txt")
|
|
@@ -125,7 +127,7 @@ def build_spatial_graphs(input_dir, *, knn_k):
|
|
|
125
127
|
data_list.append(Data(
|
|
126
128
|
x=x,
|
|
127
129
|
y=torch.tensor([training_label], dtype=torch.long),
|
|
128
|
-
edge_index=_build_knn_edge_index(coordinates, knn_k),
|
|
130
|
+
edge_index=_build_knn_edge_index(coordinates, config.main.knn_k),
|
|
129
131
|
))
|
|
130
132
|
|
|
131
133
|
return SpatialGraphDataset(
|
|
@@ -5,13 +5,12 @@ from pathlib import Path
|
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
import pandas as pd
|
|
8
|
-
from scipy.stats import hypergeom,
|
|
8
|
+
from scipy.stats import false_discovery_control, hypergeom, ks_2samp
|
|
9
|
+
|
|
10
|
+
from ..paths import build_paths
|
|
11
|
+
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
MIN_FLOAT_P = np.finfo(np.float64).tiny
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def read_nonempty_lines(path):
|
|
13
|
+
def read_nonempty_lines(path):
|
|
15
14
|
values = [
|
|
16
15
|
line.strip()
|
|
17
16
|
for line in Path(path).read_text(encoding="utf-8-sig").splitlines()
|
|
@@ -22,42 +21,14 @@ def read_nonempty_lines(path):
|
|
|
22
21
|
return values
|
|
23
22
|
|
|
24
23
|
|
|
25
|
-
def
|
|
26
|
-
pvalues = np.asarray(pvalues, dtype=np.float64)
|
|
27
|
-
flat = pvalues.ravel(order="F")
|
|
28
|
-
order = np.argsort(flat, kind="stable")
|
|
29
|
-
sorted_p = flat[order]
|
|
30
|
-
weighted = sorted_p.size * sorted_p / np.arange(1, sorted_p.size + 1)
|
|
31
|
-
adjusted_sorted = np.minimum.accumulate(weighted[::-1])[::-1]
|
|
32
|
-
adjusted = np.empty_like(flat)
|
|
33
|
-
adjusted[order] = adjusted_sorted
|
|
34
|
-
return adjusted.reshape(pvalues.shape, order="F")
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def bh_adjust(pvalues):
|
|
38
|
-
"""Benjamini-Hochberg adjustment for one family of CN hypotheses."""
|
|
39
|
-
pvalues = np.asarray(pvalues, dtype=np.float64)
|
|
40
|
-
clean_pvalues = np.where(np.isfinite(pvalues), pvalues, 1.0)
|
|
41
|
-
clean_pvalues = np.clip(clean_pvalues, 0.0, 1.0)
|
|
42
|
-
order = np.argsort(clean_pvalues, kind="stable")
|
|
43
|
-
sorted_pvalues = clean_pvalues[order]
|
|
44
|
-
ranks = np.arange(1, sorted_pvalues.size + 1, dtype=np.float64)
|
|
45
|
-
adjusted_sorted = np.minimum.accumulate(
|
|
46
|
-
(sorted_pvalues.size * sorted_pvalues / ranks)[::-1]
|
|
47
|
-
)[::-1]
|
|
48
|
-
adjusted = np.empty_like(clean_pvalues)
|
|
49
|
-
adjusted[order] = np.clip(adjusted_sorted, 0.0, 1.0)
|
|
50
|
-
return adjusted
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def calculate_enrichment_matrix(
|
|
24
|
+
def calculate_enrichment_matrix(
|
|
54
25
|
cell_labels,
|
|
55
26
|
cn_labels,
|
|
56
27
|
cell_types,
|
|
57
28
|
num_cn,
|
|
58
29
|
minimum_adjusted_p,
|
|
59
30
|
):
|
|
60
|
-
"""
|
|
31
|
+
"""Calculate BH-adjusted cell-type enrichment scores for one sample."""
|
|
61
32
|
cell_labels = np.asarray(cell_labels, dtype=str)
|
|
62
33
|
cn_labels = np.asarray(cn_labels, dtype=np.int64)
|
|
63
34
|
if cell_labels.ndim != 1 or cn_labels.ndim != 1:
|
|
@@ -96,70 +67,47 @@ def calculate_enrichment_matrix(
|
|
|
96
67
|
int(cell_type_totals[cell_type_index]),
|
|
97
68
|
int(cn_totals[cn_index]),
|
|
98
69
|
)
|
|
99
|
-
raw_pvalues[cell_type_index, cn_index] =
|
|
70
|
+
raw_pvalues[cell_type_index, cn_index] = upper_tail
|
|
100
71
|
|
|
101
|
-
adjusted =
|
|
72
|
+
adjusted = false_discovery_control(
|
|
73
|
+
raw_pvalues.ravel(order="F"), method="bh"
|
|
74
|
+
).reshape(raw_pvalues.shape, order="F")
|
|
102
75
|
adjusted[adjusted < minimum_adjusted_p] = minimum_adjusted_p
|
|
103
76
|
return -np.log10(adjusted)
|
|
104
77
|
|
|
105
78
|
|
|
106
|
-
def calculate_enrichment(
|
|
107
|
-
dataset,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
output_dir / "CellTypeVec_List.csv", index=False
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
rows = []
|
|
131
|
-
for sample_index, sample in enumerate(samples):
|
|
132
|
-
cell_labels = (
|
|
133
|
-
pd.read_csv(
|
|
134
|
-
dataset / f"{sample}_CellTypeLabel.txt",
|
|
79
|
+
def calculate_enrichment(
|
|
80
|
+
dataset,
|
|
81
|
+
cell_types,
|
|
82
|
+
cn_labels_by_sample,
|
|
83
|
+
output_dir,
|
|
84
|
+
num_cn,
|
|
85
|
+
minimum_adjusted_p,
|
|
86
|
+
):
|
|
87
|
+
dataset = Path(dataset)
|
|
88
|
+
output_dir = Path(output_dir)
|
|
89
|
+
samples = read_nonempty_lines(dataset / "ImageNameList.txt")
|
|
90
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
91
|
+
pd.DataFrame({"Var1": cell_types}).to_csv(
|
|
92
|
+
output_dir / "CellTypeVec_List.csv", index=False
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
matrices = {}
|
|
96
|
+
for sample in samples:
|
|
97
|
+
cell_labels = (
|
|
98
|
+
pd.read_csv(
|
|
99
|
+
dataset / f"{sample}_CellTypeLabel.txt",
|
|
135
100
|
sep="\t",
|
|
136
101
|
header=None,
|
|
137
102
|
dtype=str,
|
|
138
103
|
)
|
|
139
|
-
.iloc[:, 0]
|
|
140
|
-
.to_numpy()
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
)
|
|
147
|
-
cn_labels = np.asarray(
|
|
148
|
-
hard_assignments[sample_index], dtype=np.int64
|
|
149
|
-
)
|
|
150
|
-
else:
|
|
151
|
-
cn_labels = (
|
|
152
|
-
pd.read_csv(
|
|
153
|
-
cn_label_dir / f"{sample}_CNLabel_MajorityVoting.csv",
|
|
154
|
-
header=None,
|
|
155
|
-
dtype=np.int64,
|
|
156
|
-
)
|
|
157
|
-
.iloc[:, 0]
|
|
158
|
-
.to_numpy()
|
|
159
|
-
)
|
|
160
|
-
matrix = calculate_enrichment_matrix(
|
|
161
|
-
cell_labels,
|
|
162
|
-
cn_labels,
|
|
104
|
+
.iloc[:, 0]
|
|
105
|
+
.to_numpy()
|
|
106
|
+
)
|
|
107
|
+
cn_labels = np.asarray(cn_labels_by_sample[sample], dtype=np.int64)
|
|
108
|
+
matrix = calculate_enrichment_matrix(
|
|
109
|
+
cell_labels,
|
|
110
|
+
cn_labels,
|
|
163
111
|
cell_types,
|
|
164
112
|
num_cn,
|
|
165
113
|
minimum_adjusted_p,
|
|
@@ -167,11 +115,25 @@ def calculate_enrichment(
|
|
|
167
115
|
np.savetxt(
|
|
168
116
|
output_dir / f"{sample}_EnrichScoreMatrix.csv",
|
|
169
117
|
matrix,
|
|
170
|
-
delimiter=",",
|
|
171
|
-
fmt="%.15g",
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
return
|
|
118
|
+
delimiter=",",
|
|
119
|
+
fmt="%.15g",
|
|
120
|
+
)
|
|
121
|
+
matrices[sample] = matrix
|
|
122
|
+
return matrices
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _load_cn_labels(samples, cn_label_dir):
|
|
126
|
+
cn_label_dir = Path(cn_label_dir)
|
|
127
|
+
return {
|
|
128
|
+
sample: pd.read_csv(
|
|
129
|
+
cn_label_dir / f"{sample}_CNLabel_MajorityVoting.csv",
|
|
130
|
+
header=None,
|
|
131
|
+
dtype=np.int64,
|
|
132
|
+
)
|
|
133
|
+
.iloc[:, 0]
|
|
134
|
+
.to_numpy()
|
|
135
|
+
for sample in samples
|
|
136
|
+
}
|
|
175
137
|
|
|
176
138
|
|
|
177
139
|
def export_enrichment_long(dataset, enrichment_dir, output_file):
|
|
@@ -221,82 +183,51 @@ def overlap_coefficient(first, second):
|
|
|
221
183
|
return len(first & second) / smaller_size if smaller_size else 0.0
|
|
222
184
|
|
|
223
185
|
|
|
224
|
-
def
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
for cn in range(matrix.shape[1])
|
|
246
|
-
]
|
|
247
|
-
return dominant_sets, num_cn
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
def _summarize_ttest(cn, real_values, background_values):
|
|
251
|
-
real_values = np.asarray(real_values, dtype=float)
|
|
252
|
-
background_values = np.asarray(background_values, dtype=float)
|
|
253
|
-
if len(real_values) >= 2 and len(background_values) >= 2:
|
|
254
|
-
result = ttest_ind(
|
|
255
|
-
real_values,
|
|
256
|
-
background_values,
|
|
257
|
-
equal_var=False,
|
|
258
|
-
alternative="greater",
|
|
259
|
-
)
|
|
260
|
-
t_statistic, p_value = float(result.statistic), float(result.pvalue)
|
|
261
|
-
else:
|
|
262
|
-
t_statistic, p_value = np.nan, np.nan
|
|
263
|
-
real_mean = np.mean(real_values) if len(real_values) else np.nan
|
|
264
|
-
background_mean = (
|
|
265
|
-
np.mean(background_values) if len(background_values) else np.nan
|
|
266
|
-
)
|
|
267
|
-
return {
|
|
268
|
-
"CN": cn,
|
|
269
|
-
"n_real": len(real_values),
|
|
270
|
-
"n_background": len(background_values),
|
|
271
|
-
"real_mean": real_mean,
|
|
272
|
-
"real_std": np.std(real_values, ddof=1) if len(real_values) > 1 else np.nan,
|
|
273
|
-
"background_mean": background_mean,
|
|
274
|
-
"background_std": (
|
|
275
|
-
np.std(background_values, ddof=1)
|
|
276
|
-
if len(background_values) > 1
|
|
277
|
-
else np.nan
|
|
278
|
-
),
|
|
279
|
-
"delta_mean": real_mean - background_mean,
|
|
280
|
-
"t_statistic": t_statistic,
|
|
281
|
-
"p_value_one_sided": p_value,
|
|
186
|
+
def _summarize_ks(cn, real_values, background_values):
|
|
187
|
+
real_values = np.asarray(real_values, dtype=float)
|
|
188
|
+
background_values = np.asarray(background_values, dtype=float)
|
|
189
|
+
result = ks_2samp(
|
|
190
|
+
real_values,
|
|
191
|
+
background_values,
|
|
192
|
+
alternative="less",
|
|
193
|
+
method="asymp",
|
|
194
|
+
)
|
|
195
|
+
real_mean = float(np.mean(real_values))
|
|
196
|
+
background_mean = float(np.mean(background_values))
|
|
197
|
+
return {
|
|
198
|
+
"CN": cn,
|
|
199
|
+
"n_real": len(real_values),
|
|
200
|
+
"n_background": len(background_values),
|
|
201
|
+
"real_mean": real_mean,
|
|
202
|
+
"background_mean": background_mean,
|
|
203
|
+
"delta_mean": real_mean - background_mean,
|
|
204
|
+
"KSStatistic": float(result.statistic),
|
|
205
|
+
"KSLocation": float(result.statistic_location),
|
|
206
|
+
"p_value_one_sided": float(result.pvalue),
|
|
282
207
|
}
|
|
283
208
|
|
|
284
209
|
|
|
285
|
-
def calculate_recurrence(
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
output_dir,
|
|
210
|
+
def calculate_recurrence(
|
|
211
|
+
enrichment_matrices,
|
|
212
|
+
cell_types,
|
|
213
|
+
output_dir,
|
|
289
214
|
score_threshold,
|
|
290
215
|
recurrence_p_threshold,
|
|
291
|
-
n_random,
|
|
292
|
-
seed,
|
|
293
|
-
):
|
|
294
|
-
samples =
|
|
295
|
-
if len(samples) < 2:
|
|
296
|
-
raise ValueError("At least two samples are required for recurrence analysis")
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
216
|
+
n_random,
|
|
217
|
+
seed,
|
|
218
|
+
):
|
|
219
|
+
samples = list(enrichment_matrices)
|
|
220
|
+
if len(samples) < 2:
|
|
221
|
+
raise ValueError("At least two samples are required for recurrence analysis")
|
|
222
|
+
cell_types = np.asarray(cell_types, dtype=str)
|
|
223
|
+
num_cn = enrichment_matrices[samples[0]].shape[1]
|
|
224
|
+
dominant_sets = {
|
|
225
|
+
sample: [
|
|
226
|
+
set(cell_types[matrix[:, cn] > score_threshold])
|
|
227
|
+
for cn in range(num_cn)
|
|
228
|
+
]
|
|
229
|
+
for sample, matrix in enrichment_matrices.items()
|
|
230
|
+
}
|
|
300
231
|
sample_pairs = list(combinations(samples, 2))
|
|
301
232
|
real_df = pd.DataFrame(
|
|
302
233
|
[
|
|
@@ -322,35 +253,20 @@ def calculate_recurrence(
|
|
|
322
253
|
)
|
|
323
254
|
background_df = pd.DataFrame({"OverlapCoefficient": background_values})
|
|
324
255
|
|
|
325
|
-
|
|
256
|
+
ks_rows = []
|
|
326
257
|
for cn in [f"CN{index + 1}" for index in range(num_cn)]:
|
|
327
258
|
values = real_df.loc[
|
|
328
259
|
real_df["CN"] == cn, "OverlapCoefficient"
|
|
329
260
|
].to_numpy()
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
ttest_df.loc[cn_mask, "p_value_one_sided"].to_numpy()
|
|
340
|
-
)
|
|
341
|
-
ttest_df["p_adjusted_bh"] = np.nan
|
|
342
|
-
ttest_df["RecurrenceScore"] = np.nan
|
|
343
|
-
ttest_df["SignificantRecurrence"] = False
|
|
344
|
-
ttest_df.loc[cn_mask, "p_adjusted_bh"] = adjusted_pvalues
|
|
345
|
-
ttest_df.loc[cn_mask, "RecurrenceScore"] = -np.log10(
|
|
346
|
-
np.maximum(adjusted_pvalues, MIN_FLOAT_P)
|
|
347
|
-
)
|
|
348
|
-
ttest_df.loc[cn_mask, "SignificantRecurrence"] = (
|
|
349
|
-
(
|
|
350
|
-
ttest_df.loc[cn_mask, "p_adjusted_bh"]
|
|
351
|
-
< recurrence_p_threshold
|
|
352
|
-
)
|
|
353
|
-
& (ttest_df.loc[cn_mask, "delta_mean"] > 0)
|
|
261
|
+
ks_rows.append(_summarize_ks(cn, values, background_values))
|
|
262
|
+
ks_df = pd.DataFrame(ks_rows)
|
|
263
|
+
ks_df["p_adjusted_bh"] = false_discovery_control(
|
|
264
|
+
ks_df["p_value_one_sided"].to_numpy(), method="bh"
|
|
265
|
+
)
|
|
266
|
+
ks_df["RecurrenceScore"] = ks_df["KSStatistic"] * 100.0
|
|
267
|
+
ks_df["SignificantRecurrence"] = (
|
|
268
|
+
(ks_df["p_adjusted_bh"] < recurrence_p_threshold)
|
|
269
|
+
& (ks_df["delta_mean"] > 0)
|
|
354
270
|
)
|
|
355
271
|
|
|
356
272
|
output_dir = Path(output_dir)
|
|
@@ -359,68 +275,90 @@ def calculate_recurrence(
|
|
|
359
275
|
background_df.to_csv(
|
|
360
276
|
output_dir / "BackgroundDistribution_randomCN.csv", index=False
|
|
361
277
|
)
|
|
362
|
-
|
|
363
|
-
return real_df, background_df,
|
|
278
|
+
ks_df.to_csv(output_dir / "KSTest_Results.csv", index=False)
|
|
279
|
+
return real_df, background_df, ks_df
|
|
364
280
|
|
|
365
281
|
|
|
366
|
-
def evaluate_cn_num(
|
|
282
|
+
def evaluate_cn_num(
|
|
367
283
|
num_cn,
|
|
368
284
|
dataset,
|
|
369
285
|
celltype_file,
|
|
370
286
|
output_dir,
|
|
371
287
|
enrichment_p_threshold,
|
|
372
288
|
minimum_adjusted_p,
|
|
373
|
-
recurrence_p_threshold,
|
|
374
|
-
n_random,
|
|
375
|
-
seed,
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
):
|
|
289
|
+
recurrence_p_threshold,
|
|
290
|
+
n_random,
|
|
291
|
+
seed,
|
|
292
|
+
cn_labels_by_sample,
|
|
293
|
+
):
|
|
379
294
|
"""Calculate the recurrence-weighted CN resolution index and per-CN tests."""
|
|
380
295
|
output_dir = Path(output_dir)
|
|
381
296
|
enrichment_dir = output_dir / "EnrichScoreMatrix"
|
|
382
297
|
overlap_dir = output_dir / "OverlapCoefficient"
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
)
|
|
392
|
-
real_df, _,
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
overlap_dir,
|
|
396
|
-
score_threshold=-np.log10(enrichment_p_threshold),
|
|
298
|
+
cell_types = read_nonempty_lines(celltype_file)
|
|
299
|
+
enrichment_matrices = calculate_enrichment(
|
|
300
|
+
dataset=dataset,
|
|
301
|
+
cell_types=cell_types,
|
|
302
|
+
cn_labels_by_sample=cn_labels_by_sample,
|
|
303
|
+
output_dir=enrichment_dir,
|
|
304
|
+
num_cn=num_cn,
|
|
305
|
+
minimum_adjusted_p=minimum_adjusted_p,
|
|
306
|
+
)
|
|
307
|
+
real_df, _, per_cn = calculate_recurrence(
|
|
308
|
+
enrichment_matrices=enrichment_matrices,
|
|
309
|
+
cell_types=cell_types,
|
|
310
|
+
output_dir=overlap_dir,
|
|
311
|
+
score_threshold=-np.log10(enrichment_p_threshold),
|
|
397
312
|
recurrence_p_threshold=recurrence_p_threshold,
|
|
398
313
|
n_random=n_random,
|
|
399
314
|
seed=seed,
|
|
400
315
|
)
|
|
401
|
-
|
|
402
|
-
significant_mask = per_cn["SignificantRecurrence"].astype(bool)
|
|
316
|
+
significant_mask = per_cn["SignificantRecurrence"].astype(bool)
|
|
403
317
|
significant_cn = int(significant_mask.sum())
|
|
404
318
|
significant_fraction = significant_cn / num_cn
|
|
405
319
|
mean_rs = float(per_cn["RecurrenceScore"].mean())
|
|
406
320
|
mean_oc = float(real_df["OverlapCoefficient"].mean())
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
"
|
|
412
|
-
"
|
|
413
|
-
"
|
|
414
|
-
"
|
|
415
|
-
"MeanOC": mean_oc,
|
|
416
|
-
"Score": score,
|
|
321
|
+
cri = float(significant_fraction**2 * mean_rs)
|
|
322
|
+
result = {
|
|
323
|
+
"NumCN": int(num_cn),
|
|
324
|
+
"Ksig": significant_cn,
|
|
325
|
+
"SignificantFraction": significant_fraction,
|
|
326
|
+
"MeanOC": mean_oc,
|
|
327
|
+
"MeanRS": mean_rs,
|
|
328
|
+
"CRI": cri,
|
|
417
329
|
}
|
|
418
330
|
pd.DataFrame([result]).to_csv(output_dir / "CNNumScore.csv", index=False)
|
|
419
|
-
return result, per_cn
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
def
|
|
423
|
-
"""
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
331
|
+
return result, per_cn
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def evaluate_selected_cn(result, config):
|
|
335
|
+
"""Evaluate the selected model using the configured project layout."""
|
|
336
|
+
paths = build_paths(config)
|
|
337
|
+
cn_labels_by_sample = _load_cn_labels(
|
|
338
|
+
result.dataset.sample_names, result.hard_assignment_dir
|
|
339
|
+
)
|
|
340
|
+
metrics, recurrence = evaluate_cn_num(
|
|
341
|
+
num_cn=result.selected_cn,
|
|
342
|
+
dataset=result.input_dir,
|
|
343
|
+
cn_labels_by_sample=cn_labels_by_sample,
|
|
344
|
+
celltype_file=result.unique_celltypes_file,
|
|
345
|
+
output_dir=paths.downstream_data,
|
|
346
|
+
enrichment_p_threshold=config.enrichment.p_value_threshold,
|
|
347
|
+
minimum_adjusted_p=config.enrichment.minimum_adjusted_p,
|
|
348
|
+
recurrence_p_threshold=config.recurrence.recurrence_p_threshold,
|
|
349
|
+
n_random=config.recurrence.null_draws,
|
|
350
|
+
seed=config.random_seed,
|
|
351
|
+
)
|
|
352
|
+
export_enrichment_long(
|
|
353
|
+
dataset=result.input_dir,
|
|
354
|
+
enrichment_dir=paths.enrichment,
|
|
355
|
+
output_file=paths.communication_config / "EnrichScoreMatrix_long.csv",
|
|
356
|
+
)
|
|
357
|
+
return metrics, recurrence
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def select_cn_num(results):
|
|
361
|
+
"""Select maximum CRI, using smaller K as the deterministic tie-break."""
|
|
362
|
+
if not results:
|
|
363
|
+
raise ValueError("No CN-number results were supplied")
|
|
364
|
+
return min(results, key=lambda row: (-row["CRI"], row["NumCN"]))
|
|
@@ -212,14 +212,19 @@ def train_one_run(
|
|
|
212
212
|
_, _, _, assignment_logits, _ = model(
|
|
213
213
|
data.x, data.edge_index, data.batch
|
|
214
214
|
)
|
|
215
|
-
if
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
215
|
+
if save_soft_assignments:
|
|
216
|
+
soft_assignments = torch.softmax(
|
|
217
|
+
assignment_logits, dim=-1
|
|
218
|
+
).cpu().numpy()
|
|
219
|
+
np.savetxt(
|
|
220
|
+
output_folder / f"ClusterAssignMatrix1_{index}.csv",
|
|
221
|
+
soft_assignments,
|
|
222
|
+
delimiter=",",
|
|
223
|
+
)
|
|
224
|
+
if collect_hard_assignments:
|
|
225
|
+
hard_assignments[index] = (
|
|
226
|
+
np.argmax(soft_assignments, axis=-1) + 1
|
|
227
|
+
if save_soft_assignments
|
|
228
|
+
else torch.argmax(assignment_logits, dim=-1).cpu().numpy() + 1
|
|
229
|
+
)
|
|
225
230
|
return summary, hard_assignments
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cytocommunity2
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Weakly supervised cellular-neighborhood learning for spatial omics
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -10,7 +10,7 @@ Requires-Dist: numpy<2,>=1.23
|
|
|
10
10
|
Requires-Dist: pandas<3,>=1.5
|
|
11
11
|
Requires-Dist: sci-palettes
|
|
12
12
|
Requires-Dist: scikit-learn>=1.1
|
|
13
|
-
Requires-Dist: scipy>=1.
|
|
13
|
+
Requires-Dist: scipy>=1.11
|
|
14
14
|
Requires-Dist: seaborn>=0.12
|
|
15
15
|
Requires-Dist: torch-geometric>=2.0
|
|
16
16
|
Provides-Extra: downstream
|
|
@@ -81,7 +81,6 @@ conda create -n cytocommunity2 python=3.10
|
|
|
81
81
|
conda activate cytocommunity2
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
Install PyTorch for your compute platform before installing CytoCommunity2.
|
|
85
84
|
For an NVIDIA GPU on Windows or Linux, first install the appropriate
|
|
86
85
|
CUDA-enabled PyTorch build from the
|
|
87
86
|
[official PyTorch installation page](https://pytorch.org/get-started/locally/).
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/communication/between.py
RENAMED
|
File without changes
|
{cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/communication/cca.py
RENAMED
|
File without changes
|
{cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/communication/utils.py
RENAMED
|
File without changes
|
{cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/communication/within.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/plotting/__init__.py
RENAMED
|
File without changes
|
{cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/plotting/cn_selection.py
RENAMED
|
File without changes
|
{cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/plotting/coherence.py
RENAMED
|
File without changes
|
{cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/plotting/common.py
RENAMED
|
File without changes
|
|
File without changes
|
{cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/plotting/composition.py
RENAMED
|
File without changes
|
{cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/plotting/dominant.py
RENAMED
|
File without changes
|
{cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/plotting/dotplots.py
RENAMED
|
File without changes
|
{cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2/downstream/plotting/recurrence.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cytocommunity2-0.1.2 → cytocommunity2-0.1.4}/src/cytocommunity2.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|