spatialrna 0.1.2__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.
- spatialrna-0.1.2/PKG-INFO +52 -0
- spatialrna-0.1.2/README.md +31 -0
- spatialrna-0.1.2/pyproject.toml +48 -0
- spatialrna-0.1.2/setup.cfg +4 -0
- spatialrna-0.1.2/spatialrna/__init__.py +1 -0
- spatialrna-0.1.2/spatialrna/spatialrna.py +335 -0
- spatialrna-0.1.2/spatialrna.egg-info/PKG-INFO +52 -0
- spatialrna-0.1.2/spatialrna.egg-info/SOURCES.txt +13 -0
- spatialrna-0.1.2/spatialrna.egg-info/dependency_links.txt +1 -0
- spatialrna-0.1.2/spatialrna.egg-info/requires.txt +9 -0
- spatialrna-0.1.2/spatialrna.egg-info/top_level.txt +1 -0
- spatialrna-0.1.2/tests/test_data.py +225 -0
- spatialrna-0.1.2/tests/test_reload.py +137 -0
- spatialrna-0.1.2/tests/test_subgraph.py +98 -0
- spatialrna-0.1.2/tests/test_subgraph_mode.py +136 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: spatialrna
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: A package for spatial RNA graph data processing and analysis.
|
|
5
|
+
Author-email: Ruqian Lyu <rlyu@svi.edu.au>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: repository, https://gitlab.svi.edu.au/biocellgen-private/spatialrna
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: torch
|
|
14
|
+
Requires-Dist: torch_geometric
|
|
15
|
+
Requires-Dist: pandas
|
|
16
|
+
Requires-Dist: numpy
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: pytest>=6.0; extra == "dev"
|
|
19
|
+
Requires-Dist: twine>=4.0.2; extra == "dev"
|
|
20
|
+
Requires-Dist: build; extra == "dev"
|
|
21
|
+
|
|
22
|
+
## A package that provides easier inferface to GNN models for single-molecule ST data
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
## Pre-requisite packages installation
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
## Using Conda and pip
|
|
30
|
+
|
|
31
|
+
conda create -n dev-spatialrna
|
|
32
|
+
conda activate dev-spatialrna
|
|
33
|
+
conda install python=3.10.0
|
|
34
|
+
|
|
35
|
+
## torch 2.4.0 cuda version 12.4
|
|
36
|
+
pip install torch==2.4.0 --index-url https://download.pytorch.org/whl/cu124
|
|
37
|
+
|
|
38
|
+
## torch_geometric=2.6.1 https://pytorch-geometric.readthedocs.io/en/stable/install/installation.html#installation-via-pypi
|
|
39
|
+
|
|
40
|
+
pip install torch_geometric==2.6.1
|
|
41
|
+
|
|
42
|
+
## Additional useful libraries https://pytorch-geometric.readthedocs.io/en/stable/install/installation.html#additional-libraries
|
|
43
|
+
|
|
44
|
+
pip install pyg_lib torch_scatter torch_sparse torch_cluster torch_spline_conv -f https://data.pyg.org/whl/torch-2.4.0+cu124.html
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Install spatialrna
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
pip install spatialrna
|
|
52
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
## A package that provides easier inferface to GNN models for single-molecule ST data
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
## Pre-requisite packages installation
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
## Using Conda and pip
|
|
9
|
+
|
|
10
|
+
conda create -n dev-spatialrna
|
|
11
|
+
conda activate dev-spatialrna
|
|
12
|
+
conda install python=3.10.0
|
|
13
|
+
|
|
14
|
+
## torch 2.4.0 cuda version 12.4
|
|
15
|
+
pip install torch==2.4.0 --index-url https://download.pytorch.org/whl/cu124
|
|
16
|
+
|
|
17
|
+
## torch_geometric=2.6.1 https://pytorch-geometric.readthedocs.io/en/stable/install/installation.html#installation-via-pypi
|
|
18
|
+
|
|
19
|
+
pip install torch_geometric==2.6.1
|
|
20
|
+
|
|
21
|
+
## Additional useful libraries https://pytorch-geometric.readthedocs.io/en/stable/install/installation.html#additional-libraries
|
|
22
|
+
|
|
23
|
+
pip install pyg_lib torch_scatter torch_sparse torch_cluster torch_spline_conv -f https://data.pyg.org/whl/torch-2.4.0+cu124.html
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Install spatialrna
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
pip install spatialrna
|
|
31
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "spatialrna"
|
|
7
|
+
version = "0.1.2"
|
|
8
|
+
description="A package for spatial RNA graph data processing and analysis."
|
|
9
|
+
readme = {file = "README.md", content-type = "text/markdown"}
|
|
10
|
+
authors = [
|
|
11
|
+
{name = "Ruqian Lyu", email = "rlyu@svi.edu.au"},
|
|
12
|
+
]
|
|
13
|
+
license = {text = "MIT"}
|
|
14
|
+
classifiers = [
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Programming Language :: Python :: 3.10",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
dependencies = [
|
|
21
|
+
"torch",
|
|
22
|
+
"torch_geometric",
|
|
23
|
+
"pandas",
|
|
24
|
+
"numpy"
|
|
25
|
+
]
|
|
26
|
+
requires-python = ">=3.10"
|
|
27
|
+
|
|
28
|
+
[tool.setuptools]
|
|
29
|
+
include-package-data = false
|
|
30
|
+
|
|
31
|
+
[tool.setuptools.packages.find]
|
|
32
|
+
exclude = ["data", "tests"]
|
|
33
|
+
include = ["spatialrna"]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# BETA-feature "Automatic discovery"
|
|
37
|
+
# Leaving out any information about the package structure to enable automatic discovery. src-layout works out of the box
|
|
38
|
+
# https://setuptools.pypa.io/en/latest/userguide/package_discovery.html#automatic-discovery
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
repository = "https://gitlab.svi.edu.au/biocellgen-private/spatialrna"
|
|
42
|
+
|
|
43
|
+
[project.optional-dependencies]
|
|
44
|
+
dev = [
|
|
45
|
+
"pytest>=6.0",
|
|
46
|
+
"twine>=4.0.2",
|
|
47
|
+
"build",
|
|
48
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .spatialrna import SpatialRNA
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
from torch_geometric.data import InMemoryDataset, Dataset, Data
|
|
2
|
+
from typing import Callable, Optional, List
|
|
3
|
+
from torch_geometric.nn import radius_graph
|
|
4
|
+
import os
|
|
5
|
+
import os.path as osp
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import torch
|
|
8
|
+
from torch_geometric.io import fs
|
|
9
|
+
from torch_geometric.loader import LinkNeighborLoader
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## feature matrix. Reduce mem and I/O by mapping gene label to int IDs. Delay mapping to 1-D tensors when training.
|
|
14
|
+
## TODO: warnings about different num_tiles, radius_r arguments if only loading not processing
|
|
15
|
+
|
|
16
|
+
def files_exist(files: List[str]) -> bool:
|
|
17
|
+
# NOTE: We return `False` in case `files` is empty, leading to a
|
|
18
|
+
# re-processing of files on every instantiation.
|
|
19
|
+
return len(files) != 0 and all([fs.exists(f) for f in files])
|
|
20
|
+
|
|
21
|
+
class SpatialRNA(InMemoryDataset):
|
|
22
|
+
r"""A spatial RNA graph dataset. One dataset per tissue sample, and optionally creates
|
|
23
|
+
tiles from a tissue area. For each generated data.pt, it contains the nodes in core areas for
|
|
24
|
+
making inference for transcripts with complete neighbourhoods.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
root (str): Root directory where the dataset should be saved.
|
|
29
|
+
sample_name (str): The name of this sample. Appended to the processed [sample_name]_data.pt.
|
|
30
|
+
Using basename of root dir if not supplied.
|
|
31
|
+
radius_r (float): The radius for building RNA graphs.
|
|
32
|
+
tile_by_dim (str): Along which dimension to make tiles. It should be a column in the transcript data.frame.
|
|
33
|
+
num_tiles (int): The number of tiles to create from this tissue sample.
|
|
34
|
+
process_mode (str): The mode of process can be either "tile" or "subgraph".
|
|
35
|
+
load_type (str): If :obj:`"tile"`, load the tile graph data.
|
|
36
|
+
If :obj:`"subgraph"`, load the tile subgraph data.
|
|
37
|
+
load_tile_id (int): Select one tile to load.
|
|
38
|
+
process_tile_ids (List[int]): Select tiles to process including generating tile graph and subgraph.
|
|
39
|
+
one_hot_encoding (dict): Dictionary that mapps gene names to integer (or one-hot-encoding tensors).
|
|
40
|
+
It is essential to apply the same dictionary for all samples that are analyzed together.
|
|
41
|
+
transform (callable, optional): A function/transform that takes in an
|
|
42
|
+
:obj:`torch_geometric.data.Data` object and returns a transformed
|
|
43
|
+
version. The data object will be transformed before every access.
|
|
44
|
+
(default: :obj:`None`)
|
|
45
|
+
pre_transform (callable, optional): A function/transform that takes in
|
|
46
|
+
an :obj:`torch_geometric.data.Data` object and returns a
|
|
47
|
+
transformed version. The data object will be transformed before
|
|
48
|
+
being saved to disk. (default: :obj:`None`)
|
|
49
|
+
force_reload (bool, optional): Whether to re-process the dataset.
|
|
50
|
+
(default: :obj:`False`)
|
|
51
|
+
force_resample (bool, optional): Whether to re-process the tile dataset to generate subgraphs.
|
|
52
|
+
(default: :obj:`False`)
|
|
53
|
+
subgraph_mode (str): The mode of subgraph generation can be either "link_based" or "node_based".
|
|
54
|
+
(default: :obj:`link_based`)
|
|
55
|
+
num_sampled_edges (int): sample this many edge_index as positive edges when generating subgraph from a tile (used in LinkNeighborLoader).
|
|
56
|
+
num_seed_nodes:(int, optional): Number of seed nodes for generating subgraphs.
|
|
57
|
+
(default: :obj:`5000`)
|
|
58
|
+
num_walks:(int, optional): Number of walks for generating subgraphs.
|
|
59
|
+
(default: :obj:`5`)
|
|
60
|
+
|
|
61
|
+
log (bool, optional): Whether to print any console output while
|
|
62
|
+
downloading and processing the dataset. (default: :obj:`True`)
|
|
63
|
+
**kwargs (optional): Additional arguments of
|
|
64
|
+
:class:`torch.utils.data.LinkNeighborLoader`, such as :obj:`num_neighbors`
|
|
65
|
+
"""
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
root: str,
|
|
69
|
+
sample_name: Optional[str] = None,
|
|
70
|
+
radius_r: float = 3.0,
|
|
71
|
+
dim_x: str = "X",
|
|
72
|
+
dim_y: str = "Y",
|
|
73
|
+
tile_by_dim: str = "Y",
|
|
74
|
+
num_tiles: int = 1,
|
|
75
|
+
process_mode: str = "tile",
|
|
76
|
+
load_type: str = "tile",
|
|
77
|
+
load_tile_id: int = 0,
|
|
78
|
+
pad_hops: float = 2,
|
|
79
|
+
feature_col = "gene",
|
|
80
|
+
max_num_neighbors: int = 500,
|
|
81
|
+
process_tile_ids: List[int] = None,
|
|
82
|
+
one_hot_encoding: dict = None,
|
|
83
|
+
subgraph_mode: str = "link_based",
|
|
84
|
+
num_sampled_edges: int = 5000,
|
|
85
|
+
num_seed_nodes: int = 5000,
|
|
86
|
+
num_walks: int = 5,
|
|
87
|
+
transform = None,
|
|
88
|
+
pre_transform = None,
|
|
89
|
+
force_reload: bool = False,
|
|
90
|
+
force_resample: bool = False,
|
|
91
|
+
log: bool= True,
|
|
92
|
+
seed: int = 100,
|
|
93
|
+
**kwargs,
|
|
94
|
+
|
|
95
|
+
)-> None:
|
|
96
|
+
assert load_type in ["tile","subgraph","blank"]
|
|
97
|
+
assert process_mode in ["tile", "subgraph"]
|
|
98
|
+
assert subgraph_mode in ["link_based", "node_based"]
|
|
99
|
+
|
|
100
|
+
self.num_tiles = num_tiles
|
|
101
|
+
if sample_name is not None:
|
|
102
|
+
self.sample_name = sample_name
|
|
103
|
+
else:
|
|
104
|
+
self.sample_name = osp.basename(root)
|
|
105
|
+
|
|
106
|
+
self.radius_r = radius_r
|
|
107
|
+
self.load_tile_id = load_tile_id
|
|
108
|
+
self.pad_hops = pad_hops
|
|
109
|
+
self.max_num_neighbors = max_num_neighbors
|
|
110
|
+
self.one_hot_encoding = one_hot_encoding
|
|
111
|
+
self.seed = seed
|
|
112
|
+
self.feature_col = feature_col
|
|
113
|
+
self.num_sampled_edges = num_sampled_edges
|
|
114
|
+
self.load_type = load_type
|
|
115
|
+
self.process_mode = process_mode
|
|
116
|
+
self.num_seed_nodes = num_seed_nodes
|
|
117
|
+
self.num_walks = num_walks
|
|
118
|
+
|
|
119
|
+
assert tile_by_dim in [dim_x,dim_y]
|
|
120
|
+
assert one_hot_encoding is not None
|
|
121
|
+
|
|
122
|
+
self.dim_x = dim_x
|
|
123
|
+
self.dim_y = dim_y
|
|
124
|
+
self.tile_by_dim = tile_by_dim
|
|
125
|
+
self.log = log
|
|
126
|
+
self.force_resample = force_resample
|
|
127
|
+
self.subgraph_mode = subgraph_mode
|
|
128
|
+
|
|
129
|
+
if process_tile_ids is None:
|
|
130
|
+
self.process_tile_ids = [load_tile_id]
|
|
131
|
+
else:
|
|
132
|
+
self.process_tile_ids = process_tile_ids
|
|
133
|
+
# super init, process tiles, if tiles.pt not exist, and regenerate if force_reload=True.
|
|
134
|
+
super().__init__(root, transform, pre_transform,force_reload=force_reload, log=log)
|
|
135
|
+
# subsequently generates the subgraphs if requested
|
|
136
|
+
if process_mode == "subgraph":
|
|
137
|
+
self.generate_subgraph(**kwargs)
|
|
138
|
+
|
|
139
|
+
if load_type == 'subgraph':
|
|
140
|
+
load_file_name = osp.join(self.subgraph_dir,f'{self.sample_name}_subgraph_data_tile{self.load_tile_id}.pt')
|
|
141
|
+
# if self.force_resample or not files_exist([load_file_name]):
|
|
142
|
+
# self.generate_subgraph(**kwargs)
|
|
143
|
+
elif load_type == 'tile':
|
|
144
|
+
load_file_name = osp.join(self.processed_dir,f'{self.sample_name}_data_tile{self.load_tile_id}.pt')
|
|
145
|
+
else:
|
|
146
|
+
load_file_name = None
|
|
147
|
+
|
|
148
|
+
if load_file_name is None:
|
|
149
|
+
print(load_file_name," not exist, nothing loaded")
|
|
150
|
+
elif files_exist([load_file_name]):
|
|
151
|
+
print("loading from file ",load_file_name)
|
|
152
|
+
self.load(load_file_name)
|
|
153
|
+
else:
|
|
154
|
+
print(load_file_name," not exist, nothing loaded")
|
|
155
|
+
None
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def subgraph_dir(self) -> str:
|
|
160
|
+
return osp.join(self.root,"subgraph")
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def raw_file_names(self):
|
|
164
|
+
return [f'{self.sample_name}.csv']
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def subgraph_file_names(self):
|
|
168
|
+
return [f'{self.sample_name}_subgraph_data_tile{tile}.pt' for tile in self.process_tile_ids]
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def processed_file_names(self):
|
|
172
|
+
return [f'{self.sample_name}_data_tile{tile}.pt' for tile in self.process_tile_ids]
|
|
173
|
+
|
|
174
|
+
@property
|
|
175
|
+
def subgraph_paths(self) -> List[str]:
|
|
176
|
+
r"""The absolute filepaths for generated subgraph data from each sample tile
|
|
177
|
+
"""
|
|
178
|
+
files = self.subgraph_file_names
|
|
179
|
+
# Prevent a common source of error in which `file_names` are not
|
|
180
|
+
# defined as a property.
|
|
181
|
+
if isinstance(files, Callable):
|
|
182
|
+
files = files()
|
|
183
|
+
return [osp.join(self.subgraph_dir, f) for f in list(files)]
|
|
184
|
+
|
|
185
|
+
def get_padding(self, log:bool, tile_df, full_df, padding:float):
|
|
186
|
+
r"""A helper function to get the padding area for a tile of tissue.
|
|
187
|
+
Args:
|
|
188
|
+
log (bool), whether to print logs
|
|
189
|
+
tile_df: the data.frame with current tile.
|
|
190
|
+
full_df: original tissue data.frame
|
|
191
|
+
padding: size of the padding (used for padding the core area of the tile)
|
|
192
|
+
"""
|
|
193
|
+
left_most = tile_df[self.tile_by_dim].min()
|
|
194
|
+
right_most = tile_df[self.tile_by_dim].max()
|
|
195
|
+
|
|
196
|
+
padding_left_mask = (full_df[self.tile_by_dim] >= (left_most - padding)) & (full_df[self.tile_by_dim] < left_most)
|
|
197
|
+
padding_right_mask = (full_df[self.tile_by_dim] > (right_most)) & (full_df[self.tile_by_dim] <= (right_most + padding))
|
|
198
|
+
|
|
199
|
+
if log:
|
|
200
|
+
print("left padding shape ",padding_left_mask.sum())
|
|
201
|
+
print("right padding shape ",padding_right_mask.sum())
|
|
202
|
+
|
|
203
|
+
padding_left_df = full_df[padding_left_mask]
|
|
204
|
+
padding_right_df = full_df[padding_right_mask]
|
|
205
|
+
|
|
206
|
+
padding_left_df = padding_left_df.assign(core_mask = False)
|
|
207
|
+
padding_right_df = padding_right_df.assign(core_mask = False)
|
|
208
|
+
|
|
209
|
+
return pd.concat([padding_left_df,padding_right_df])
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def generate_subgraph(self,**kwargs):
|
|
213
|
+
import random
|
|
214
|
+
import sys
|
|
215
|
+
if self.log:
|
|
216
|
+
print("To generate subgraphs for ",self.subgraph_paths)
|
|
217
|
+
|
|
218
|
+
if files_exist(self.subgraph_paths) and not self.force_resample:
|
|
219
|
+
print("Subgraphs already exist, skipping subgraph generation")
|
|
220
|
+
return
|
|
221
|
+
elif not files_exist(self.processed_paths):
|
|
222
|
+
sys.exit("Wrong logic in generate_subgraph function. Attempted to generate subgraphs when tile graphs are not available")
|
|
223
|
+
|
|
224
|
+
for s_id in self.process_tile_ids:
|
|
225
|
+
random.seed(self.seed)
|
|
226
|
+
if self.log:
|
|
227
|
+
print("Loading processed data tile for subgraphing ", osp.join(self.processed_dir,f'{self.sample_name}_data_tile{s_id}.pt'))
|
|
228
|
+
|
|
229
|
+
tile_data = torch.load(osp.join(self.processed_dir,f'{self.sample_name}_data_tile{s_id}.pt'),weights_only=True)
|
|
230
|
+
print("load tile data from ", osp.join(self.processed_dir,f'{self.sample_name}_data_tile{s_id}.pt'))
|
|
231
|
+
tile_data = Data(**tile_data[0])
|
|
232
|
+
print(tile_data)
|
|
233
|
+
|
|
234
|
+
if self.subgraph_mode == "link_based":
|
|
235
|
+
if self.log:
|
|
236
|
+
print("Generating subgraphs for tile ", s_id, "Sampling " ,self.num_sampled_edges, " edges")
|
|
237
|
+
sampled_edge_index = tile_data.edge_index[:,random.sample(range(tile_data.edge_index.shape[1]),self.num_sampled_edges)]
|
|
238
|
+
|
|
239
|
+
subgraph_loader = LinkNeighborLoader(
|
|
240
|
+
data = tile_data,
|
|
241
|
+
edge_label_index = sampled_edge_index,
|
|
242
|
+
**kwargs
|
|
243
|
+
)
|
|
244
|
+
# subgraph_g = []
|
|
245
|
+
# ## Get one batch as the sampled subgraph from the tissue tile
|
|
246
|
+
# for batch in subgraph_loader:
|
|
247
|
+
# subgraph_g = subgraph_g + [batch]
|
|
248
|
+
# break
|
|
249
|
+
elif self.subgraph_mode == "node_based":
|
|
250
|
+
if self.log:
|
|
251
|
+
print("Generating subgraphs for tile ", s_id, "Sampling ", self.num_seed_nodes, " nodes with ", self.num_walks, " random walks.")
|
|
252
|
+
random.seed(self.seed)
|
|
253
|
+
sample_nodes = random.sample(range(tile_data.x.squeeze().size()[0]),self.num_seed_nodes)
|
|
254
|
+
sample_nodes_bywalks = torch.tensor(sample_nodes * self.num_walks)
|
|
255
|
+
sample_nodes_bywalks = sample_nodes_bywalks.sort().values
|
|
256
|
+
from torch_cluster import random_walk
|
|
257
|
+
sampled_edges_by_random_walk = random_walk(
|
|
258
|
+
row = tile_data.edge_index[0],
|
|
259
|
+
col = tile_data.edge_index[1],
|
|
260
|
+
start = sample_nodes_bywalks,
|
|
261
|
+
walk_length = 1, # 1 to get immediate neighbours
|
|
262
|
+
return_edge_indices=False
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
if self.log:
|
|
266
|
+
print("batch size ", sampled_edges_by_random_walk.squeeze().size()[0])
|
|
267
|
+
print("start node ", sample_nodes_bywalks, "size " , sample_nodes_bywalks.size())
|
|
268
|
+
print(" sampled_edges_by_random_walk ", sampled_edges_by_random_walk)
|
|
269
|
+
|
|
270
|
+
subgraph_loader = LinkNeighborLoader(
|
|
271
|
+
data = tile_data,
|
|
272
|
+
batch_size = sampled_edges_by_random_walk.squeeze().size()[0],
|
|
273
|
+
edge_label_index = torch.stack(
|
|
274
|
+
[sampled_edges_by_random_walk[:,0],sampled_edges_by_random_walk[:,1]]
|
|
275
|
+
),
|
|
276
|
+
**kwargs
|
|
277
|
+
)
|
|
278
|
+
subgraph_g = []
|
|
279
|
+
## Get one batch as the sampled subgraph from the tissue tile
|
|
280
|
+
for batch in subgraph_loader:
|
|
281
|
+
subgraph_g = subgraph_g + [batch]
|
|
282
|
+
break
|
|
283
|
+
## simplify g before saving
|
|
284
|
+
subgraph_g = Data(x = subgraph_g[0].x, edge_index = subgraph_g[0].edge_index, edge_label_index = subgraph_g[0].edge_label_index)
|
|
285
|
+
self.save([subgraph_g], osp.join(self.subgraph_dir,f'{self.sample_name}_subgraph_data_tile{s_id}.pt'))
|
|
286
|
+
def process(self)-> None:
|
|
287
|
+
# Load raw data
|
|
288
|
+
import numpy as np
|
|
289
|
+
if self.log:
|
|
290
|
+
print("Processing raw file ",self.raw_paths[0])
|
|
291
|
+
print("To create processed files ", self.processed_paths)
|
|
292
|
+
|
|
293
|
+
raw_data = pd.read_csv(self.raw_paths[0])
|
|
294
|
+
if self.log:
|
|
295
|
+
print(f'Raw data shape {raw_data.shape[0]}')
|
|
296
|
+
|
|
297
|
+
raw_data["trans_id"] = np.arange(raw_data.shape[0])
|
|
298
|
+
## cutting along one selected axis
|
|
299
|
+
raw_data["tiles_range"] = pd.cut(
|
|
300
|
+
raw_data[self.tile_by_dim],
|
|
301
|
+
bins = self.num_tiles,
|
|
302
|
+
labels= ["tile"+str(x) for x in range(self.num_tiles)]
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
for tile in self.process_tile_ids:
|
|
306
|
+
tile_df = raw_data[raw_data.tiles_range == ("tile"+str(tile))]
|
|
307
|
+
if self.log:
|
|
308
|
+
print("tile core area shape ", tile_df.shape)
|
|
309
|
+
assert tile_df.shape[0] > 0, "The requested tile contains no transcripts, likely due to the tile id is not in the range 0:(num_tiles-1)"
|
|
310
|
+
tile_df = tile_df.assign(core_mask=True)
|
|
311
|
+
padding_df = self.get_padding(
|
|
312
|
+
log = self.log,
|
|
313
|
+
tile_df = tile_df,
|
|
314
|
+
full_df = raw_data,
|
|
315
|
+
padding = self.pad_hops * self.radius_r
|
|
316
|
+
)
|
|
317
|
+
tile_df = pd.concat([tile_df,padding_df])
|
|
318
|
+
if self.log:
|
|
319
|
+
print("core area plus paddings, shape ", tile_df.shape)
|
|
320
|
+
|
|
321
|
+
if isinstance(next(iter(self.one_hot_encoding.items()))[1],int):
|
|
322
|
+
feature_x = torch.tensor([self.one_hot_encoding[x] for x in tile_df[self.feature_col]])
|
|
323
|
+
else:
|
|
324
|
+
feature_x = torch.stack([self.one_hot_encoding[x] for x in tile_df[self.feature_col]])
|
|
325
|
+
xy_pos = torch.tensor(np.array(tile_df[[ self.dim_x , self.dim_y]]))
|
|
326
|
+
|
|
327
|
+
edge_index = radius_graph(xy_pos, r = self.radius_r, max_num_neighbors = self.max_num_neighbors)
|
|
328
|
+
|
|
329
|
+
data_1 = Data(
|
|
330
|
+
x = feature_x,
|
|
331
|
+
edge_index = edge_index,
|
|
332
|
+
trans_id = torch.tensor(list(tile_df.trans_id)),
|
|
333
|
+
core_mask = torch.tensor(list(tile_df.core_mask)))
|
|
334
|
+
|
|
335
|
+
self.save([data_1], osp.join(self.processed_dir,f'{self.sample_name}_data_tile{tile}.pt'))
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: spatialrna
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: A package for spatial RNA graph data processing and analysis.
|
|
5
|
+
Author-email: Ruqian Lyu <rlyu@svi.edu.au>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: repository, https://gitlab.svi.edu.au/biocellgen-private/spatialrna
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: torch
|
|
14
|
+
Requires-Dist: torch_geometric
|
|
15
|
+
Requires-Dist: pandas
|
|
16
|
+
Requires-Dist: numpy
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: pytest>=6.0; extra == "dev"
|
|
19
|
+
Requires-Dist: twine>=4.0.2; extra == "dev"
|
|
20
|
+
Requires-Dist: build; extra == "dev"
|
|
21
|
+
|
|
22
|
+
## A package that provides easier inferface to GNN models for single-molecule ST data
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
## Pre-requisite packages installation
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
## Using Conda and pip
|
|
30
|
+
|
|
31
|
+
conda create -n dev-spatialrna
|
|
32
|
+
conda activate dev-spatialrna
|
|
33
|
+
conda install python=3.10.0
|
|
34
|
+
|
|
35
|
+
## torch 2.4.0 cuda version 12.4
|
|
36
|
+
pip install torch==2.4.0 --index-url https://download.pytorch.org/whl/cu124
|
|
37
|
+
|
|
38
|
+
## torch_geometric=2.6.1 https://pytorch-geometric.readthedocs.io/en/stable/install/installation.html#installation-via-pypi
|
|
39
|
+
|
|
40
|
+
pip install torch_geometric==2.6.1
|
|
41
|
+
|
|
42
|
+
## Additional useful libraries https://pytorch-geometric.readthedocs.io/en/stable/install/installation.html#additional-libraries
|
|
43
|
+
|
|
44
|
+
pip install pyg_lib torch_scatter torch_sparse torch_cluster torch_spline_conv -f https://data.pyg.org/whl/torch-2.4.0+cu124.html
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Install spatialrna
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
pip install spatialrna
|
|
52
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
spatialrna/__init__.py
|
|
4
|
+
spatialrna/spatialrna.py
|
|
5
|
+
spatialrna.egg-info/PKG-INFO
|
|
6
|
+
spatialrna.egg-info/SOURCES.txt
|
|
7
|
+
spatialrna.egg-info/dependency_links.txt
|
|
8
|
+
spatialrna.egg-info/requires.txt
|
|
9
|
+
spatialrna.egg-info/top_level.txt
|
|
10
|
+
tests/test_data.py
|
|
11
|
+
tests/test_reload.py
|
|
12
|
+
tests/test_subgraph.py
|
|
13
|
+
tests/test_subgraph_mode.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
spatialrna
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pytest
|
|
3
|
+
from spatialrna.spatialrna import SpatialRNA
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from shutil import rmtree
|
|
6
|
+
|
|
7
|
+
def create_test_data():
|
|
8
|
+
# Create a sample dataset for testing
|
|
9
|
+
data = {
|
|
10
|
+
"gene": ["gene1", "gene1", "gene2", "gene3", "gene3", "gene3"],
|
|
11
|
+
"x": [1, 1, 2, 2, 2.5,8],
|
|
12
|
+
"y": [1, 2.0, 1, 1, 2.5,3],
|
|
13
|
+
}
|
|
14
|
+
df = pd.DataFrame(data)
|
|
15
|
+
os.makedirs("data/test_sample/raw", exist_ok=True)
|
|
16
|
+
df.to_csv("data/test_sample/raw/test_sample.csv", index=False)
|
|
17
|
+
return None
|
|
18
|
+
|
|
19
|
+
def clean_test_data():
|
|
20
|
+
if os.path.exists("data/test_sample/"):
|
|
21
|
+
rmtree("data/test_sample/")
|
|
22
|
+
clean_test_data()
|
|
23
|
+
|
|
24
|
+
def test_initialization():
|
|
25
|
+
create_test_data()
|
|
26
|
+
dataset = SpatialRNA(
|
|
27
|
+
root="data/test_sample/",
|
|
28
|
+
sample_name="test_sample",
|
|
29
|
+
radius_r=1.0,
|
|
30
|
+
tile_by_dim="y",
|
|
31
|
+
dim_x="x",
|
|
32
|
+
dim_y="y",
|
|
33
|
+
num_tiles=2,
|
|
34
|
+
load_type="tile",
|
|
35
|
+
process_tile_ids=[0],
|
|
36
|
+
one_hot_encoding={"gene1": 0, "gene2": 1, "gene3": 3},
|
|
37
|
+
log=False
|
|
38
|
+
)
|
|
39
|
+
assert dataset.num_tiles == 2
|
|
40
|
+
assert dataset.radius_r == 1.0
|
|
41
|
+
assert dataset.tile_by_dim == "y"
|
|
42
|
+
assert dataset.process_tile_ids == [0]
|
|
43
|
+
assert dataset.load_type == "tile"
|
|
44
|
+
assert dataset.process_mode == "tile"
|
|
45
|
+
assert dataset.load_tile_id == 0
|
|
46
|
+
|
|
47
|
+
clean_test_data()
|
|
48
|
+
|
|
49
|
+
def test_load_blank():
|
|
50
|
+
create_test_data()
|
|
51
|
+
dataset = SpatialRNA(
|
|
52
|
+
root="data/test_sample/",
|
|
53
|
+
sample_name="test_sample",
|
|
54
|
+
radius_r=1.0,
|
|
55
|
+
tile_by_dim="y",
|
|
56
|
+
dim_x="x",
|
|
57
|
+
dim_y="y",
|
|
58
|
+
num_tiles=2,
|
|
59
|
+
load_type="blank",
|
|
60
|
+
process_tile_ids=[0],
|
|
61
|
+
one_hot_encoding={"gene1": 0, "gene2": 1, "gene3": 3},
|
|
62
|
+
log=False
|
|
63
|
+
)
|
|
64
|
+
clean_test_data()
|
|
65
|
+
assert dataset[0] is None
|
|
66
|
+
|
|
67
|
+
def test_load_when_not_process():
|
|
68
|
+
create_test_data()
|
|
69
|
+
dataset2 = SpatialRNA(
|
|
70
|
+
root="data/test_sample/",
|
|
71
|
+
sample_name="test_sample",
|
|
72
|
+
process_mode="tile",
|
|
73
|
+
radius_r=1.0,
|
|
74
|
+
tile_by_dim="y",
|
|
75
|
+
dim_x="x",
|
|
76
|
+
dim_y="y",
|
|
77
|
+
num_tiles=2,
|
|
78
|
+
load_type="tile",
|
|
79
|
+
load_tile_id=1,
|
|
80
|
+
process_tile_ids=[0],
|
|
81
|
+
one_hot_encoding={"gene1": 0, "gene2": 1, "gene3": 3},
|
|
82
|
+
log=False
|
|
83
|
+
)
|
|
84
|
+
assert dataset2[0] is None
|
|
85
|
+
clean_test_data()
|
|
86
|
+
|
|
87
|
+
def test_new_process_id():
|
|
88
|
+
create_test_data()
|
|
89
|
+
dataset1 = SpatialRNA(
|
|
90
|
+
root="data/test_sample/",
|
|
91
|
+
sample_name="test_sample",
|
|
92
|
+
radius_r=1.0,
|
|
93
|
+
tile_by_dim="y",
|
|
94
|
+
dim_x="x",
|
|
95
|
+
dim_y="y",
|
|
96
|
+
num_tiles=2,
|
|
97
|
+
load_type="blank",
|
|
98
|
+
process_mode="tile",
|
|
99
|
+
process_tile_ids=[0],
|
|
100
|
+
one_hot_encoding={"gene1": 0, "gene2": 1, "gene3": 3},
|
|
101
|
+
log=False
|
|
102
|
+
)
|
|
103
|
+
## tile 0 was processed but tile 1 has not.
|
|
104
|
+
print(os.listdir("data/test_sample/processed"))
|
|
105
|
+
assert not os.path.exists("data/test_sample/processed/test_sample_data_tile1.pt")
|
|
106
|
+
assert os.path.exists("data/test_sample/processed/test_sample_data_tile0.pt")
|
|
107
|
+
dataset2 = SpatialRNA(
|
|
108
|
+
root="data/test_sample/",
|
|
109
|
+
sample_name="test_sample",
|
|
110
|
+
radius_r=1.0,
|
|
111
|
+
tile_by_dim="y",
|
|
112
|
+
dim_x="x",
|
|
113
|
+
dim_y="y",
|
|
114
|
+
num_tiles=2,
|
|
115
|
+
load_type="tile",
|
|
116
|
+
process_mode="tile",
|
|
117
|
+
process_tile_ids=[1],
|
|
118
|
+
one_hot_encoding={"gene1": 0, "gene2": 1, "gene3": 3},
|
|
119
|
+
log=False
|
|
120
|
+
)
|
|
121
|
+
assert os.path.exists("data/test_sample/processed/test_sample_data_tile1.pt")
|
|
122
|
+
|
|
123
|
+
clean_test_data()
|
|
124
|
+
|
|
125
|
+
def test_tile_by_dim_y():
|
|
126
|
+
# Test the tile_by_dim, x or y method
|
|
127
|
+
create_test_data()
|
|
128
|
+
|
|
129
|
+
dataset = SpatialRNA(
|
|
130
|
+
root="data/test_sample/",
|
|
131
|
+
sample_name="test_sample",
|
|
132
|
+
radius_r=1.0,
|
|
133
|
+
tile_by_dim="y",
|
|
134
|
+
dim_x="x",
|
|
135
|
+
dim_y="y",
|
|
136
|
+
num_tiles=2,
|
|
137
|
+
load_type="tile",
|
|
138
|
+
load_tile_id=0,
|
|
139
|
+
pad_hops = 0,
|
|
140
|
+
process_tile_ids=[0],
|
|
141
|
+
one_hot_encoding={"gene1": 0, "gene2": 1, "gene3": 3},
|
|
142
|
+
log=False
|
|
143
|
+
)
|
|
144
|
+
assert dataset.tile_by_dim == "y"
|
|
145
|
+
assert dataset.radius_r == 1.0
|
|
146
|
+
assert dataset[0].x.size()[0] == 4
|
|
147
|
+
clean_test_data()
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_tile_by_dim_x():
|
|
151
|
+
# Test the tile_by_dim, x or y method
|
|
152
|
+
create_test_data()
|
|
153
|
+
|
|
154
|
+
dataset = SpatialRNA(
|
|
155
|
+
root="data/test_sample/",
|
|
156
|
+
sample_name="test_sample",
|
|
157
|
+
radius_r=1.0,
|
|
158
|
+
tile_by_dim="x",
|
|
159
|
+
dim_x="x",
|
|
160
|
+
dim_y="y",
|
|
161
|
+
num_tiles=2,
|
|
162
|
+
load_type="tile",
|
|
163
|
+
load_tile_id=0,
|
|
164
|
+
process_tile_ids=[0],
|
|
165
|
+
one_hot_encoding={"gene1": 0, "gene2": 1, "gene3": 3},
|
|
166
|
+
log=False
|
|
167
|
+
)
|
|
168
|
+
assert dataset.tile_by_dim == "x"
|
|
169
|
+
assert dataset.radius_r == 1.0
|
|
170
|
+
assert dataset[0].x.size()[0] == 5
|
|
171
|
+
clean_test_data()
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
## one tile = no tiling
|
|
175
|
+
def test_one_tile():
|
|
176
|
+
create_test_data()
|
|
177
|
+
dataset = SpatialRNA(
|
|
178
|
+
root="data/test_sample/",
|
|
179
|
+
sample_name="test_sample",
|
|
180
|
+
radius_r=1.0,
|
|
181
|
+
tile_by_dim="y",
|
|
182
|
+
dim_x="x",
|
|
183
|
+
dim_y="y",
|
|
184
|
+
num_tiles=1,
|
|
185
|
+
load_type="tile",
|
|
186
|
+
process_tile_ids=[0],
|
|
187
|
+
one_hot_encoding={"gene1": 0, "gene2": 1, "gene3": 3},
|
|
188
|
+
log=False
|
|
189
|
+
)
|
|
190
|
+
print(dataset[0].x)
|
|
191
|
+
print(dataset[0].edge_index.shape)
|
|
192
|
+
print(dataset[0].edge_index[0])
|
|
193
|
+
print(dataset[0].edge_index[1])
|
|
194
|
+
assert dataset[0].x.size()[0] == 6
|
|
195
|
+
assert dataset[0].edge_index.size()[0] == 2
|
|
196
|
+
assert dataset[0].edge_index.size()[1] == 2
|
|
197
|
+
clean_test_data()
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
## one tile = no tiling ie. entire tissue region as one tile.
|
|
201
|
+
## radius_r is non-inclusive
|
|
202
|
+
def test_one_tile_r11():
|
|
203
|
+
create_test_data()
|
|
204
|
+
dataset = SpatialRNA(
|
|
205
|
+
root="data/test_sample/",
|
|
206
|
+
sample_name="test_sample",
|
|
207
|
+
radius_r=1.1,
|
|
208
|
+
tile_by_dim="y",
|
|
209
|
+
dim_x="x",
|
|
210
|
+
dim_y="y",
|
|
211
|
+
num_tiles=1,
|
|
212
|
+
load_type="tile",
|
|
213
|
+
process_tile_ids=[0],
|
|
214
|
+
one_hot_encoding={"gene1": 0, "gene2": 1, "gene3": 3},
|
|
215
|
+
log=False
|
|
216
|
+
)
|
|
217
|
+
print(dataset[0].x)
|
|
218
|
+
print(dataset[0].edge_index.size)
|
|
219
|
+
|
|
220
|
+
print(dataset[0].edge_index[0])
|
|
221
|
+
print(dataset[0].edge_index[1])
|
|
222
|
+
assert dataset[0].x.size()[0] == 6
|
|
223
|
+
assert dataset[0].edge_index.size()[1] == 8
|
|
224
|
+
assert dataset[0].edge_index.size()[0] == 2
|
|
225
|
+
clean_test_data()
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pytest
|
|
3
|
+
from spatialrna.spatialrna import SpatialRNA
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from shutil import rmtree
|
|
6
|
+
from torch_geometric.sampler import NegativeSampling
|
|
7
|
+
|
|
8
|
+
def create_test_data():
|
|
9
|
+
# Create a sample dataset for testing
|
|
10
|
+
data = {
|
|
11
|
+
"gene": ["gene1", "gene1", "gene2", "gene3", "gene3", "gene3"],
|
|
12
|
+
"x": [1, 1, 2, 2, 2.5,8],
|
|
13
|
+
"y": [1, 2.0, 1, 1, 2.5,3],
|
|
14
|
+
}
|
|
15
|
+
df = pd.DataFrame(data)
|
|
16
|
+
os.makedirs("data/test_sample/raw", exist_ok=True)
|
|
17
|
+
df.to_csv("data/test_sample/raw/test_sample.csv", index=False)
|
|
18
|
+
return None
|
|
19
|
+
|
|
20
|
+
def clean_test_data():
|
|
21
|
+
if os.path.exists("data/test_sample/"):
|
|
22
|
+
rmtree("data/test_sample/")
|
|
23
|
+
clean_test_data()
|
|
24
|
+
|
|
25
|
+
def test_force_reload():
|
|
26
|
+
clean_test_data()
|
|
27
|
+
create_test_data()
|
|
28
|
+
# process mode with subgraph, tile graph will be created by init (after checking force_reload, etc)
|
|
29
|
+
# try:
|
|
30
|
+
dataset = SpatialRNA(
|
|
31
|
+
root="data/test_sample/",
|
|
32
|
+
sample_name="test_sample",
|
|
33
|
+
radius_r=1.1,
|
|
34
|
+
tile_by_dim="y",
|
|
35
|
+
dim_x="x",
|
|
36
|
+
dim_y="y",
|
|
37
|
+
num_tiles=2,
|
|
38
|
+
load_type="blank",
|
|
39
|
+
load_tile_id=0,
|
|
40
|
+
process_tile_ids=[0,1],
|
|
41
|
+
process_mode="subgraph",
|
|
42
|
+
subgraph_mode="link_based",
|
|
43
|
+
num_sampled_edges=2,
|
|
44
|
+
num_neighbors = [-1,-1],
|
|
45
|
+
batch_size = 4,
|
|
46
|
+
neg_sampling = NegativeSampling(mode="triplet",amount=1),
|
|
47
|
+
one_hot_encoding={"gene1": 0, "gene2": 1, "gene3": 3},
|
|
48
|
+
log=True
|
|
49
|
+
)
|
|
50
|
+
time_stamp1 = os.path.getmtime("data/test_sample/processed/test_sample_data_tile1.pt")
|
|
51
|
+
import time
|
|
52
|
+
time.sleep(3)
|
|
53
|
+
# force_reload controls only the re-processing of data_tile.pt
|
|
54
|
+
SpatialRNA(
|
|
55
|
+
root="data/test_sample/",
|
|
56
|
+
sample_name="test_sample",
|
|
57
|
+
radius_r=1.1,
|
|
58
|
+
tile_by_dim="y",
|
|
59
|
+
dim_x="x",
|
|
60
|
+
dim_y="y",
|
|
61
|
+
num_tiles=2,
|
|
62
|
+
force_reload=True,
|
|
63
|
+
load_type="blank",
|
|
64
|
+
load_tile_id=0,
|
|
65
|
+
process_tile_ids=[0,1],
|
|
66
|
+
process_mode="subgraph",
|
|
67
|
+
subgraph_mode="link_based",
|
|
68
|
+
num_sampled_edges=2,
|
|
69
|
+
num_neighbors = [-1,-1],
|
|
70
|
+
batch_size = 4,
|
|
71
|
+
neg_sampling = NegativeSampling(mode="triplet",amount=1),
|
|
72
|
+
one_hot_encoding={"gene1": 0, "gene2": 1, "gene3": 3},
|
|
73
|
+
log=True
|
|
74
|
+
)
|
|
75
|
+
time_stamp2 = os.path.getmtime("data/test_sample/processed/test_sample_data_tile1.pt")
|
|
76
|
+
assert time_stamp1 != time_stamp2
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_force_resample():
|
|
80
|
+
clean_test_data()
|
|
81
|
+
create_test_data()
|
|
82
|
+
# process mode with subgraph, tile graph will be created by init (after checking force_reload, etc)
|
|
83
|
+
# try:
|
|
84
|
+
dataset = SpatialRNA(
|
|
85
|
+
root="data/test_sample/",
|
|
86
|
+
sample_name="test_sample",
|
|
87
|
+
radius_r=1.1,
|
|
88
|
+
tile_by_dim="y",
|
|
89
|
+
dim_x="x",
|
|
90
|
+
dim_y="y",
|
|
91
|
+
num_tiles=2,
|
|
92
|
+
load_type="blank",
|
|
93
|
+
load_tile_id=0,
|
|
94
|
+
process_tile_ids=[0,1],
|
|
95
|
+
process_mode="subgraph",
|
|
96
|
+
subgraph_mode="link_based",
|
|
97
|
+
num_sampled_edges=2,
|
|
98
|
+
num_neighbors = [-1,-1],
|
|
99
|
+
batch_size = 4,
|
|
100
|
+
neg_sampling = NegativeSampling(mode="triplet",amount=1),
|
|
101
|
+
one_hot_encoding={"gene1": 0, "gene2": 1, "gene3": 3},
|
|
102
|
+
log=True
|
|
103
|
+
)
|
|
104
|
+
time_stamp1_tile1 = os.path.getmtime("data/test_sample/processed/test_sample_data_tile1.pt")
|
|
105
|
+
time_stamp1_subgraph_tile1 = os.path.getmtime("data/test_sample/subgraph/test_sample_subgraph_data_tile1.pt")
|
|
106
|
+
|
|
107
|
+
import time
|
|
108
|
+
time.sleep(3)
|
|
109
|
+
|
|
110
|
+
# force_resample controls only the re-sampling of subgraphs from data_tile.pt
|
|
111
|
+
SpatialRNA(
|
|
112
|
+
root="data/test_sample/",
|
|
113
|
+
sample_name="test_sample",
|
|
114
|
+
radius_r=1.1,
|
|
115
|
+
tile_by_dim="y",
|
|
116
|
+
dim_x="x",
|
|
117
|
+
dim_y="y",
|
|
118
|
+
num_tiles=2,
|
|
119
|
+
force_reload=False,
|
|
120
|
+
force_resample=True,
|
|
121
|
+
load_type="blank",
|
|
122
|
+
load_tile_id=0,
|
|
123
|
+
process_tile_ids=[0,1],
|
|
124
|
+
process_mode="subgraph",
|
|
125
|
+
subgraph_mode="link_based",
|
|
126
|
+
num_sampled_edges=2,
|
|
127
|
+
num_neighbors = [-1,-1],
|
|
128
|
+
batch_size = 4,
|
|
129
|
+
neg_sampling = NegativeSampling(mode="triplet",amount=1),
|
|
130
|
+
one_hot_encoding={"gene1": 0, "gene2": 1, "gene3": 3},
|
|
131
|
+
log=True
|
|
132
|
+
)
|
|
133
|
+
time_stamp2_tile1 = os.path.getmtime("data/test_sample/processed/test_sample_data_tile1.pt")
|
|
134
|
+
time_stamp2_subgraph_tile1 = os.path.getmtime("data/test_sample/subgraph/test_sample_subgraph_data_tile1.pt")
|
|
135
|
+
|
|
136
|
+
assert time_stamp1_tile1 == time_stamp2_tile1
|
|
137
|
+
assert time_stamp1_subgraph_tile1 != time_stamp2_subgraph_tile1
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pytest
|
|
3
|
+
from spatialrna.spatialrna import SpatialRNA
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from shutil import rmtree
|
|
6
|
+
from torch_geometric.sampler import NegativeSampling
|
|
7
|
+
|
|
8
|
+
def create_test_data():
|
|
9
|
+
# Create a sample dataset for testing
|
|
10
|
+
data = {
|
|
11
|
+
"gene": ["gene1", "gene1", "gene2", "gene3", "gene3", "gene3"],
|
|
12
|
+
"x": [1, 1, 2, 2, 2.5,8],
|
|
13
|
+
"y": [1, 2.0, 1, 1, 2.5,3],
|
|
14
|
+
}
|
|
15
|
+
df = pd.DataFrame(data)
|
|
16
|
+
os.makedirs("data/test_sample/raw", exist_ok=True)
|
|
17
|
+
df.to_csv("data/test_sample/raw/test_sample.csv", index=False)
|
|
18
|
+
return None
|
|
19
|
+
|
|
20
|
+
def clean_test_data():
|
|
21
|
+
if os.path.exists("data/test_sample/"):
|
|
22
|
+
rmtree("data/test_sample/")
|
|
23
|
+
clean_test_data()
|
|
24
|
+
|
|
25
|
+
def test_process_order_subgraph():
|
|
26
|
+
clean_test_data()
|
|
27
|
+
create_test_data()
|
|
28
|
+
# process mode with subgraph, tile graph will be created by init (after checking force_reload, etc)
|
|
29
|
+
# try:
|
|
30
|
+
dataset = SpatialRNA(
|
|
31
|
+
root="data/test_sample/",
|
|
32
|
+
sample_name="test_sample",
|
|
33
|
+
radius_r=15,
|
|
34
|
+
tile_by_dim="y",
|
|
35
|
+
dim_x="x",
|
|
36
|
+
dim_y="y",
|
|
37
|
+
num_tiles=2,
|
|
38
|
+
load_type="subgraph",
|
|
39
|
+
load_tile_id=0,
|
|
40
|
+
process_tile_ids=[1],
|
|
41
|
+
process_mode="subgraph",
|
|
42
|
+
subgraph_mode="link_based",
|
|
43
|
+
num_sampled_edges=2,
|
|
44
|
+
num_neighbors = [-1,-1],
|
|
45
|
+
batch_size = 4,
|
|
46
|
+
neg_sampling = NegativeSampling(mode="triplet",amount=1),
|
|
47
|
+
one_hot_encoding={"gene1": 0, "gene2": 1, "gene3": 3},
|
|
48
|
+
log=True
|
|
49
|
+
)
|
|
50
|
+
assert dataset[0] is None
|
|
51
|
+
# except Exception as e:
|
|
52
|
+
# assert isinstance(e, TypeError)
|
|
53
|
+
dataset = SpatialRNA(
|
|
54
|
+
root="data/test_sample/",
|
|
55
|
+
sample_name="test_sample",
|
|
56
|
+
radius_r=11,
|
|
57
|
+
tile_by_dim="y",
|
|
58
|
+
dim_x="x",
|
|
59
|
+
dim_y="y",
|
|
60
|
+
num_tiles=2,
|
|
61
|
+
load_type="subgraph",
|
|
62
|
+
load_tile_id=1,
|
|
63
|
+
process_tile_ids=[0],
|
|
64
|
+
process_mode="subgraph",
|
|
65
|
+
subgraph_mode="link_based",
|
|
66
|
+
num_sampled_edges=2,
|
|
67
|
+
num_neighbors = [-1,-1],
|
|
68
|
+
batch_size = 4,
|
|
69
|
+
neg_sampling = NegativeSampling(mode="triplet",amount=1),
|
|
70
|
+
one_hot_encoding={"gene1": 0, "gene2": 1, "gene3": 3},
|
|
71
|
+
log=True
|
|
72
|
+
)
|
|
73
|
+
clean_test_data()
|
|
74
|
+
create_test_data()
|
|
75
|
+
dataset = SpatialRNA(
|
|
76
|
+
root="data/test_sample/",
|
|
77
|
+
sample_name="test_sample",
|
|
78
|
+
radius_r=11,
|
|
79
|
+
tile_by_dim="y",
|
|
80
|
+
dim_x="x",
|
|
81
|
+
dim_y="y",
|
|
82
|
+
num_tiles=2,
|
|
83
|
+
load_type="subgraph",
|
|
84
|
+
load_tile_id=0,
|
|
85
|
+
process_tile_ids=[0],
|
|
86
|
+
process_mode="subgraph",
|
|
87
|
+
subgraph_mode="link_based",
|
|
88
|
+
num_sampled_edges=2,
|
|
89
|
+
num_neighbors = [-1,-1],
|
|
90
|
+
batch_size = 4,
|
|
91
|
+
neg_sampling = NegativeSampling(mode="triplet",amount=1),
|
|
92
|
+
one_hot_encoding={"gene1": 0, "gene2": 1, "gene3": 3},
|
|
93
|
+
log=False
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
#clean_test_data()
|
|
97
|
+
|
|
98
|
+
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pytest
|
|
3
|
+
from spatialrna.spatialrna import SpatialRNA
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from shutil import rmtree
|
|
6
|
+
from torch_geometric.sampler import NegativeSampling
|
|
7
|
+
|
|
8
|
+
def create_test_data():
|
|
9
|
+
# Create a sample dataset for testing
|
|
10
|
+
data = {
|
|
11
|
+
"gene": ["gene1", "gene1", "gene2", "gene3", "gene3", "gene3"],
|
|
12
|
+
"x": [1, 1, 2, 2, 2.5,8],
|
|
13
|
+
"y": [1, 2.0, 1, 1, 2.5,3],
|
|
14
|
+
}
|
|
15
|
+
df = pd.DataFrame(data)
|
|
16
|
+
os.makedirs("data/test_sample/raw", exist_ok=True)
|
|
17
|
+
df.to_csv("data/test_sample/raw/test_sample.csv", index=False)
|
|
18
|
+
return None
|
|
19
|
+
|
|
20
|
+
def clean_test_data():
|
|
21
|
+
if os.path.exists("data/test_sample/"):
|
|
22
|
+
rmtree("data/test_sample/")
|
|
23
|
+
|
|
24
|
+
def test_subgraph_mode_basic():
|
|
25
|
+
create_test_data()
|
|
26
|
+
SpatialRNA(
|
|
27
|
+
root="data/test_sample/",
|
|
28
|
+
sample_name="test_sample",
|
|
29
|
+
radius_r=1.1,
|
|
30
|
+
tile_by_dim="y",
|
|
31
|
+
num_tiles=2,
|
|
32
|
+
dim_x="x",
|
|
33
|
+
dim_y="y",
|
|
34
|
+
load_type="blank",
|
|
35
|
+
pad_hops=0,
|
|
36
|
+
process_tile_ids=[0,1],
|
|
37
|
+
process_mode="tile",
|
|
38
|
+
one_hot_encoding={"gene1": 0, "gene2": 1, "gene3": 3},
|
|
39
|
+
log=False
|
|
40
|
+
)
|
|
41
|
+
assert os.path.exists("data/test_sample/processed/test_sample_data_tile0.pt")
|
|
42
|
+
assert os.path.exists("data/test_sample/processed/test_sample_data_tile1.pt")
|
|
43
|
+
# process mode with subgraph, when tile was not created, the tile graph will be created.
|
|
44
|
+
clean_test_data()
|
|
45
|
+
create_test_data()
|
|
46
|
+
subgraphs = SpatialRNA(
|
|
47
|
+
root="data/test_sample/",
|
|
48
|
+
sample_name="test_sample",
|
|
49
|
+
radius_r=11,
|
|
50
|
+
tile_by_dim="y",
|
|
51
|
+
num_tiles=2,
|
|
52
|
+
dim_x="x",
|
|
53
|
+
dim_y="y",
|
|
54
|
+
load_tile_id=1,
|
|
55
|
+
load_type="subgraph",
|
|
56
|
+
pad_hops=0,
|
|
57
|
+
process_tile_ids=[1],
|
|
58
|
+
process_mode="subgraph",
|
|
59
|
+
subgraph_mode="link_based",
|
|
60
|
+
num_sampled_edges=2,
|
|
61
|
+
num_neighbors = [-1,-1],
|
|
62
|
+
batch_size = 2,
|
|
63
|
+
one_hot_encoding={"gene1": 0, "gene2": 1, "gene3": 3},
|
|
64
|
+
log=False
|
|
65
|
+
)
|
|
66
|
+
assert not os.path.exists("data/test_sample/subgraph/test_sample_subgraph_data_tile0.pt")
|
|
67
|
+
assert os.path.exists("data/test_sample/subgraph/test_sample_subgraph_data_tile1.pt")
|
|
68
|
+
|
|
69
|
+
subgraphs = SpatialRNA(
|
|
70
|
+
root="data/test_sample/",
|
|
71
|
+
sample_name="test_sample",
|
|
72
|
+
radius_r=1.1,
|
|
73
|
+
tile_by_dim="y",
|
|
74
|
+
dim_x="x",
|
|
75
|
+
dim_y="y",
|
|
76
|
+
num_tiles=2,
|
|
77
|
+
load_type="subgraph",
|
|
78
|
+
load_tile_id=1,
|
|
79
|
+
pad_hops=0,
|
|
80
|
+
process_tile_ids=[0],
|
|
81
|
+
process_mode="subgraph",
|
|
82
|
+
subgraph_mode="node_based",
|
|
83
|
+
force_resample=True,
|
|
84
|
+
num_seed_nodes=2,
|
|
85
|
+
num_neighbors = [-1,-1],
|
|
86
|
+
num_walks=2,
|
|
87
|
+
# batch_size = 2, batch size is inferred when subgraph mode is node_based with random walks
|
|
88
|
+
one_hot_encoding={"gene1": 0, "gene2": 1, "gene3": 3},
|
|
89
|
+
log=False
|
|
90
|
+
)
|
|
91
|
+
assert os.path.exists("data/test_sample/subgraph/test_sample_subgraph_data_tile0.pt")
|
|
92
|
+
assert os.path.exists("data/test_sample/subgraph/test_sample_subgraph_data_tile1.pt")
|
|
93
|
+
|
|
94
|
+
clean_test_data()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_subgraph_node_based():
|
|
98
|
+
import shutil
|
|
99
|
+
|
|
100
|
+
if os.path.exists("data/test_df/subgraph"):
|
|
101
|
+
shutil.rmtree("data/test_df/subgraph")
|
|
102
|
+
if os.path.exists("data/test_df/processed"):
|
|
103
|
+
shutil.rmtree("data/test_df/processed")
|
|
104
|
+
# the loaded subgraph data object is a list of 1
|
|
105
|
+
subgraphs = SpatialRNA(
|
|
106
|
+
root="data/test_df/",
|
|
107
|
+
sample_name="test_df",
|
|
108
|
+
radius_r=15,
|
|
109
|
+
tile_by_dim="y",
|
|
110
|
+
dim_x="x",
|
|
111
|
+
dim_y="y",
|
|
112
|
+
num_tiles=2,
|
|
113
|
+
load_type="subgraph",
|
|
114
|
+
load_tile_id=1,
|
|
115
|
+
pad_hops=0,
|
|
116
|
+
process_tile_ids=[0,1],
|
|
117
|
+
process_mode="subgraph",
|
|
118
|
+
subgraph_mode="node_based",
|
|
119
|
+
force_resample=True,
|
|
120
|
+
num_seed_nodes=4,
|
|
121
|
+
num_neighbors = [-1,-1],
|
|
122
|
+
subgraph_type= "bidirectional",
|
|
123
|
+
num_walks=2,
|
|
124
|
+
# batch_size = 2, batch size is inferred when subgraph mode is node_based with random walks
|
|
125
|
+
one_hot_encoding={"A": 0, "B": 1, "C": 3},
|
|
126
|
+
seed = 100,
|
|
127
|
+
log=True
|
|
128
|
+
)
|
|
129
|
+
import torch
|
|
130
|
+
assert subgraphs[0].x.size()[0] == 8
|
|
131
|
+
#[3, 3, 0, 0, 1, 3, 3, 0]
|
|
132
|
+
assert subgraphs[0].x[0] == 3
|
|
133
|
+
assert subgraphs[0].x[1] == 3
|
|
134
|
+
assert subgraphs[0].x[2] == 0
|
|
135
|
+
|
|
136
|
+
assert (subgraphs[0].edge_label_index.size()[1] == 8)
|