├── LICENSE ├── README.md ├── TESLA_package ├── LICENSE ├── README.md ├── TESLA.egg-info │ ├── PKG-INFO │ ├── SOURCES.txt │ ├── dependency_links.txt │ ├── requires.txt │ └── top_level.txt ├── TESLA │ ├── ConnectedComponents.py │ ├── TESLA.py │ ├── TLS_detection.py │ ├── __init__.py │ ├── __pycache__ │ │ ├── TESLA.cpython-37.pyc │ │ ├── __init__.cpython-37.pyc │ │ ├── annotation.cpython-37.pyc │ │ ├── calculate_dis.cpython-37.pyc │ │ ├── contour_util.cpython-37.pyc │ │ ├── imputation.cpython-37.pyc │ │ ├── models.cpython-37.pyc │ │ └── util.cpython-37.pyc │ ├── annotation.py │ ├── calculate_dis.py │ ├── contour_util.py │ ├── imputation.py │ ├── models.py │ ├── tumor_edge_core.py │ └── util.py ├── TESLAforST.egg-info │ ├── PKG-INFO │ ├── SOURCES.txt │ ├── dependency_links.txt │ ├── requires.txt │ └── top_level.txt ├── build │ └── lib │ │ └── TESLA │ │ ├── ConnectedComponents.py │ │ ├── TESLA.py │ │ ├── TLS_detection.py │ │ ├── __init__.py │ │ ├── annotation.py │ │ ├── calculate_dis.py │ │ ├── contour_util.py │ │ ├── imputation.py │ │ ├── models.py │ │ ├── tumor_edge_core.py │ │ └── util.py ├── dist │ ├── TESLA-1.0.0-py3.7.egg │ ├── TESLAforST-1.2.0-py3-none-any.whl │ ├── TESLAforST-1.2.0.tar.gz │ ├── TESLAforST-1.2.1-py3-none-any.whl │ ├── TESLAforST-1.2.1.tar.gz │ ├── TESLAforST-1.2.2-py3-none-any.whl │ └── TESLAforST-1.2.2.tar.gz └── setup.py ├── docs └── asserts │ └── images │ ├── applications.jpg │ ├── applications_new.jpg │ └── workflow.jpg └── tutorial ├── output_19_0.jpg ├── output_23_0.png ├── output_28_1.jpg ├── output_35_0.jpg ├── output_37_2.jpg ├── output_43_0.jpg ├── output_44_0.jpg ├── output_51_0.jpg ├── tutorial.ipynb └── tutorial.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 JianHu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TESLA v1.2.4 2 | 3 | ## TESLA: Deciphering tumor ecosystems at super-resolution from spatial transcriptomics 4 | 5 | ### Jian Hu*, Kyle Coleman, Edward B. Lee, Humam Kadara, Linghua Wang*, Mingyao Li* 6 | 7 | TESLA (Tumor Edge Structure and Lymphocyte multi-level Annotation) is a machine learning framework for multi-level tissue annotation on the histology image with pixel-level resolution in Spatial Transcriptomics (ST). By integrating information from high-resolution histology image, TESLA can impute gene expression at superpixels and fill in missing gene expression in tissue gaps. The increased gene expression resolution makes it possible to treat gene expression data as images, which enabled the integration with histological features for joint tissue segmentation and annotation of different cell types directly on the histology image with pixel-level resolution. Additionally, TESLA can detect unique structures of tumor immune microenvironment such as Tertiary Lymphoid Structures (TLSs), , separate a tumor into core and edge to examine their cellular compositions, expression features, and molecular processes. TESLA has been evaluated on five cancer datasets. Our results consistently showed that TESLA can generate high-quality super-resolution gene expression images, which facilitated the downstream multi-level tissue annotation. 8 | 9 |
10 | 11 | ![TESLA workflow](docs/asserts/images/workflow.jpg) 12 | 13 |
14 | 15 | ## Applications 16 | TESLA has been used for cancer genomics data analysis in numerous top-tier journals. 17 | 18 |
19 | 20 | ![TESLA applications](docs/asserts/images/applications_new.jpg) 21 | 22 |
23 | 24 | ## Usage 25 | 26 | The [**TESLA**](https://github.com/jianhuupenn/TESLA) package is an implementation of multi-level tissue annotation on the histology image with pixel-level resolution in spatial transcriptomics. With TESLA, you can: 27 | 28 | - Enhance the gene expression resolution to pixel resolution the same as the histology image; 29 | - Annotate tumor region at pixel resolution; 30 | - Annotate user-defined cell types at pixel resolution; 31 | - Characterize the intra-tumor heterogeneity, e.g, the tumor leading edge, tumor core and edge, tumor subtypes. 32 |
33 | For tutorial, please refer to: https://github.com/jianhuupenn/TESLA/blob/main/tutorial/tutorial.md 34 |
35 | A Jupyter Notebook of the tutorial is accessible from : https://github.com/jianhuupenn/TESLA/blob/main/tutorial/tutorial.ipynb 36 |
37 | Please install jupyter in order to open this notebook. 38 |
39 | Toy data and results can be downloaded at: https://drive.google.com/drive/folders/1hC6ldkxmZX0yiCWZR57iMXjWIIm9qUJU?usp=sharing 40 | 41 | ## System Requirements 42 | Python support packages: torch, pandas, numpy, scipy, scanpy > 1.5, anndata, sklearn, cv2. 43 | 44 | ## Versions the software has been tested on 45 | Environment 1: 46 | - System: Mac OS 10.13.6 47 | - Python: 3.7.0 48 | - Python packages: pandas = 1.1.3, numpy = 1.18.1, torch=1.5.1,louvain=0.6.1,scipy = 1.4.1, scanpy = 1.5.1, anndata = 0.6.22.post1, sklearn = 0.22.1, cv2=4.5.1. 49 |
50 | 51 | Environment 2: 52 | - System: Anaconda 53 | - Python: 3.7.9 54 | - Python packages: pandas = 1.1.3, numpy = 1.20.2, python-igraph=0.8.3, torch=1.6.0,louvain=0.7.0, scipy = 1.5.2, scanpy = 1.6.0, anndata = 0.7.4, sklearn = 0.23.3, cv2=4.5.1 55 |
56 | 57 | ## Contributing 58 | 59 | Souce code: [Github](https://github.com/jianhuupenn/TESLA) 60 | 61 | We are continuing adding new features. Bug reports or feature requests are welcome. 62 | 63 | Last update: 11/20/2021, version 1.0.0 64 | 65 | 66 | 67 | ## References 68 | 69 | Please consider citing the following reference: 70 | 71 | - https://doi.org/10.1016/j.cels.2023.03.008 72 |
73 | -------------------------------------------------------------------------------- /TESLA_package/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 JianHu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /TESLA_package/README.md: -------------------------------------------------------------------------------- 1 | # TESLA 2 | 3 | ## TESLA: Deciphering tumor ecosystems at super-resolution from spatial transcriptomics 4 | 5 | ### Jian Hu,*, Kyle Coleman, Edward B. Lee, Humam Kadara, Linghua Wang,*, Mingyao Li,* 6 | 7 | TESLA is a machine learning framework for multi-level tissue annotation with pixel-level resolution in ST. TESLA integrates histological information with gene expression to annotate heterogeneous immune and tumor cells directly on the histology image. TESLA further detects unique Tumor Microenvironment (TME) features such as tertiary lymphoid structures and differential transcriptome programs in the core or edge of a tumor, which represent a promising avenue for understanding the spatial architecture of the TME. Although we illustrated the applications in cancer, TESLA can also be applied to other diseases. 8 | For more info, please go to: 9 | https://github.com/jianhuupenn/TESLA -------------------------------------------------------------------------------- /TESLA_package/TESLA.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: TESLA 3 | Version: 1.0.0 4 | Summary: TESLA: Deciphering tumor ecosystems at super-resolution from spatial transcriptomics 5 | Home-page: https://github.com/jianhuupenn/TESLA 6 | Author: Jian Hu 7 | Author-email: jianhu@pennmedicine.upenn.edu 8 | License: UNKNOWN 9 | Description: # TESLA 10 | 11 | ## TESLA: Deciphering tumor ecosystems at super-resolution from spatial transcriptomics 12 | 13 | ### Jian Hu,*, Kyle Coleman, Edward B. Lee, Humam Kadara, Linghua Wang,*, Mingyao Li,* 14 | 15 | TESLA is a machine learning framework for multi-level tissue annotation with pixel-level resolution in ST. TESLA integrates histological information with gene expression to annotate heterogeneous immune and tumor cells directly on the histology image. TESLA further detects unique Tumor Microenvironment (TME) features such as tertiary lymphoid structures and differential transcriptome programs in the core or edge of a tumor, which represent a promising avenue for understanding the spatial architecture of the TME. Although we illustrated the applications in cancer, TESLA can also be applied to other diseases. 16 | For more info, please go to: 17 | https://github.com/jianhuupenn/TESLA 18 | Platform: UNKNOWN 19 | Classifier: Programming Language :: Python :: 3 20 | Classifier: License :: OSI Approved :: MIT License 21 | Classifier: Operating System :: OS Independent 22 | Requires-Python: >=3.6 23 | Description-Content-Type: text/markdown 24 | -------------------------------------------------------------------------------- /TESLA_package/TESLA.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | README.md 2 | setup.py 3 | TESLA/ConnectedComponents.py 4 | TESLA/TESLA.py 5 | TESLA/__init__.py 6 | TESLA/annotation.py 7 | TESLA/calculate_dis.py 8 | TESLA/contour_util.py 9 | TESLA/imputation.py 10 | TESLA/models.py 11 | TESLA/util.py 12 | TESLA.egg-info/PKG-INFO 13 | TESLA.egg-info/SOURCES.txt 14 | TESLA.egg-info/dependency_links.txt 15 | TESLA.egg-info/requires.txt 16 | TESLA.egg-info/top_level.txt -------------------------------------------------------------------------------- /TESLA_package/TESLA.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /TESLA_package/TESLA.egg-info/requires.txt: -------------------------------------------------------------------------------- 1 | torch 2 | pandas 3 | numpy 4 | scipy 5 | scanpy 6 | anndata 7 | sklearn 8 | numba 9 | -------------------------------------------------------------------------------- /TESLA_package/TESLA.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | TESLA 2 | -------------------------------------------------------------------------------- /TESLA_package/TESLA/ConnectedComponents.py: -------------------------------------------------------------------------------- 1 | #Get connected components in an undirected graph 2 | import pandas as pd 3 | import numpy as np 4 | from collections import deque 5 | class Graph: 6 | # init function to declare class variables 7 | def __init__(self, V, adj): 8 | self.V = V 9 | self.adj = adj 10 | def DFSUtil(self, v, tmp, visited): 11 | # Mark the current vertex as visited 12 | visited[v] = 1 13 | # Store the vertex to list 14 | tmp.append(v) 15 | # Repeat for all vertices adjacent to this vertex v 16 | nbr=self.adj[v][self.adj[v]==1].index.tolist() 17 | for i in nbr: 18 | if visited[i] == 0: 19 | tmp = self.DFSUtil(i, tmp, visited) 20 | return tmp 21 | def ConnectedComponentsDFS(self): 22 | visited = pd.Series([0]* len(self.V), index=self.V) 23 | cc = [] 24 | for v in self.V: 25 | if visited[v] == 0: 26 | tmp = [] 27 | cc.append(self.DFSUtil(v, tmp, visited)) 28 | return cc 29 | 30 | def ConnectedComponents(self): 31 | visited = pd.Series([0] * len(self.V), index=self.V) 32 | cc = [] 33 | for v in self.V: 34 | if visited[v] == 1: 35 | continue 36 | 37 | queue = deque([v]) 38 | visited[v] = 1 39 | tmp = [v] 40 | while len(queue) > 0: 41 | elem = queue.pop() 42 | nbrs = self.adj[elem][self.adj[elem] == 1].index.tolist() 43 | 44 | for nbr in nbrs: 45 | if visited[nbr] == 0: 46 | queue.append(nbr) 47 | visited[nbr] = 1 48 | tmp.append(nbr) 49 | 50 | if len(tmp) > 0: 51 | cc.append(tmp) 52 | 53 | return cc 54 | -------------------------------------------------------------------------------- /TESLA_package/TESLA/TESLA.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import time 4 | import cv2 5 | import argparse 6 | import torch 7 | import torch.nn as nn 8 | import torch.nn.functional as F 9 | import torch.optim as optim 10 | import torch.nn.init 11 | from torch.autograd import Variable 12 | from . models import Spatial_CNN 13 | 14 | class TESLA(object): 15 | def __init__(self): 16 | super(TESLA, self).__init__() 17 | 18 | def train(self, input, 19 | use_cuda=False, 20 | train_refine=True, 21 | radius=3, 22 | nChannel=100, 23 | lr=0.1, 24 | minLabels=20, 25 | maxIter=30, 26 | stepsize_sim=1, 27 | stepsize_con=10, 28 | threshold=1000, 29 | plot_intermedium=False, 30 | plot_dir="./"): 31 | self.use_cuda=use_cuda 32 | self.train_refine=train_refine 33 | self.radius=radius 34 | self.nChannel=nChannel 35 | self.lr=lr 36 | self.minLabels=minLabels 37 | self.maxIter=maxIter 38 | self.stepsize_sim=stepsize_sim 39 | self.stepsize_con=stepsize_con 40 | self.threshold=threshold 41 | self.plot_intermedium=plot_intermedium 42 | self.resize_height=input.shape[0] 43 | self.resize_width=input.shape[1] 44 | input = torch.from_numpy( np.array([input.transpose( (2, 0, 1) ).astype('float32')]) ) 45 | if use_cuda: 46 | input = input.cuda() 47 | input = Variable(input) 48 | #--------------------------------------- Train --------------------------------------- 49 | self.model = Spatial_CNN(input.size(1), nConv=2, nChannel=self.nChannel, kernel_size_list=[5, 5],stride_list=[1, 1], padding_list=[2, 2]) 50 | if use_cuda: 51 | self.model.cuda() 52 | # Similarity loss definition 53 | loss_fn = torch.nn.CrossEntropyLoss() 54 | # Continuity loss definition 55 | loss_hpy = torch.nn.L1Loss(size_average = True) 56 | loss_hpz = torch.nn.L1Loss(size_average = True) 57 | HPy_target = torch.zeros(self.resize_height-1, self.resize_width, nChannel) 58 | HPz_target = torch.zeros(self.resize_height, self.resize_width-1, nChannel) 59 | if use_cuda: 60 | HPy_target = HPy_target.cuda() 61 | HPz_target = HPz_target.cuda() 62 | optimizer = optim.SGD(self.model.parameters(), lr=lr, momentum=0.9) 63 | label_colours = np.random.randint(255,size=(100,3)) 64 | start_time = time.time() 65 | self.model.train() 66 | for batch_idx in range(maxIter): 67 | # forwarding 68 | optimizer.zero_grad() 69 | output = self.model( input )[ 0 ] 70 | output = output.permute( 1, 2, 0 ).contiguous().view( -1, nChannel ) 71 | outputHP = output.reshape( (self.resize_height, self.resize_width, nChannel) ) 72 | HPy = outputHP[1:, :, :] - outputHP[0:-1, :, :] 73 | HPz = outputHP[:, 1:, :] - outputHP[:, 0:-1, :] 74 | lhpy = loss_hpy(HPy,HPy_target) 75 | lhpz = loss_hpz(HPz,HPz_target) 76 | _, target = torch.max( output, 1 ) 77 | img_target = target.data.cpu().numpy() 78 | # Total number of clusters 79 | nLabels = len(np.unique(img_target)) 80 | # Number of main clusters 81 | mainLabels=(pd.Series(img_target).value_counts()>=threshold).sum() 82 | #--------------------------Refine during training---------------------------------------- 83 | if train_refine: 84 | pixel_num=pd.Series(img_target).value_counts() 85 | main_clusters=pixel_num.index[pixel_num>=threshold].tolist() 86 | minor_clusters=pixel_num.index[pixel_num0: 98 | replace_map[i]=nbs_num.index[nbs_num.index.isin(main_clusters)][0] 99 | target=torch.from_numpy(pd.Series(img_target).replace(replace_map).to_numpy()) 100 | #------------------------------------------------------------------ 101 | loss1 = stepsize_sim * loss_fn(output, target) 102 | loss2 = stepsize_con * (lhpy + lhpz) 103 | loss=loss1+loss2 104 | loss.backward() 105 | optimizer.step() 106 | print (batch_idx, '/', maxIter, '|', ' label num :', nLabels, '|',' main clusters :',mainLabels,' | feature loss :', loss1.item(),' | spatial loss :', loss2.item()) 107 | print("--- %s seconds ---" % (time.time() - start_time)) 108 | if batch_idx%2==0: 109 | output = self.model(input)[0] 110 | output = output.permute(1, 2, 0).contiguous().view(-1, nChannel) 111 | _, target = torch.max(output, 1) 112 | img_target = target.data.cpu().numpy() 113 | img_target_rgb = np.array([label_colours[ c % label_colours.shape[0]] for c in img_target]) 114 | img_target_rgb = img_target_rgb.reshape(self.resize_height, self.resize_width, 3).astype( np.uint8 ) 115 | if self.plot_intermedium: 116 | cv2.imwrite(plot_dir+str(batch_idx)+"_nL"+str(nLabels)+"_mL"+str(mainLabels)+".png", img_target_rgb) 117 | if mainLabels <= minLabels: 118 | print ("mainLabels", mainLabels, "reached minLabels", minLabels, ".") 119 | break 120 | 121 | def predict(self, input): 122 | input = torch.from_numpy( np.array([input.transpose( (2, 0, 1) ).astype('float32')]) ) 123 | output = self.model( input )[ 0 ] 124 | output = output.permute( 1, 2, 0 ).contiguous().view( -1, self.nChannel ) 125 | prob=output.data.cpu().numpy() 126 | prob=np.divide(np.exp(prob), np.sum(np.exp(prob), 1).reshape(-1, 1)) 127 | _, pred = torch.max( output, 1 ) 128 | pred = pred.data.cpu().numpy() 129 | return prob, pred 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /TESLA_package/TESLA/TLS_detection.py: -------------------------------------------------------------------------------- 1 | import os, sys, csv,re, time, random 2 | import cv2 3 | import numpy as np 4 | import pandas as pd 5 | import scanpy as sc 6 | from scipy.sparse import issparse 7 | from . util import * 8 | from . contour_util import * 9 | 10 | def TLS_detection( pred_refined_list, cluster_density_list, num_required, cnt_color, pooling="min"): 11 | pred_TLS=np.zeros([len(pred_refined_list[0]), len(pred_refined_list)]) 12 | for i in range(len(pred_refined_list)): 13 | tmp=np.zeros(pred_refined_list[i].shape) 14 | for k, v in cluster_density_list[i].items(): 15 | tmp[pred_refined_list[i]==k]=v/np.max(list(cluster_density_list[i].values())) 16 | pred_TLS[:,i]=tmp 17 | target = np.partition(pred_TLS, -num_required, axis=1)[:,-num_required:] #Select top num_required 18 | if pooling=="mean": 19 | target=np.mean(target, axis=1) 20 | elif pooling=="min": 21 | target=np.min(target, axis=1) 22 | else: 23 | print("Error! Pooling logic not understood.") 24 | target=(target-np.min(target))/(np.max(target)-np.min(target)) 25 | target[target<0.5]=0 26 | return target 27 | 28 | 29 | def plot_TLS_score(img, resize_factor, binary,target, cnt_color): 30 | resize_width=int(img.shape[1]*resize_factor) 31 | resize_height=int(img.shape[0]*resize_factor) 32 | binary_resized=cv2.resize(binary, (resize_width, resize_height)) 33 | img_resized =cv2.resize(img, (resize_width, resize_height)) 34 | target_img=target.reshape(resize_height, resize_width) 35 | target_img_rgb=(cnt_color((target*255).astype("int"))[:, 0:3]*255).reshape(resize_height, resize_width,3).astype( np.uint8 ) 36 | target_img_rgb=(cnt_color((target*255).astype("int"))[:, 0:3]*255).reshape(resize_height, resize_width,3).astype( np.uint8 ) 37 | target_img_rgb=cv2.cvtColor(target_img_rgb, cv2.COLOR_RGB2BGR) 38 | ret_img=img_resized.copy() 39 | #Whiten 40 | white_ratio=0.5 41 | ret_img[binary_resized!=0]=ret_img[binary_resized!=0]*(1-white_ratio)+np.array([255, 255, 255])*(white_ratio) 42 | ret_img[target_img!=0]=target_img_rgb[target_img!=0] 43 | ret_img[binary_resized==0]=255 44 | return ret_img 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /TESLA_package/TESLA/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.2.4' 2 | from . TESLA import * 3 | from . util import * 4 | from . contour_util import * 5 | from . calculate_dis import * 6 | from . imputation import * 7 | from . annotation import * 8 | from . contour_util import * 9 | from . TLS_detection import * 10 | from . tumor_edge_core import * 11 | -------------------------------------------------------------------------------- /TESLA_package/TESLA/__pycache__/TESLA.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianhuupenn/TESLA/8c4dcf896497dd4a34a6e720f03ec68c1502d571/TESLA_package/TESLA/__pycache__/TESLA.cpython-37.pyc -------------------------------------------------------------------------------- /TESLA_package/TESLA/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianhuupenn/TESLA/8c4dcf896497dd4a34a6e720f03ec68c1502d571/TESLA_package/TESLA/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /TESLA_package/TESLA/__pycache__/annotation.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianhuupenn/TESLA/8c4dcf896497dd4a34a6e720f03ec68c1502d571/TESLA_package/TESLA/__pycache__/annotation.cpython-37.pyc -------------------------------------------------------------------------------- /TESLA_package/TESLA/__pycache__/calculate_dis.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianhuupenn/TESLA/8c4dcf896497dd4a34a6e720f03ec68c1502d571/TESLA_package/TESLA/__pycache__/calculate_dis.cpython-37.pyc -------------------------------------------------------------------------------- /TESLA_package/TESLA/__pycache__/contour_util.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianhuupenn/TESLA/8c4dcf896497dd4a34a6e720f03ec68c1502d571/TESLA_package/TESLA/__pycache__/contour_util.cpython-37.pyc -------------------------------------------------------------------------------- /TESLA_package/TESLA/__pycache__/imputation.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianhuupenn/TESLA/8c4dcf896497dd4a34a6e720f03ec68c1502d571/TESLA_package/TESLA/__pycache__/imputation.cpython-37.pyc -------------------------------------------------------------------------------- /TESLA_package/TESLA/__pycache__/models.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianhuupenn/TESLA/8c4dcf896497dd4a34a6e720f03ec68c1502d571/TESLA_package/TESLA/__pycache__/models.cpython-37.pyc -------------------------------------------------------------------------------- /TESLA_package/TESLA/__pycache__/util.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianhuupenn/TESLA/8c4dcf896497dd4a34a6e720f03ec68c1502d571/TESLA_package/TESLA/__pycache__/util.cpython-37.pyc -------------------------------------------------------------------------------- /TESLA_package/TESLA/annotation.py: -------------------------------------------------------------------------------- 1 | import os,csv,re, time, random 2 | import cv2 3 | import numpy as np 4 | import pandas as pd 5 | import torch 6 | import scanpy as sc 7 | from . util import * 8 | from . contour_util import * 9 | from . calculate_dis import * 10 | from . TESLA import TESLA 11 | 12 | 13 | def annotation(img, 14 | sudo_adata, 15 | genes, 16 | resize_factor, 17 | binary, 18 | res=50, 19 | num_required=1, 20 | pooling="min", 21 | rseed=100, 22 | tseed=100, 23 | nseed=100, 24 | nChannel=100, 25 | threshold=1000, 26 | radius=3, 27 | minLabels=30, 28 | train_refine=True, 29 | plot_intermedium=False, 30 | target_size="small", 31 | min_UMI=1): 32 | #-------------------------------Image band-------------------------------------------------# 33 | if target_size=="small": 34 | target_ratio=1/2 35 | elif target_size=="large": 36 | target_ratio=1/3 37 | else: 38 | print("target_size not valid, Please specify (small / large).Use default small") 39 | target_ratio=1/2 40 | print("Computing image band...") 41 | resize_width=int(img.shape[1]*resize_factor) 42 | resize_height=int(img.shape[0]*resize_factor) 43 | img_resized=(img * np.dstack([(binary!=0)]*3)).astype(np.uint8) 44 | img_resized = cv2.resize(img_resized, (resize_width, resize_height), interpolation = cv2.INTER_AREA) 45 | gray_resized = cv2.cvtColor(img_resized,cv2.COLOR_BGR2GRAY) 46 | gray_resized=gray_resized.reshape(list(gray_resized.shape)+[1]) 47 | binary_resized=cv2.resize(binary, (resize_width, resize_height), interpolation = cv2.INTER_AREA) 48 | img_band1=(gray_resized-np.min(gray_resized))/(np.max(gray_resized)-np.min(gray_resized)) 49 | #-------------------------------Gene band-------------------------------------------------# 50 | print("Computing gene band...") 51 | genes=list(set([i for i in genes if i in sudo_adata.var.index ])) 52 | assert num_required<=len(genes) 53 | tmp=sudo_adata.X[:,sudo_adata.var.index.isin(genes)] 54 | tmp=(tmp-np.min(tmp, 0))/(np.max(tmp, 0)-np.min(tmp, 0)) 55 | tmp = np.partition(tmp, -num_required, axis=1)[:,-num_required:] #Select top num_required 56 | if pooling=="mean": 57 | sudo_adata.obs["meta"]=np.mean(tmp, axis=1) 58 | elif pooling=="min": 59 | sudo_adata.obs["meta"]=np.min(tmp, axis=1) 60 | else: 61 | print("Error! Pooling logic not understood.") 62 | gene_img=np.zeros(list(img.shape[0:2])+[1]) 63 | for _, row in sudo_adata.obs.iterrows(): 64 | x=row["x"] 65 | y=row["y"] 66 | exp=row["meta"] 67 | gene_img[int(x-res/2):int(x+res/2), int(y-res/2):int(y+res/2),0]=exp 68 | gene_img[binary==0]=0 69 | gene_band1=gene_img[:,:,0] 70 | #Filter on min UMI 71 | gene_band1[gene_band1<=np.log(min_UMI+1)]=0 72 | gene_band1=cv2.resize(gene_band1, (resize_width, resize_height), interpolation = cv2.INTER_AREA) 73 | gene_band1=(gene_band1-np.min(gene_band1))/(np.max(gene_band1)-np.min(gene_band1)) 74 | gene_band1=gene_band1.reshape(list(gene_band1.shape)+[1]) 75 | #-------------------------------TESLA-------------------------------------------------# 76 | print("Running TESLA...") 77 | assert np.max(img_band1)==1 and np.min(img_band1)==0 and np.max(gene_band1)==1 and np.min(gene_band1)==0 78 | data=np.concatenate((img_band1, gene_band1), axis=2) 79 | random.seed(rseed) 80 | torch.manual_seed(tseed) 81 | np.random.seed(nseed) 82 | tesla=TESLA() 83 | tesla.train(input=data, use_cuda=False, train_refine=train_refine, radius=radius, nChannel=nChannel, lr=0.1, 84 | minLabels=minLabels, maxIter=30, stepsize_sim=1, stepsize_con=5, threshold=threshold, plot_intermedium=plot_intermedium, plot_dir=None) 85 | prob, pred = tesla.predict(data) 86 | pred_refined=refine_clusters(pred=pred, resize_height=resize_height, resize_width=resize_width, threshold=threshold, radius=radius) 87 | nLabels=len(np.unique(pred)) 88 | mainLabels=len(np.unique(pred_refined)) 89 | #-----------------------------------Find target cluster---------------------------# 90 | print("Finding target clusters...") 91 | marker=gene_band1.flatten() 92 | clusters=pred_refined.flatten() 93 | c_m={} #cluster_marker expression 94 | for i in np.unique(clusters): 95 | c_m[i]=np.mean(marker[clusters==i]) 96 | c_m = sorted(c_m.items(), key=lambda x: x[1], reverse=True) 97 | target_clusters=list(filter(lambda x: (x[1] > c_m[0][1]*target_ratio), c_m)) 98 | target_clusters=[x[0] for x in target_clusters] 99 | print("c_m:\n", c_m, "\n", "Target clusters:\n", target_clusters, sep = '') 100 | return pred_refined, target_clusters, c_m 101 | 102 | def visualize_annotation(pred_refined, 103 | target_clusters, 104 | c_m, 105 | img, 106 | binary, 107 | resize_factor, 108 | cnt_color=clr.LinearSegmentedColormap.from_list('red', ["#EAE7CC", '#BA0000'], N=256)): 109 | resize_width=int(img.shape[1]*resize_factor) 110 | resize_height=int(img.shape[0]*resize_factor) 111 | binary_resized=cv2.resize(binary, (resize_width, resize_height), interpolation = cv2.INTER_AREA) 112 | background = cv2.resize(img, (resize_width, resize_height), interpolation = cv2.INTER_AREA) 113 | ret_img=(background.copy()).astype(np.uint8) 114 | alpha=0.8 115 | #Whiten 116 | white_ratio=0.5 117 | ret_img=ret_img*(1-white_ratio)+np.array([255, 255, 255])*(white_ratio) 118 | for i in range(len(target_clusters)): 119 | color=((np.array(cnt_color(int(c_m[i][1]/c_m[0][1]*255)))[0:3])*255).astype(int)[::-1] 120 | target_img=(1*(pred_refined==target_clusters[i])).reshape(resize_height, resize_width) 121 | target_img[binary_resized==0]=0 122 | ret_img[target_img!=0]=ret_img[target_img!=0]*(1-alpha)+np.array(color)*(alpha) 123 | return ret_img 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /TESLA_package/TESLA/calculate_dis.py: -------------------------------------------------------------------------------- 1 | import os,csv,re 2 | import pandas as pd 3 | import numpy as np 4 | import scanpy as sc 5 | import math 6 | import matplotlib.colors as clr 7 | import matplotlib.pyplot as plt 8 | 9 | def distance(t1,t2): 10 | sum=((t1-t2)**2).sum() 11 | return math.sqrt(sum) 12 | 13 | def calculate_dis_matrix(x, y, x_pixel=None, y_pixel=None, image=None, beta=49, alpha=1, histology=True): 14 | #x,y,x_pixel, y_pixel are lists 15 | dis=np.zeros((len(x),len(x))) 16 | if histology: 17 | assert (x_pixel is not None) & (x_pixel is not None) & (image is not None) 18 | assert (len(x)==len(x_pixel)) & (len(y)==len(y_pixel)) 19 | print("Calculateing distance matrix using histology image...") 20 | #beta to control the range of neighbourhood when calculate grey vale for one spot 21 | #alpha to control the color scale 22 | beta_half=round(beta/2) 23 | g=[] 24 | for i in range(len(x_pixel)): 25 | max_x=image.shape[0] 26 | max_y=image.shape[1] 27 | nbs=image[max(0,x_pixel[i]-beta_half):min(max_x,x_pixel[i]+beta_half+1),max(0,y_pixel[i]-beta_half):min(max_y,y_pixel[i]+beta_half+1)] 28 | g.append(np.mean(np.mean(nbs,axis=0),axis=0)) 29 | c0, c1, c2=[], [], [] 30 | for i in g: 31 | c0.append(i[0]) 32 | c1.append(i[1]) 33 | c2.append(i[2]) 34 | c0=np.array(c0) 35 | c1=np.array(c1) 36 | c2=np.array(c2) 37 | print("Var of c0,c1,c2 = ", np.var(c0),np.var(c1),np.var(c2)) 38 | c3=(c0*np.var(c0)+c1*np.var(c1)+c2*np.var(c2))/(np.var(c0)+np.var(c1)+np.var(c2)) 39 | c4=(c3-np.mean(c3))/np.std(c3) 40 | z_scale=np.max([np.std(x), np.std(y)])*alpha 41 | z=c4*z_scale 42 | z=z.tolist() 43 | print("Var of x,y,z = ", np.var(x),np.var(y),np.var(z)) 44 | for i in range(len(x)): 45 | if i%500==0: 46 | print("Calculating spot ", i, "/", len(x)) 47 | for j in range(i, len(x)): 48 | cord1=np.array([x[i],y[i],z[i]]) 49 | cord2=np.array([x[j],y[j],z[j]]) 50 | dis[i][j]=dis[j][i]=distance(cord1, cord2) 51 | return dis, z 52 | else: 53 | print("Calculateing distance matrix using xy only...") 54 | for i in range(len(x)): 55 | if i%50==0: 56 | print("Calculating spot ", i, "/", len(x)) 57 | for j in range(i, len(x)): 58 | cord1=np.array([x[i],y[i]]) 59 | cord2=np.array([x[j],y[j]]) 60 | dis[i][j]=dis[j][i]=distance(cord1, cord2) 61 | return dis 62 | 63 | def extract_color(x_pixel=None, y_pixel=None, image=None, beta=49, RGB=True): 64 | if RGB: 65 | #beta to control the range of neighbourhood when calculate grey vale for one spot 66 | beta_half=round(beta/2) 67 | g=[] 68 | for i in range(len(x_pixel)): 69 | max_x=image.shape[0] 70 | max_y=image.shape[1] 71 | nbs=image[max(0,x_pixel[i]-beta_half):min(max_x,x_pixel[i]+beta_half+1),max(0,y_pixel[i]-beta_half):min(max_y,y_pixel[i]+beta_half+1)] 72 | g.append(np.mean(np.mean(nbs,axis=0),axis=0)) 73 | c0, c1, c2=[], [], [] 74 | for i in g: 75 | c0.append(i[0]) 76 | c1.append(i[1]) 77 | c2.append(i[2]) 78 | c0=np.array(c0) 79 | c1=np.array(c1) 80 | c2=np.array(c2) 81 | c3=(c0*np.var(c0)+c1*np.var(c1)+c2*np.var(c2))/(np.var(c0)+np.var(c1)+np.var(c2)) 82 | else: 83 | beta_half=round(beta/2) 84 | g=[] 85 | for i in range(len(x_pixel)): 86 | max_x=image.shape[0] 87 | max_y=image.shape[1] 88 | nbs=image[max(0,x_pixel[i]-beta_half):min(max_x,x_pixel[i]+beta_half+1),max(0,y_pixel[i]-beta_half):min(max_y,y_pixel[i]+beta_half+1)] 89 | g.append(np.mean(nbs)) 90 | c3=np.array(g) 91 | return c3 92 | -------------------------------------------------------------------------------- /TESLA_package/TESLA/contour_util.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import cv2 4 | 5 | def scan_contour(spots, scan_x=True, shape="hexagon"): 6 | #shape="hexagon" # For 10X Vsium, shape="square" for ST data 7 | if scan_x: 8 | array_a="array_x" 9 | array_b="array_y" 10 | pixel_a="pixel_x" 11 | pixel_b="pixel_y" 12 | else: 13 | array_a="array_y" 14 | array_b="array_x" 15 | pixel_a="pixel_y" 16 | pixel_b="pixel_x" 17 | upper, lower={}, {} 18 | uniq_array_a=sorted(set(spots[array_a])) 19 | if shape=="hexagon": 20 | for i in range(len(uniq_array_a)-1): 21 | a1=uniq_array_a[i] 22 | a2=uniq_array_a[i+1] 23 | group=spots.loc[spots[array_a].isin([a1, a2]),:] 24 | lower[np.min(group[pixel_a])]=np.min(group[pixel_b]) 25 | upper[np.min(group[pixel_a])]=np.max(group[pixel_b]) 26 | #print(a1, lower[np.min(group[pixel_a])], upper[np.min(group[pixel_a])]) 27 | a1=uniq_array_a[-1] 28 | a2=uniq_array_a[-2] 29 | group=spots.loc[spots[array_a].isin([a1, a2]),:] 30 | lower[np.min(group[pixel_a])]=np.min(group[pixel_b]) 31 | upper[np.min(group[pixel_a])]=np.max(group[pixel_b]) 32 | #print(a1, lower[np.min(group[pixel_a])], upper[np.min(group[pixel_a])]) 33 | elif shape=="square": 34 | for i in range(len(uniq_array_a)-1): 35 | a1=uniq_array_a[i] 36 | group=spots.loc[spots[array_a]==a1,:] 37 | lower[np.min(group[pixel_a])]=np.min(group[pixel_b]) 38 | upper[np.min(group[pixel_a])]=np.max(group[pixel_b]) 39 | a1=uniq_array_a[-1] 40 | group=spots.loc[spots[array_a]==a1,:] 41 | lower[np.min(group[pixel_a])]=np.min(group[pixel_b]) 42 | upper[np.min(group[pixel_a])]=np.max(group[pixel_b]) 43 | else: 44 | print("Error, unknown shape, pls specify 'square' or 'hexagon'.") 45 | lower=np.array(list(lower.items())[::-1]).astype("int32") 46 | upper=np.array(list(upper.items())).astype("int32") 47 | cnt=np.concatenate((upper, lower), axis=0) 48 | cnt=cnt.reshape(cnt.shape[0], 1, 2) 49 | if scan_x: 50 | cnt=cnt[:, : , [1, 0]] 51 | return cnt 52 | 53 | def cv2_detect_contour(img, 54 | CANNY_THRESH_1 = 100, 55 | CANNY_THRESH_2 = 200, 56 | apertureSize=5, 57 | L2gradient = True, 58 | all_cnt_info=False): 59 | if len(img.shape)==3: 60 | gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) 61 | elif len(img.shape)==2: 62 | gray=(img*((1, 255)[np.max(img)<=1])).astype(np.uint8) 63 | else: 64 | print("Image format error!") 65 | edges = cv2.Canny(gray, CANNY_THRESH_1, CANNY_THRESH_2,apertureSize = apertureSize, L2gradient = L2gradient) 66 | edges = cv2.dilate(edges, None) 67 | edges = cv2.erode(edges, None) 68 | cnt_info = [] 69 | cnts, _ = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE) 70 | for c in cnts: 71 | cnt_info.append((c,cv2.isContourConvex(c),cv2.contourArea(c),)) 72 | cnt_info = sorted(cnt_info, key=lambda c: c[2], reverse=True) 73 | cnt=cnt_info[0][0] 74 | if all_cnt_info: 75 | return cnt_info 76 | else: 77 | return cnt 78 | 79 | 80 | def cut_contour_boundary(cnt, x_min, x_max, y_min, y_max, enlarge): 81 | ret=cnt.copy() 82 | ret[:, : , 0][ret[:, : , 0]>y_max]=y_max 83 | ret[:, : , 0][ret[:, : , 0]x_max]=x_max 85 | ret[:, : , 1][ret[:, : , 1]0: 62 | dis_tmp=(nbs.to_numpy()+0.1)/np.min(nbs.to_numpy()+0.1) #avoid 0 distance 63 | if isinstance(k, int): 64 | weights=((1/(dis_tmp**k))/((1/(dis_tmp**k)).sum())) 65 | else: 66 | weights=np.exp(-dis_tmp)/np.sum(np.exp(-dis_tmp)) 67 | #For superpixel have super far nbs, weights=0 68 | if np.min(nbs.values)>dis_threshold: 69 | weights=weights*0 70 | row_index=[known_adata.obs.index.get_loc(i) for i in nbs.index] 71 | sudo_adata.X[i, :]=np.dot(weights, known_adata.X[row_index,:]) 72 | else: 73 | sudo_adata.X[i, :]=0 74 | return sudo_adata 75 | 76 | -------------------------------------------------------------------------------- /TESLA_package/TESLA/models.py: -------------------------------------------------------------------------------- 1 | #from __future__ import print_function 2 | import torch 3 | import torch.nn as nn 4 | import torch.nn.functional as F 5 | import torch.optim as optim 6 | import numpy as np 7 | import torch.nn.init 8 | 9 | class Spatial_CNN(nn.Module): 10 | def __init__(self,input_dim, nChannel=100, nConv=2, kernel_size_list=[3,3],stride_list=[1,1], padding_list=[1,1]): 11 | super(Spatial_CNN, self).__init__() 12 | assert nConv==len(kernel_size_list)==len(stride_list)==len(padding_list) 13 | self.nChannel=nChannel 14 | self.nConv=nConv 15 | self.conv1 = nn.Conv2d(input_dim, self.nChannel, kernel_size=kernel_size_list[0], stride=stride_list[0], padding=padding_list[0] ) 16 | self.bn1 = nn.BatchNorm2d(self.nChannel) 17 | self.conv2 = nn.ModuleList() 18 | self.bn2 = nn.ModuleList() 19 | for i in range(self.nConv-1): 20 | self.conv2.append( nn.Conv2d(self.nChannel, self.nChannel, kernel_size=kernel_size_list[i+1], stride=stride_list[i+1], padding=padding_list[i+1] ) ) 21 | self.bn2.append( nn.BatchNorm2d(self.nChannel) ) 22 | self.conv3 = nn.Conv2d(nChannel, nChannel, kernel_size=1, stride=1, padding=0 ) 23 | self.bn3 = nn.BatchNorm2d(self.nChannel) 24 | def forward(self, x): 25 | x = self.conv1(x) 26 | x = F.relu( x ) 27 | x = self.bn1(x) 28 | for i in range(self.nConv-1): 29 | x = self.conv2[i](x) 30 | x = F.relu( x ) 31 | x = self.bn2[i](x) 32 | x = self.conv3(x) 33 | x = self.bn3(x) 34 | return x 35 | -------------------------------------------------------------------------------- /TESLA_package/TESLA/tumor_edge_core.py: -------------------------------------------------------------------------------- 1 | import os, sys, csv,re, time, random 2 | import cv2 3 | import numpy as np 4 | import pandas as pd 5 | import scanpy as sc 6 | from scipy.sparse import issparse 7 | from . util import * 8 | from . contour_util import * 9 | from . ConnectedComponents import Graph 10 | 11 | 12 | def leading_edge_detection(img, pred_refined, resize_factor, target_clusters, binary): 13 | resize_width=int(img.shape[1]*resize_factor) 14 | resize_height=int(img.shape[0]*resize_factor) 15 | img_new = img.copy() 16 | img_new_resized = cv2.resize(img_new, (resize_width, resize_height)) 17 | for t in target_clusters: 18 | target_img=(1*(pred_refined==t)).reshape(resize_height, resize_width) 19 | cnt_info=cv2_detect_contour((target_img==0).astype(np.uint8), apertureSize=5,L2gradient = True, all_cnt_info=True) 20 | cnt_info=list(filter(lambda x: (x[2] > 2000), cnt_info)) 21 | for i in range(len(cnt_info)): 22 | cv2.drawContours(img_new_resized, [cnt_info[i][0]], -1, ([0, 0, 0]), thickness=2) #BGR 23 | return img_new_resized 24 | 25 | 26 | def tumor_edge_core_separation(img, binary, resize_factor, pred_refined, target_clusters, sudo_adata, res, shrink_rate=0.8): 27 | resize_width=int(img.shape[1]*resize_factor) 28 | resize_height=int(img.shape[0]*resize_factor) 29 | #Extract target pixels 30 | target_img=(1*(np.isin(pred_refined, target_clusters))).reshape(resize_height, resize_width) 31 | #Filter using binary filter 32 | binary_resized=cv2.resize(binary, (resize_width, resize_height), interpolation = cv2.INTER_AREA) 33 | target_img=target_img*(binary_resized!=0) 34 | tumor_binary=cv2.resize(target_img.astype(np.uint8), ((img.shape[1], img.shape[0]))) 35 | tumor_index=[] 36 | for index, row in sudo_adata.obs.iterrows(): 37 | x=int(row["x"]) 38 | y=int(row["y"]) 39 | if tumor_binary[x, y]!=0: tumor_index.append(index) 40 | sudo_tumor=sudo_adata[sudo_adata.obs.index.isin(tumor_index)] 41 | print("Running Connected Components ...") 42 | adj=pd.DataFrame(np.zeros([sudo_tumor.shape[0], sudo_tumor.shape[0]]), columns=sudo_tumor.obs.index.tolist(), index=sudo_tumor.obs.index.tolist()) 43 | for index, row in sudo_tumor.obs.iterrows(): 44 | x, y=row["x"], row["y"] 45 | nbr=sudo_tumor.obs[((sudo_tumor.obs["x"]==x) & (np.abs(sudo_tumor.obs["y"]-y)<=res))| ((sudo_tumor.obs["y"]==y) & (np.abs(sudo_tumor.obs["x"]-x)<=res))] 46 | adj.loc[index, nbr.index.tolist()]=1 47 | sys.setrecursionlimit(adj.shape[0]) 48 | g = Graph(V=adj.index.tolist(), adj=adj) 49 | c_c = g.ConnectedComponents() 50 | cc_info=[] 51 | for c in c_c: 52 | cc_info.append((c,len(c))) 53 | cc_info = sorted(cc_info, key=lambda c: c[1], reverse=True) 54 | print("Running Select biggest Tumor region ...") 55 | #cc_info[0][0] contains the index of all superpiexls in the biggest tumor region 56 | sudo_tumor_sub=sudo_tumor[sudo_tumor.obs.index.isin(cc_info[0][0])] 57 | adj_sub=adj.loc[cc_info[0][0], cc_info[0][0]] 58 | adj_sub=adj_sub[np.sum(adj_sub, 1)>2] 59 | sudo_tumor_sub=sudo_tumor_sub[sudo_tumor_sub.obs.index.isin(adj_sub.index.tolist())] 60 | binary_tumor = np.zeros(img.shape[0:2]).astype(np.uint8) 61 | for index, row in sudo_tumor_sub.obs.iterrows(): 62 | x=int(row["x"]) 63 | y=int(row["y"]) 64 | binary_tumor[int(x-res/2):int(x+res/2), int(y-res/2):int(y+res/2)]=1 65 | cnt_tumor=cv2_detect_contour((binary_tumor==0).astype(np.uint8), apertureSize=5,L2gradient = True) 66 | print("Running Core and edge separation ...") 67 | cnt_tumor_reduced=scale_contour(cnt_tumor, shrink_rate) 68 | binary_core = np.zeros(img.shape[0:2]).astype(np.uint8) 69 | cv2.drawContours(binary_core, [cnt_tumor_reduced], -1, (1), thickness=-1) 70 | binary_core=binary_core*binary_tumor 71 | cnt_core=cv2_detect_contour((binary_core==0).astype(np.uint8), apertureSize=5,L2gradient = True) 72 | print("Running Create Gene Expression adata for tumor edge vs. core ...") 73 | region=[] 74 | for index, row in sudo_tumor_sub.obs.iterrows(): 75 | x=int(row["x"]) 76 | y=int(row["y"]) 77 | region.append(("edge","core",)[binary_core[x, y]]) 78 | sudo_tumor_sub.obs["region"]=region 79 | return binary_tumor, binary_core, sudo_tumor_sub 80 | 81 | 82 | def plot_tumor_edge_core(img, resize_factor, binary, binary_tumor, binary_core, color_edge=[66, 50, 225], color_core=[62, 25, 53]): 83 | resize_width=int(img.shape[1]*resize_factor) 84 | resize_height=int(img.shape[0]*resize_factor) 85 | white_ratio=0.5 86 | alpha=0.8 87 | ret_img=img.copy() 88 | cnt_tumor=cv2_detect_contour((binary_tumor==0).astype(np.uint8), apertureSize=5,L2gradient = True) 89 | cnt_core=cv2_detect_contour((binary_core==0).astype(np.uint8), apertureSize=5,L2gradient = True) 90 | ret_img[binary!=0]=ret_img[binary!=0]*(1-white_ratio)+np.array([255, 255, 255])*(white_ratio) 91 | #Core and edge region 92 | target_img = np.zeros(img.shape, dtype=np.uint8) 93 | cv2.drawContours(target_img, [cnt_tumor], -1, (color_edge), thickness=-1) #BGR 94 | cv2.drawContours(target_img, [cnt_core], -1, (color_core), thickness=-1) #BGR 95 | #Overlay 96 | ret_img[binary_tumor!=0]=ret_img[binary_tumor!=0]*(1-alpha)+target_img[binary_tumor!=0]*alpha 97 | ret_img=cv2.resize(ret_img, (resize_width, resize_height), interpolation = cv2.INTER_AREA) 98 | return ret_img 99 | 100 | 101 | def tumor_edge_core_DE(sudo_core_edge): 102 | sc.tl.rank_genes_groups(sudo_core_edge, groupby="region",reference="rest",n_genes=sudo_core_edge.shape[1], method='wilcoxon') 103 | pvals_adj1=[i[1] for i in sudo_core_edge.uns['rank_genes_groups']["pvals_adj"]] 104 | pvals_adj0=[i[0] for i in sudo_core_edge.uns['rank_genes_groups']["pvals_adj"]] 105 | genes1=[i[1] for i in sudo_core_edge.uns['rank_genes_groups']["names"]] 106 | genes0=[i[0] for i in sudo_core_edge.uns['rank_genes_groups']["names"]] 107 | if issparse(sudo_core_edge.X): 108 | obs_tidy=pd.DataFrame(sudo_core_edge.X.A) 109 | else: 110 | obs_tidy=pd.DataFrame(sudo_core_edge.X) 111 | obs_tidy.index=sudo_core_edge.obs["region"].tolist() 112 | obs_tidy.columns=sudo_core_edge.var.index.tolist() 113 | #--------Edge enriched genes 114 | obs_tidy1=obs_tidy.loc[:,genes1] 115 | # 1. compute mean value 116 | mean_obs1 = obs_tidy1.groupby(level=0).mean() 117 | mean_all1=obs_tidy1.mean() 118 | # 2. compute fraction of cells having value >0 119 | obs_bool1 = obs_tidy1.astype(bool) 120 | fraction_obs1 = obs_bool1.groupby(level=0).sum() / obs_bool1.groupby(level=0).count() 121 | # compute fold change. 122 | fold_change1 = (mean_obs1.loc["edge"] / (mean_obs1.loc["core"]+ 1e-9)).values 123 | df1 = {'edge_genes': genes1, 124 | 'in_group_fraction': fraction_obs1.loc["edge"].tolist(), 125 | "out_group_fraction":fraction_obs1.loc["core"].tolist(), 126 | "in_out_group_ratio":(fraction_obs1.loc["edge"]/fraction_obs1.loc["core"]).tolist(), 127 | "in_group_mean_exp": mean_obs1.loc["edge"].tolist(), 128 | "out_group_mean_exp": mean_obs1.loc["core"].tolist(), 129 | "fold_change":fold_change1.tolist(), 130 | "pvals_adj":pvals_adj1, 131 | "all_mean_exp": mean_all1} 132 | df1 = pd.DataFrame(data=df1) 133 | df1=df1[(df1["pvals_adj"]<0.05)] 134 | #--------Core enriched genes 135 | obs_tidy0=obs_tidy.loc[:,genes0] 136 | # 1. compute mean value 137 | mean_obs0 = obs_tidy0.groupby(level=0).mean() 138 | mean_all0=obs_tidy0.mean() 139 | # 2. compute fraction of cells having value >0 140 | obs_bool0 = obs_tidy0.astype(bool) 141 | fraction_obs0 = obs_bool0.groupby(level=0).sum() / obs_bool0.groupby(level=0).count() 142 | # compute fold change. 143 | fold_change0 = (mean_obs0.loc["core"] / (mean_obs0.loc["edge"]+ 1e-9)).values 144 | df0 = {'edge_genes': genes0, 145 | 'in_group_fraction': fraction_obs0.loc["core"].tolist(), 146 | "out_group_fraction":fraction_obs0.loc["edge"].tolist(), 147 | "in_out_group_ratio":(fraction_obs0.loc["core"]/fraction_obs0.loc["edge"]).tolist(), 148 | "in_group_mean_exp": mean_obs0.loc["core"].tolist(), 149 | "out_group_mean_exp": mean_obs0.loc["edge"].tolist(), 150 | "fold_change":fold_change0.tolist(), 151 | "pvals_adj":pvals_adj0, 152 | "all_mean_exp": mean_all0} 153 | df0 = pd.DataFrame(data=df0) 154 | df0=df0[(df0["pvals_adj"]<0.05)] 155 | return df0, df1 156 | 157 | def filter_DE_genes(df, min_all_mean_exp=0.1, min_in_out_group_ratio=1, min_in_group_fraction=0.5, min_fold_change=1.2): 158 | df_filtered=df.copy() 159 | df_filtered=df_filtered[(df_filtered["all_mean_exp"]>min_all_mean_exp) & 160 | (df_filtered["in_out_group_ratio"]>=min_in_out_group_ratio) & 161 | (df_filtered["in_group_fraction"]>min_in_group_fraction) & 162 | (df_filtered["fold_change"]>min_fold_change)] 163 | return df_filtered 164 | 165 | def plot_edge_core_enrichd_genes(img, resize_factor, binary, binary_tumor, sudo_core_edge, genes, cnt_color, plot_dir, res): 166 | resize_width=int(img.shape[1]*resize_factor) 167 | resize_height=int(img.shape[0]*resize_factor) 168 | if issparse(sudo_core_edge.X):sudo_core_edge.X=sudo_core_edge.X.A 169 | res=res*1.5 170 | white_ratio=0.5 171 | for g in genes: 172 | sudo_core_edge.obs["exp"]=sudo_core_edge.X[:,sudo_core_edge.var.index==g] 173 | sudo_core_edge.obs["relexp"]=(sudo_core_edge.obs["exp"]-np.min(sudo_core_edge.obs["exp"]))/(np.max(sudo_core_edge.obs["exp"])-np.min(sudo_core_edge.obs["exp"])) 174 | img_new=np.zeros(img.shape, dtype=np.uint8) 175 | for _, row in sudo_core_edge.obs.iterrows(): 176 | x=row["x"] 177 | y=row["y"] 178 | c=row["relexp"] 179 | img_new[int(x-res/2):int(x+res/2), int(y-res/2):int(y+res/2),:]=((np.array(cnt_color(int(c*255)))[0:3])*255).astype(int) 180 | img_new=cv2.cvtColor(img_new, cv2.COLOR_RGB2BGR) 181 | #Background 182 | ret_img=img.copy() 183 | ret_img[binary!=0]=ret_img[binary!=0]*(1-white_ratio)+np.array([255, 255, 255])*(white_ratio) 184 | #Overlay 185 | ret_img[binary_tumor!=0]=img_new[binary_tumor!=0] 186 | ret_img=cv2.resize(ret_img, (resize_width, resize_height), interpolation = cv2.INTER_AREA) 187 | cv2.imwrite(plot_dir+g+'.jpg', ret_img) 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /TESLA_package/TESLA/util.py: -------------------------------------------------------------------------------- 1 | import scanpy as sc 2 | import pandas as pd 3 | import numpy as np 4 | import scipy 5 | import os 6 | from anndata import AnnData,read_csv,read_text,read_mtx 7 | from scipy.sparse import issparse 8 | 9 | def prefilter_cells(adata,min_counts=None,max_counts=None,min_genes=200,max_genes=None): 10 | if min_genes is None and min_counts is None and max_genes is None and max_counts is None: 11 | raise ValueError('Provide one of min_counts, min_genes, max_counts or max_genes.') 12 | id_tmp=np.asarray([True]*adata.shape[0],dtype=bool) 13 | id_tmp=np.logical_and(id_tmp,sc.pp.filter_cells(adata.X,min_genes=min_genes)[0]) if min_genes is not None else id_tmp 14 | id_tmp=np.logical_and(id_tmp,sc.pp.filter_cells(adata.X,max_genes=max_genes)[0]) if max_genes is not None else id_tmp 15 | id_tmp=np.logical_and(id_tmp,sc.pp.filter_cells(adata.X,min_counts=min_counts)[0]) if min_counts is not None else id_tmp 16 | id_tmp=np.logical_and(id_tmp,sc.pp.filter_cells(adata.X,max_counts=max_counts)[0]) if max_counts is not None else id_tmp 17 | adata._inplace_subset_obs(id_tmp) 18 | adata.raw=sc.pp.log1p(adata,copy=True) #check the rowname 19 | print("the var_names of adata.raw: adata.raw.var_names.is_unique=:",adata.raw.var_names.is_unique) 20 | 21 | 22 | def prefilter_genes(adata,min_counts=None,max_counts=None,min_cells=10,max_cells=None): 23 | if min_cells is None and min_counts is None and max_cells is None and max_counts is None: 24 | raise ValueError('Provide one of min_counts, min_genes, max_counts or max_genes.') 25 | id_tmp=np.asarray([True]*adata.shape[1],dtype=bool) 26 | id_tmp=np.logical_and(id_tmp,sc.pp.filter_genes(adata.X,min_cells=min_cells)[0]) if min_cells is not None else id_tmp 27 | id_tmp=np.logical_and(id_tmp,sc.pp.filter_genes(adata.X,max_cells=max_cells)[0]) if max_cells is not None else id_tmp 28 | id_tmp=np.logical_and(id_tmp,sc.pp.filter_genes(adata.X,min_counts=min_counts)[0]) if min_counts is not None else id_tmp 29 | id_tmp=np.logical_and(id_tmp,sc.pp.filter_genes(adata.X,max_counts=max_counts)[0]) if max_counts is not None else id_tmp 30 | adata._inplace_subset_var(id_tmp) 31 | 32 | 33 | def prefilter_specialgenes(adata,Gene1Pattern="ERCC",Gene2Pattern="MT-"): 34 | id_tmp1=np.asarray([not str(name).startswith(Gene1Pattern) for name in adata.var_names],dtype=bool) 35 | id_tmp2=np.asarray([not str(name).startswith(Gene2Pattern) for name in adata.var_names],dtype=bool) 36 | id_tmp=np.logical_and(id_tmp1,id_tmp2) 37 | adata._inplace_subset_var(id_tmp) 38 | 39 | def relative_func(expres): 40 | #expres: an array counts expression for a gene 41 | maxd = np.max(expres) - np.min(expres) 42 | min_exp=np.min(expres) 43 | rexpr = (expres - min_exp)/maxd 44 | return rexpr 45 | 46 | def plot_relative_exp(input_adata, gene, x_name, y_name,color,use_raw=False, spot_size=200000): 47 | adata=input_adata.copy() 48 | if use_raw: 49 | X=adata.raw.X 50 | else: 51 | X=adata.X 52 | if issparse(X): 53 | X=pd.DataFrame(X.A) 54 | else: 55 | X=pd.DataFrame(X) 56 | X.index=adata.obs.index 57 | X.columns=adata.var.index 58 | rexpr=relative_func(X.loc[:,gene]) 59 | adata.obs["rexpr"]=rexpr 60 | fig=sc.pl.scatter(adata,x=x_name,y=y_name,color="rexpr",title=gene+"_rexpr",color_map=color,show=False,size=spot_size/adata.shape[0]) 61 | return fig 62 | 63 | def plot_log_exp(input_adata, gene, x_name, y_name,color,use_raw=False): 64 | adata=input_adata.copy() 65 | if use_raw: 66 | X=adata.X 67 | else: 68 | X=adata.raw.X 69 | if issparse(X): 70 | X=pd.DataFrame(X.A) 71 | else: 72 | X=pd.DataFrame(X) 73 | X.index=adata.obs.index 74 | X.columns=adata.var.index 75 | adata.obs["log"]=np.log((X.loc[:,gene]+1).tolist()) 76 | fig=sc.pl.scatter(adata,x=x_name,y=y_name,color="log",title=gene+"_log",color_map=color,show=False,size=200000/adata.shape[0]) 77 | return fig 78 | 79 | def refine_clusters(pred, resize_height, resize_width, threshold, radius): 80 | pixel_num=pd.Series(pred).value_counts() 81 | clusters=pixel_num.index.tolist() 82 | reorder_map={} 83 | for i in range(pixel_num.shape[0]): 84 | reorder_map[clusters[i]]=i 85 | pred_reordered=pd.Series(pred).replace(reorder_map).to_numpy() 86 | pixel_num=pd.Series(pred_reordered).value_counts() 87 | # Number of clusters 88 | nLabels = len(np.unique(pred_reordered)) 89 | # Number of main clusters 90 | mainLabels=(pd.Series(pred_reordered).value_counts()>=threshold).sum() 91 | #------------- Refine clusters --------------------- 92 | main_clusters=pixel_num.index[pixel_num>=threshold].tolist() 93 | minor_clusters=pixel_num.index[pixel_num0: 105 | replace_map[i]=nbs_num.index[ nbs_num.index.isin(main_clusters) ][ 0 ] 106 | pred_refined=pd.Series(pred_reordered).replace(replace_map).to_numpy() 107 | return pred_refined 108 | -------------------------------------------------------------------------------- /TESLA_package/TESLAforST.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: TESLAforST 3 | Version: 1.2.2 4 | Summary: TESLA: Deciphering tumor ecosystems at super-resolution from spatial transcriptomics 5 | Home-page: https://github.com/jianhuupenn/TESLA 6 | Author: Jian Hu 7 | Author-email: jianhu@pennmedicine.upenn.edu 8 | License: UNKNOWN 9 | Description: # TESLA 10 | 11 | ## TESLA: Deciphering tumor ecosystems at super-resolution from spatial transcriptomics 12 | 13 | ### Jian Hu,*, Kyle Coleman, Edward B. Lee, Humam Kadara, Linghua Wang,*, Mingyao Li,* 14 | 15 | TESLA is a machine learning framework for multi-level tissue annotation with pixel-level resolution in ST. TESLA integrates histological information with gene expression to annotate heterogeneous immune and tumor cells directly on the histology image. TESLA further detects unique Tumor Microenvironment (TME) features such as tertiary lymphoid structures and differential transcriptome programs in the core or edge of a tumor, which represent a promising avenue for understanding the spatial architecture of the TME. Although we illustrated the applications in cancer, TESLA can also be applied to other diseases. 16 | For more info, please go to: 17 | https://github.com/jianhuupenn/TESLA 18 | Platform: UNKNOWN 19 | Classifier: Programming Language :: Python :: 3 20 | Classifier: License :: OSI Approved :: MIT License 21 | Classifier: Operating System :: OS Independent 22 | Requires-Python: >=3.6 23 | Description-Content-Type: text/markdown 24 | -------------------------------------------------------------------------------- /TESLA_package/TESLAforST.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | README.md 2 | setup.py 3 | TESLA/ConnectedComponents.py 4 | TESLA/TESLA.py 5 | TESLA/TLS_detection.py 6 | TESLA/__init__.py 7 | TESLA/annotation.py 8 | TESLA/calculate_dis.py 9 | TESLA/contour_util.py 10 | TESLA/imputation.py 11 | TESLA/models.py 12 | TESLA/tumor_edge_core.py 13 | TESLA/util.py 14 | TESLAforST.egg-info/PKG-INFO 15 | TESLAforST.egg-info/SOURCES.txt 16 | TESLAforST.egg-info/dependency_links.txt 17 | TESLAforST.egg-info/requires.txt 18 | TESLAforST.egg-info/top_level.txt -------------------------------------------------------------------------------- /TESLA_package/TESLAforST.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /TESLA_package/TESLAforST.egg-info/requires.txt: -------------------------------------------------------------------------------- 1 | torch 2 | pandas 3 | numpy 4 | scipy 5 | scanpy 6 | anndata 7 | sklearn 8 | numba 9 | -------------------------------------------------------------------------------- /TESLA_package/TESLAforST.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | TESLA 2 | -------------------------------------------------------------------------------- /TESLA_package/build/lib/TESLA/ConnectedComponents.py: -------------------------------------------------------------------------------- 1 | #Get connected components in an undirected graph 2 | import pandas as pd 3 | import numpy as np 4 | class Graph: 5 | # init function to declare class variables 6 | def __init__(self, V, adj): 7 | self.V = V 8 | self.adj = adj 9 | def DFSUtil(self, v, tmp, visited): 10 | # Mark the current vertex as visited 11 | visited[v] = 1 12 | # Store the vertex to list 13 | tmp.append(v) 14 | # Repeat for all vertices adjacent to this vertex v 15 | nbr=self.adj[v][self.adj[v]==1].index.tolist() 16 | for i in nbr: 17 | if visited[i] == 0: 18 | tmp = self.DFSUtil(i, tmp, visited) 19 | return tmp 20 | def ConnectedComponents(self): 21 | visited = pd.Series([0]* len(self.V), index=self.V) 22 | cc = [] 23 | for v in self.V: 24 | if visited[v] == 0: 25 | tmp = [] 26 | cc.append(self.DFSUtil(v, tmp, visited)) 27 | return cc 28 | -------------------------------------------------------------------------------- /TESLA_package/build/lib/TESLA/TESLA.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import time 4 | import cv2 5 | import argparse 6 | import torch 7 | import torch.nn as nn 8 | import torch.nn.functional as F 9 | import torch.optim as optim 10 | import torch.nn.init 11 | from torch.autograd import Variable 12 | from . models import Spatial_CNN 13 | 14 | class TESLA(object): 15 | def __init__(self): 16 | super(TESLA, self).__init__() 17 | 18 | def train(self, input, 19 | use_cuda=False, 20 | train_refine=True, 21 | radius=3, 22 | nChannel=100, 23 | lr=0.1, 24 | minLabels=20, 25 | maxIter=30, 26 | stepsize_sim=1, 27 | stepsize_con=10, 28 | threshold=1000, 29 | plot_intermedium=False, 30 | plot_dir="./"): 31 | self.use_cuda=use_cuda 32 | self.train_refine=train_refine 33 | self.radius=radius 34 | self.nChannel=nChannel 35 | self.lr=lr 36 | self.minLabels=minLabels 37 | self.maxIter=maxIter 38 | self.stepsize_sim=stepsize_sim 39 | self.stepsize_con=stepsize_con 40 | self.threshold=threshold 41 | self.plot_intermedium=plot_intermedium 42 | self.resize_height=input.shape[0] 43 | self.resize_width=input.shape[1] 44 | input = torch.from_numpy( np.array([input.transpose( (2, 0, 1) ).astype('float32')]) ) 45 | if use_cuda: 46 | input = input.cuda() 47 | input = Variable(input) 48 | #--------------------------------------- Train --------------------------------------- 49 | self.model = Spatial_CNN(input.size(1), nConv=2, nChannel=self.nChannel, kernel_size_list=[5, 5],stride_list=[1, 1], padding_list=[2, 2]) 50 | if use_cuda: 51 | self.model.cuda() 52 | # Similarity loss definition 53 | loss_fn = torch.nn.CrossEntropyLoss() 54 | # Continuity loss definition 55 | loss_hpy = torch.nn.L1Loss(size_average = True) 56 | loss_hpz = torch.nn.L1Loss(size_average = True) 57 | HPy_target = torch.zeros(self.resize_height-1, self.resize_width, nChannel) 58 | HPz_target = torch.zeros(self.resize_height, self.resize_width-1, nChannel) 59 | if use_cuda: 60 | HPy_target = HPy_target.cuda() 61 | HPz_target = HPz_target.cuda() 62 | optimizer = optim.SGD(self.model.parameters(), lr=lr, momentum=0.9) 63 | label_colours = np.random.randint(255,size=(100,3)) 64 | start_time = time.time() 65 | self.model.train() 66 | for batch_idx in range(maxIter): 67 | # forwarding 68 | optimizer.zero_grad() 69 | output = self.model( input )[ 0 ] 70 | output = output.permute( 1, 2, 0 ).contiguous().view( -1, nChannel ) 71 | outputHP = output.reshape( (self.resize_height, self.resize_width, nChannel) ) 72 | HPy = outputHP[1:, :, :] - outputHP[0:-1, :, :] 73 | HPz = outputHP[:, 1:, :] - outputHP[:, 0:-1, :] 74 | lhpy = loss_hpy(HPy,HPy_target) 75 | lhpz = loss_hpz(HPz,HPz_target) 76 | _, target = torch.max( output, 1 ) 77 | img_target = target.data.cpu().numpy() 78 | # Total number of clusters 79 | nLabels = len(np.unique(img_target)) 80 | # Number of main clusters 81 | mainLabels=(pd.Series(img_target).value_counts()>=threshold).sum() 82 | #--------------------------Refine during training---------------------------------------- 83 | if train_refine: 84 | pixel_num=pd.Series(img_target).value_counts() 85 | main_clusters=pixel_num.index[pixel_num>=threshold].tolist() 86 | minor_clusters=pixel_num.index[pixel_num0: 98 | replace_map[i]=nbs_num.index[nbs_num.index.isin(main_clusters)][0] 99 | target=torch.from_numpy(pd.Series(img_target).replace(replace_map).to_numpy()) 100 | #------------------------------------------------------------------ 101 | loss1 = stepsize_sim * loss_fn(output, target) 102 | loss2 = stepsize_con * (lhpy + lhpz) 103 | loss=loss1+loss2 104 | loss.backward() 105 | optimizer.step() 106 | print (batch_idx, '/', maxIter, '|', ' label num :', nLabels, '|',' main clusters :',mainLabels,' | feature loss :', loss1.item(),' | spatial loss :', loss2.item()) 107 | print("--- %s seconds ---" % (time.time() - start_time)) 108 | if batch_idx%2==0: 109 | output = self.model(input)[0] 110 | output = output.permute(1, 2, 0).contiguous().view(-1, nChannel) 111 | _, target = torch.max(output, 1) 112 | img_target = target.data.cpu().numpy() 113 | img_target_rgb = np.array([label_colours[ c % label_colours.shape[0]] for c in img_target]) 114 | img_target_rgb = img_target_rgb.reshape(self.resize_height, self.resize_width, 3).astype( np.uint8 ) 115 | if self.plot_intermedium: 116 | cv2.imwrite(plot_dir+str(batch_idx)+"_nL"+str(nLabels)+"_mL"+str(mainLabels)+".png", img_target_rgb) 117 | if mainLabels <= minLabels: 118 | print ("mainLabels", mainLabels, "reached minLabels", minLabels, ".") 119 | break 120 | 121 | def predict(self, input): 122 | input = torch.from_numpy( np.array([input.transpose( (2, 0, 1) ).astype('float32')]) ) 123 | output = self.model( input )[ 0 ] 124 | output = output.permute( 1, 2, 0 ).contiguous().view( -1, self.nChannel ) 125 | prob=output.data.cpu().numpy() 126 | prob=np.divide(np.exp(prob), np.sum(np.exp(prob), 1).reshape(-1, 1)) 127 | _, pred = torch.max( output, 1 ) 128 | pred = pred.data.cpu().numpy() 129 | return prob, pred 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /TESLA_package/build/lib/TESLA/TLS_detection.py: -------------------------------------------------------------------------------- 1 | import os, sys, csv,re, time, random 2 | import cv2 3 | import numpy as np 4 | import pandas as pd 5 | import scanpy as sc 6 | from scipy.sparse import issparse 7 | from . util import * 8 | from . contour_util import * 9 | 10 | def TLS_detection( pred_refined_list, cluster_density_list, num_required, cnt_color, pooling="min"): 11 | pred_TLS=np.zeros([len(pred_refined_list[0]), len(pred_refined_list)]) 12 | for i in range(len(pred_refined_list)): 13 | tmp=np.zeros(pred_refined_list[i].shape) 14 | for k, v in cluster_density_list[i].items(): 15 | tmp[pred_refined_list[i]==k]=v/np.max(list(cluster_density_list[i].values())) 16 | pred_TLS[:,i]=tmp 17 | target = np.partition(pred_TLS, -num_required, axis=1)[:,-num_required:] #Select top num_required 18 | if pooling=="mean": 19 | target=np.mean(target, axis=1) 20 | elif pooling=="min": 21 | target=np.min(target, axis=1) 22 | else: 23 | print("Error! Pooling logic not understood.") 24 | target=(target-np.min(target))/(np.max(target)-np.min(target)) 25 | target[target<0.5]=0 26 | return target 27 | 28 | 29 | def plot_TLS_score(img, resize_factor, binary,target, cnt_color): 30 | resize_width=int(img.shape[1]*resize_factor) 31 | resize_height=int(img.shape[0]*resize_factor) 32 | binary_resized=cv2.resize(binary, (resize_width, resize_height)) 33 | img_resized =cv2.resize(img, (resize_width, resize_height)) 34 | target_img=target.reshape(resize_height, resize_width) 35 | target_img_rgb=(cnt_color((target*255).astype("int"))[:, 0:3]*255).reshape(resize_height, resize_width,3).astype( np.uint8 ) 36 | target_img_rgb=(cnt_color((target*255).astype("int"))[:, 0:3]*255).reshape(resize_height, resize_width,3).astype( np.uint8 ) 37 | target_img_rgb=cv2.cvtColor(target_img_rgb, cv2.COLOR_RGB2BGR) 38 | ret_img=img_resized.copy() 39 | #Whiten 40 | white_ratio=0.5 41 | ret_img[binary_resized!=0]=ret_img[binary_resized!=0]*(1-white_ratio)+np.array([255, 255, 255])*(white_ratio) 42 | ret_img[target_img!=0]=target_img_rgb[target_img!=0] 43 | ret_img[binary_resized==0]=255 44 | return ret_img 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /TESLA_package/build/lib/TESLA/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.2.2' 2 | from . TESLA import * 3 | from . util import * 4 | from . contour_util import * 5 | from . calculate_dis import * 6 | from . imputation import * 7 | from . annotation import * 8 | from . contour_util import * 9 | from . TLS_detection import * 10 | from . tumor_edge_core import * 11 | -------------------------------------------------------------------------------- /TESLA_package/build/lib/TESLA/annotation.py: -------------------------------------------------------------------------------- 1 | import os,csv,re, time, random 2 | import cv2 3 | import numpy as np 4 | import pandas as pd 5 | import torch 6 | import scanpy as sc 7 | from . util import * 8 | from . contour_util import * 9 | from . calculate_dis import * 10 | from . TESLA import TESLA 11 | 12 | 13 | def annotation(img, 14 | sudo_adata, 15 | genes, 16 | resize_factor, 17 | binary, 18 | res=50, 19 | num_required=1, 20 | pooling="min", 21 | rseed=100, 22 | tseed=100, 23 | nseed=100, 24 | nChannel=100, 25 | threshold=1000, 26 | radius=3, 27 | minLabels=30, 28 | train_refine=True, 29 | plot_intermedium=False, 30 | target_size="small", 31 | min_UMI=0): 32 | #-------------------------------Image band-------------------------------------------------# 33 | if target_size=="small": 34 | target_ratio=1/2 35 | elif target_size=="large": 36 | target_ratio=1/3 37 | else: 38 | print("target_size not valid, Please specify (small / large).Use default small") 39 | target_ratio=1/2 40 | print("Computing image band...") 41 | resize_width=int(img.shape[1]*resize_factor) 42 | resize_height=int(img.shape[0]*resize_factor) 43 | img_resized=(img * np.dstack([(binary!=0)]*3)).astype(np.uint8) 44 | img_resized = cv2.resize(img_resized, (resize_width, resize_height), interpolation = cv2.INTER_AREA) 45 | gray_resized = cv2.cvtColor(img_resized,cv2.COLOR_BGR2GRAY) 46 | gray_resized=gray_resized.reshape(list(gray_resized.shape)+[1]) 47 | binary_resized=cv2.resize(binary, (resize_width, resize_height), interpolation = cv2.INTER_AREA) 48 | img_band1=(gray_resized-np.min(gray_resized))/(np.max(gray_resized)-np.min(gray_resized)) 49 | #-------------------------------Gene band-------------------------------------------------# 50 | print("Computing gene band...") 51 | genes=list(set([i for i in genes if i in sudo_adata.var.index ])) 52 | assert num_required<=len(genes) 53 | tmp=sudo_adata.X[:,sudo_adata.var.index.isin(genes)] 54 | tmp=(tmp-np.min(tmp, 0))/(np.max(tmp, 0)-np.min(tmp, 0)) 55 | tmp = np.partition(tmp, -num_required, axis=1)[:,-num_required:] #Select top num_required 56 | if pooling=="mean": 57 | sudo_adata.obs["meta"]=np.mean(tmp, axis=1) 58 | elif pooling=="min": 59 | sudo_adata.obs["meta"]=np.min(tmp, axis=1) 60 | else: 61 | print("Error! Pooling logic not understood.") 62 | gene_img=np.zeros(list(img.shape[0:2])+[1]) 63 | for _, row in sudo_adata.obs.iterrows(): 64 | x=row["x"] 65 | y=row["y"] 66 | exp=row["meta"] 67 | gene_img[int(x-res/2):int(x+res/2), int(y-res/2):int(y+res/2),0]=exp 68 | gene_img[binary==0]=0 69 | gene_band1=gene_img[:,:,0] 70 | #Filter on min UMI 71 | gene_band1[gene_band1<=np.log(min_UMI+1)]=0 72 | gene_band1=cv2.resize(gene_band1, (resize_width, resize_height), interpolation = cv2.INTER_AREA) 73 | gene_band1=(gene_band1-np.min(gene_band1))/(np.max(gene_band1)-np.min(gene_band1)) 74 | gene_band1=gene_band1.reshape(list(gene_band1.shape)+[1]) 75 | #-------------------------------TESLA-------------------------------------------------# 76 | print("Running TESLA...") 77 | assert np.max(img_band1)==1 and np.min(img_band1)==0 and np.max(gene_band1)==1 and np.min(gene_band1)==0 78 | data=np.concatenate((img_band1, gene_band1), axis=2) 79 | random.seed(rseed) 80 | torch.manual_seed(tseed) 81 | np.random.seed(nseed) 82 | tesla=TESLA() 83 | tesla.train(input=data, use_cuda=False, train_refine=train_refine, radius=radius, nChannel=nChannel, lr=0.1, 84 | minLabels=minLabels, maxIter=30, stepsize_sim=1, stepsize_con=5, threshold=threshold, plot_intermedium=plot_intermedium, plot_dir=None) 85 | prob, pred = tesla.predict(data) 86 | pred_refined=refine_clusters(pred=pred, resize_height=resize_height, resize_width=resize_width, threshold=threshold, radius=radius) 87 | nLabels=len(np.unique(pred)) 88 | mainLabels=len(np.unique(pred_refined)) 89 | #-----------------------------------Find target cluster---------------------------# 90 | print("Finding target clusters...") 91 | marker=gene_band1.flatten() 92 | clusters=pred_refined.flatten() 93 | c_m={} #cluster_marker expression 94 | for i in np.unique(clusters): 95 | c_m[i]=np.mean(marker[clusters==i]) 96 | c_m = sorted(c_m.items(), key=lambda x: x[1], reverse=True) 97 | target_clusters=list(filter(lambda x: (x[1] > c_m[0][1]*target_ratio), c_m)) 98 | target_clusters=[x[0] for x in target_clusters] 99 | print("c_m:\n", c_m, "\n", "Target clusters:\n", target_clusters, sep = '') 100 | return pred_refined, target_clusters, c_m 101 | 102 | def visualize_annotation(pred_refined, 103 | target_clusters, 104 | c_m, 105 | img, 106 | binary, 107 | resize_factor, 108 | cnt_color=clr.LinearSegmentedColormap.from_list('red', ["#EAE7CC", '#BA0000'], N=256)): 109 | resize_width=int(img.shape[1]*resize_factor) 110 | resize_height=int(img.shape[0]*resize_factor) 111 | binary_resized=cv2.resize(binary, (resize_width, resize_height), interpolation = cv2.INTER_AREA) 112 | background = cv2.resize(img, (resize_width, resize_height), interpolation = cv2.INTER_AREA) 113 | ret_img=(background.copy()).astype(np.uint8) 114 | alpha=0.8 115 | #Whiten 116 | white_ratio=0.5 117 | ret_img=ret_img*(1-white_ratio)+np.array([255, 255, 255])*(white_ratio) 118 | for i in range(len(target_clusters)): 119 | color=((np.array(cnt_color(int(c_m[i][1]/c_m[0][1]*255)))[0:3])*255).astype(int)[::-1] 120 | target_img=(1*(pred_refined==target_clusters[i])).reshape(resize_height, resize_width) 121 | target_img[binary_resized==0]=0 122 | ret_img[target_img!=0]=ret_img[target_img!=0]*(1-alpha)+np.array(color)*(alpha) 123 | return ret_img 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /TESLA_package/build/lib/TESLA/calculate_dis.py: -------------------------------------------------------------------------------- 1 | import os,csv,re 2 | import pandas as pd 3 | import numpy as np 4 | import scanpy as sc 5 | import math 6 | import matplotlib.colors as clr 7 | import matplotlib.pyplot as plt 8 | 9 | def distance(t1,t2): 10 | sum=((t1-t2)**2).sum() 11 | return math.sqrt(sum) 12 | 13 | def calculate_dis_matrix(x, y, x_pixel=None, y_pixel=None, image=None, beta=49, alpha=1, histology=True): 14 | #x,y,x_pixel, y_pixel are lists 15 | dis=np.zeros((len(x),len(x))) 16 | if histology: 17 | assert (x_pixel is not None) & (x_pixel is not None) & (image is not None) 18 | assert (len(x)==len(x_pixel)) & (len(y)==len(y_pixel)) 19 | print("Calculateing distance matrix using histology image...") 20 | #beta to control the range of neighbourhood when calculate grey vale for one spot 21 | #alpha to control the color scale 22 | beta_half=round(beta/2) 23 | g=[] 24 | for i in range(len(x_pixel)): 25 | max_x=image.shape[0] 26 | max_y=image.shape[1] 27 | nbs=image[max(0,x_pixel[i]-beta_half):min(max_x,x_pixel[i]+beta_half+1),max(0,y_pixel[i]-beta_half):min(max_y,y_pixel[i]+beta_half+1)] 28 | g.append(np.mean(np.mean(nbs,axis=0),axis=0)) 29 | c0, c1, c2=[], [], [] 30 | for i in g: 31 | c0.append(i[0]) 32 | c1.append(i[1]) 33 | c2.append(i[2]) 34 | c0=np.array(c0) 35 | c1=np.array(c1) 36 | c2=np.array(c2) 37 | print("Var of c0,c1,c2 = ", np.var(c0),np.var(c1),np.var(c2)) 38 | c3=(c0*np.var(c0)+c1*np.var(c1)+c2*np.var(c2))/(np.var(c0)+np.var(c1)+np.var(c2)) 39 | c4=(c3-np.mean(c3))/np.std(c3) 40 | z_scale=np.max([np.std(x), np.std(y)])*alpha 41 | z=c4*z_scale 42 | z=z.tolist() 43 | print("Var of x,y,z = ", np.var(x),np.var(y),np.var(z)) 44 | for i in range(len(x)): 45 | if i%500==0: 46 | print("Calculating spot ", i, "/", len(x)) 47 | for j in range(i, len(x)): 48 | cord1=np.array([x[i],y[i],z[i]]) 49 | cord2=np.array([x[j],y[j],z[j]]) 50 | dis[i][j]=dis[j][i]=distance(cord1, cord2) 51 | return dis, z 52 | else: 53 | print("Calculateing distance matrix using xy only...") 54 | for i in range(len(x)): 55 | if i%50==0: 56 | print("Calculating spot ", i, "/", len(x)) 57 | for j in range(i, len(x)): 58 | cord1=np.array([x[i],y[i]]) 59 | cord2=np.array([x[j],y[j]]) 60 | dis[i][j]=dis[j][i]=distance(cord1, cord2) 61 | return dis 62 | 63 | def extract_color(x_pixel=None, y_pixel=None, image=None, beta=49, RGB=True): 64 | if RGB: 65 | #beta to control the range of neighbourhood when calculate grey vale for one spot 66 | beta_half=round(beta/2) 67 | g=[] 68 | for i in range(len(x_pixel)): 69 | max_x=image.shape[0] 70 | max_y=image.shape[1] 71 | nbs=image[max(0,x_pixel[i]-beta_half):min(max_x,x_pixel[i]+beta_half+1),max(0,y_pixel[i]-beta_half):min(max_y,y_pixel[i]+beta_half+1)] 72 | g.append(np.mean(np.mean(nbs,axis=0),axis=0)) 73 | c0, c1, c2=[], [], [] 74 | for i in g: 75 | c0.append(i[0]) 76 | c1.append(i[1]) 77 | c2.append(i[2]) 78 | c0=np.array(c0) 79 | c1=np.array(c1) 80 | c2=np.array(c2) 81 | c3=(c0*np.var(c0)+c1*np.var(c1)+c2*np.var(c2))/(np.var(c0)+np.var(c1)+np.var(c2)) 82 | else: 83 | beta_half=round(beta/2) 84 | g=[] 85 | for i in range(len(x_pixel)): 86 | max_x=image.shape[0] 87 | max_y=image.shape[1] 88 | nbs=image[max(0,x_pixel[i]-beta_half):min(max_x,x_pixel[i]+beta_half+1),max(0,y_pixel[i]-beta_half):min(max_y,y_pixel[i]+beta_half+1)] 89 | g.append(np.mean(nbs)) 90 | c3=np.array(g) 91 | return c3 92 | -------------------------------------------------------------------------------- /TESLA_package/build/lib/TESLA/contour_util.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import cv2 4 | 5 | def scan_contour(spots, scan_x=True, shape="hexagon"): 6 | #shape="hexagon" # For 10X Vsium, shape="square" for ST data 7 | if scan_x: 8 | array_a="array_x" 9 | array_b="array_y" 10 | pixel_a="pixel_x" 11 | pixel_b="pixel_y" 12 | else: 13 | array_a="array_y" 14 | array_b="array_x" 15 | pixel_a="pixel_y" 16 | pixel_b="pixel_x" 17 | upper, lower={}, {} 18 | uniq_array_a=sorted(set(spots[array_a])) 19 | if shape=="hexagon": 20 | for i in range(len(uniq_array_a)-1): 21 | a1=uniq_array_a[i] 22 | a2=uniq_array_a[i+1] 23 | group=spots.loc[spots[array_a].isin([a1, a2]),:] 24 | lower[np.min(group[pixel_a])]=np.min(group[pixel_b]) 25 | upper[np.min(group[pixel_a])]=np.max(group[pixel_b]) 26 | #print(a1, lower[np.min(group[pixel_a])], upper[np.min(group[pixel_a])]) 27 | a1=uniq_array_a[-1] 28 | a2=uniq_array_a[-2] 29 | group=spots.loc[spots[array_a].isin([a1, a2]),:] 30 | lower[np.min(group[pixel_a])]=np.min(group[pixel_b]) 31 | upper[np.min(group[pixel_a])]=np.max(group[pixel_b]) 32 | #print(a1, lower[np.min(group[pixel_a])], upper[np.min(group[pixel_a])]) 33 | elif shape=="square": 34 | for i in range(len(uniq_array_a)-1): 35 | a1=uniq_array_a[i] 36 | group=spots.loc[spots[array_a]==a1,:] 37 | lower[np.min(group[pixel_a])]=np.min(group[pixel_b]) 38 | upper[np.min(group[pixel_a])]=np.max(group[pixel_b]) 39 | a1=uniq_array_a[-1] 40 | group=spots.loc[spots[array_a]==a1,:] 41 | lower[np.min(group[pixel_a])]=np.min(group[pixel_b]) 42 | upper[np.min(group[pixel_a])]=np.max(group[pixel_b]) 43 | else: 44 | print("Error, unknown shape, pls specify 'square' or 'hexagon'.") 45 | lower=np.array(list(lower.items())[::-1]).astype("int32") 46 | upper=np.array(list(upper.items())).astype("int32") 47 | cnt=np.concatenate((upper, lower), axis=0) 48 | cnt=cnt.reshape(cnt.shape[0], 1, 2) 49 | if scan_x: 50 | cnt=cnt[:, : , [1, 0]] 51 | return cnt 52 | 53 | def cv2_detect_contour(img, 54 | CANNY_THRESH_1 = 100, 55 | CANNY_THRESH_2 = 200, 56 | apertureSize=5, 57 | L2gradient = True, 58 | all_cnt_info=False): 59 | if len(img.shape)==3: 60 | gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) 61 | elif len(img.shape)==2: 62 | gray=(img*((1, 255)[np.max(img)<=1])).astype(np.uint8) 63 | else: 64 | print("Image format error!") 65 | edges = cv2.Canny(gray, CANNY_THRESH_1, CANNY_THRESH_2,apertureSize = apertureSize, L2gradient = L2gradient) 66 | edges = cv2.dilate(edges, None) 67 | edges = cv2.erode(edges, None) 68 | cnt_info = [] 69 | cnts, _ = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE) 70 | for c in cnts: 71 | cnt_info.append((c,cv2.isContourConvex(c),cv2.contourArea(c),)) 72 | cnt_info = sorted(cnt_info, key=lambda c: c[2], reverse=True) 73 | cnt=cnt_info[0][0] 74 | if all_cnt_info: 75 | return cnt_info 76 | else: 77 | return cnt 78 | 79 | 80 | def cut_contour_boundary(cnt, x_min, x_max, y_min, y_max, enlarge): 81 | ret=cnt.copy() 82 | ret[:, : , 0][ret[:, : , 0]>y_max]=y_max 83 | ret[:, : , 0][ret[:, : , 0]x_max]=x_max 85 | ret[:, : , 1][ret[:, : , 1] 2000), cnt_info)) 21 | for i in range(len(cnt_info)): 22 | cv2.drawContours(img_new_resized, [cnt_info[i][0]], -1, ([0, 0, 0]), thickness=2) #BGR 23 | return img_new_resized 24 | 25 | 26 | def tumor_edge_core_separation(img, binary, resize_factor, pred_refined, target_clusters, sudo_adata, res, shrink_rate=0.8): 27 | resize_width=int(img.shape[1]*resize_factor) 28 | resize_height=int(img.shape[0]*resize_factor) 29 | #Extract target pixels 30 | target_img=(1*(np.isin(pred_refined, target_clusters))).reshape(resize_height, resize_width) 31 | #Filter using binary filter 32 | binary_resized=cv2.resize(binary, (resize_width, resize_height), interpolation = cv2.INTER_AREA) 33 | target_img=target_img*(binary_resized!=0) 34 | tumor_binary=cv2.resize(target_img.astype(np.uint8), ((img.shape[1], img.shape[0]))) 35 | tumor_index=[] 36 | for index, row in sudo_adata.obs.iterrows(): 37 | x=int(row["x"]) 38 | y=int(row["y"]) 39 | if tumor_binary[x, y]!=0: tumor_index.append(index) 40 | sudo_tumor=sudo_adata[sudo_adata.obs.index.isin(tumor_index)] 41 | print("Running Connected Components ...") 42 | adj=pd.DataFrame(np.zeros([sudo_tumor.shape[0], sudo_tumor.shape[0]]), columns=sudo_tumor.obs.index.tolist(), index=sudo_tumor.obs.index.tolist()) 43 | for index, row in sudo_tumor.obs.iterrows(): 44 | x, y=row["x"], row["y"] 45 | nbr=sudo_tumor.obs[((sudo_tumor.obs["x"]==x) & (np.abs(sudo_tumor.obs["y"]-y)<=res))| ((sudo_tumor.obs["y"]==y) & (np.abs(sudo_tumor.obs["x"]-x)<=res))] 46 | adj.loc[index, nbr.index.tolist()]=1 47 | sys.setrecursionlimit(adj.shape[0]) 48 | g = Graph(V=adj.index.tolist(), adj=adj) 49 | c_c = g.ConnectedComponents() 50 | cc_info=[] 51 | for c in c_c: 52 | cc_info.append((c,len(c))) 53 | cc_info = sorted(cc_info, key=lambda c: c[1], reverse=True) 54 | print("Running Select biggest Tumor region ...") 55 | #cc_info[0][0] contains the index of all superpiexls in the biggest tumor region 56 | sudo_tumor_sub=sudo_tumor[sudo_tumor.obs.index.isin(cc_info[0][0])] 57 | adj_sub=adj.loc[cc_info[0][0], cc_info[0][0]] 58 | adj_sub=adj_sub[np.sum(adj_sub, 1)>2] 59 | sudo_tumor_sub=sudo_tumor_sub[sudo_tumor_sub.obs.index.isin(adj_sub.index.tolist())] 60 | binary_tumor = np.zeros(img.shape[0:2]).astype(np.uint8) 61 | for index, row in sudo_tumor_sub.obs.iterrows(): 62 | x=int(row["x"]) 63 | y=int(row["y"]) 64 | binary_tumor[int(x-res/2):int(x+res/2), int(y-res/2):int(y+res/2)]=1 65 | cnt_tumor=cv2_detect_contour((binary_tumor==0).astype(np.uint8), apertureSize=5,L2gradient = True) 66 | print("Running Core and edge separation ...") 67 | cnt_tumor_reduced=scale_contour(cnt_tumor, shrink_rate) 68 | binary_core = np.zeros(img.shape[0:2]).astype(np.uint8) 69 | cv2.drawContours(binary_core, [cnt_tumor_reduced], -1, (1), thickness=-1) 70 | binary_core=binary_core*binary_tumor 71 | cnt_core=cv2_detect_contour((binary_core==0).astype(np.uint8), apertureSize=5,L2gradient = True) 72 | print("Running Create Gene Expression adata for tumor edge vs. core ...") 73 | region=[] 74 | for index, row in sudo_tumor_sub.obs.iterrows(): 75 | x=int(row["x"]) 76 | y=int(row["y"]) 77 | region.append(("edge","core",)[binary_core[x, y]]) 78 | sudo_tumor_sub.obs["region"]=region 79 | return binary_tumor, binary_core, sudo_tumor_sub 80 | 81 | 82 | def plot_tumor_edge_core(img, resize_factor, binary, binary_tumor, binary_core, color_edge=[66, 50, 225], color_core=[62, 25, 53]): 83 | resize_width=int(img.shape[1]*resize_factor) 84 | resize_height=int(img.shape[0]*resize_factor) 85 | white_ratio=0.5 86 | alpha=0.8 87 | ret_img=img.copy() 88 | cnt_tumor=cv2_detect_contour((binary_tumor==0).astype(np.uint8), apertureSize=5,L2gradient = True) 89 | cnt_core=cv2_detect_contour((binary_core==0).astype(np.uint8), apertureSize=5,L2gradient = True) 90 | ret_img[binary!=0]=ret_img[binary!=0]*(1-white_ratio)+np.array([255, 255, 255])*(white_ratio) 91 | #Core and edge region 92 | target_img = np.zeros(img.shape, dtype=np.uint8) 93 | cv2.drawContours(target_img, [cnt_tumor], -1, (color_edge), thickness=-1) #BGR 94 | cv2.drawContours(target_img, [cnt_core], -1, (color_core), thickness=-1) #BGR 95 | #Overlay 96 | ret_img[binary_tumor!=0]=ret_img[binary_tumor!=0]*(1-alpha)+target_img[binary_tumor!=0]*alpha 97 | ret_img=cv2.resize(ret_img, (resize_width, resize_height), interpolation = cv2.INTER_AREA) 98 | return ret_img 99 | 100 | 101 | def tumor_edge_core_DE(sudo_core_edge): 102 | sc.tl.rank_genes_groups(sudo_core_edge, groupby="region",reference="rest",n_genes=sudo_core_edge.shape[1], method='wilcoxon') 103 | pvals_adj1=[i[1] for i in sudo_core_edge.uns['rank_genes_groups']["pvals_adj"]] 104 | pvals_adj0=[i[0] for i in sudo_core_edge.uns['rank_genes_groups']["pvals_adj"]] 105 | genes1=[i[1] for i in sudo_core_edge.uns['rank_genes_groups']["names"]] 106 | genes0=[i[0] for i in sudo_core_edge.uns['rank_genes_groups']["names"]] 107 | if issparse(sudo_core_edge.X): 108 | obs_tidy=pd.DataFrame(sudo_core_edge.X.A) 109 | else: 110 | obs_tidy=pd.DataFrame(sudo_core_edge.X) 111 | obs_tidy.index=sudo_core_edge.obs["region"].tolist() 112 | obs_tidy.columns=sudo_core_edge.var.index.tolist() 113 | #--------Edge enriched genes 114 | obs_tidy1=obs_tidy.loc[:,genes1] 115 | # 1. compute mean value 116 | mean_obs1 = obs_tidy1.groupby(level=0).mean() 117 | mean_all1=obs_tidy1.mean() 118 | # 2. compute fraction of cells having value >0 119 | obs_bool1 = obs_tidy1.astype(bool) 120 | fraction_obs1 = obs_bool1.groupby(level=0).sum() / obs_bool1.groupby(level=0).count() 121 | # compute fold change. 122 | fold_change1 = (mean_obs1.loc["edge"] / (mean_obs1.loc["core"]+ 1e-9)).values 123 | df1 = {'edge_genes': genes1, 124 | 'in_group_fraction': fraction_obs1.loc["edge"].tolist(), 125 | "out_group_fraction":fraction_obs1.loc["core"].tolist(), 126 | "in_out_group_ratio":(fraction_obs1.loc["edge"]/fraction_obs1.loc["core"]).tolist(), 127 | "in_group_mean_exp": mean_obs1.loc["edge"].tolist(), 128 | "out_group_mean_exp": mean_obs1.loc["core"].tolist(), 129 | "fold_change":fold_change1.tolist(), 130 | "pvals_adj":pvals_adj1, 131 | "all_mean_exp": mean_all1} 132 | df1 = pd.DataFrame(data=df1) 133 | df1=df1[(df1["pvals_adj"]<0.05)] 134 | #--------Core enriched genes 135 | obs_tidy0=obs_tidy.loc[:,genes0] 136 | # 1. compute mean value 137 | mean_obs0 = obs_tidy0.groupby(level=0).mean() 138 | mean_all0=obs_tidy0.mean() 139 | # 2. compute fraction of cells having value >0 140 | obs_bool0 = obs_tidy0.astype(bool) 141 | fraction_obs0 = obs_bool0.groupby(level=0).sum() / obs_bool0.groupby(level=0).count() 142 | # compute fold change. 143 | fold_change0 = (mean_obs0.loc["core"] / (mean_obs0.loc["edge"]+ 1e-9)).values 144 | df0 = {'edge_genes': genes0, 145 | 'in_group_fraction': fraction_obs0.loc["core"].tolist(), 146 | "out_group_fraction":fraction_obs0.loc["edge"].tolist(), 147 | "in_out_group_ratio":(fraction_obs0.loc["core"]/fraction_obs0.loc["edge"]).tolist(), 148 | "in_group_mean_exp": mean_obs0.loc["core"].tolist(), 149 | "out_group_mean_exp": mean_obs0.loc["edge"].tolist(), 150 | "fold_change":fold_change0.tolist(), 151 | "pvals_adj":pvals_adj0, 152 | "all_mean_exp": mean_all0} 153 | df0 = pd.DataFrame(data=df0) 154 | df0=df0[(df0["pvals_adj"]<0.05)] 155 | return df0, df1 156 | 157 | def filter_DE_genes(df, min_all_mean_exp=0.1, min_in_out_group_ratio=1, min_in_group_fraction=0.5, min_fold_change=1.2): 158 | df_filtered=df.copy() 159 | df_filtered=df_filtered[(df_filtered["all_mean_exp"]>min_all_mean_exp) & 160 | (df_filtered["in_out_group_ratio"]>=min_in_out_group_ratio) & 161 | (df_filtered["in_group_fraction"]>min_in_group_fraction) & 162 | (df_filtered["fold_change"]>min_fold_change)] 163 | return df_filtered 164 | 165 | def plot_edge_core_enrichd_genes(img, resize_factor, binary, binary_tumor, sudo_core_edge, genes, cnt_color, plot_dir, res): 166 | resize_width=int(img.shape[1]*resize_factor) 167 | resize_height=int(img.shape[0]*resize_factor) 168 | if issparse(sudo_core_edge.X):sudo_core_edge.X=sudo_core_edge.X.A 169 | res=res*1.5 170 | white_ratio=0.5 171 | for g in genes: 172 | sudo_core_edge.obs["exp"]=sudo_core_edge.X[:,sudo_core_edge.var.index==g] 173 | sudo_core_edge.obs["relexp"]=(sudo_core_edge.obs["exp"]-np.min(sudo_core_edge.obs["exp"]))/(np.max(sudo_core_edge.obs["exp"])-np.min(sudo_core_edge.obs["exp"])) 174 | img_new=np.zeros(img.shape, dtype=np.uint8) 175 | for _, row in sudo_core_edge.obs.iterrows(): 176 | x=row["x"] 177 | y=row["y"] 178 | c=row["relexp"] 179 | img_new[int(x-res/2):int(x+res/2), int(y-res/2):int(y+res/2),:]=((np.array(cnt_color(int(c*255)))[0:3])*255).astype(int) 180 | img_new=cv2.cvtColor(img_new, cv2.COLOR_RGB2BGR) 181 | #Background 182 | ret_img=img.copy() 183 | ret_img[binary!=0]=ret_img[binary!=0]*(1-white_ratio)+np.array([255, 255, 255])*(white_ratio) 184 | #Overlay 185 | ret_img[binary_tumor!=0]=img_new[binary_tumor!=0] 186 | ret_img=cv2.resize(ret_img, (resize_width, resize_height), interpolation = cv2.INTER_AREA) 187 | cv2.imwrite(plot_dir+g+'.jpg', ret_img) 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /TESLA_package/build/lib/TESLA/util.py: -------------------------------------------------------------------------------- 1 | import scanpy as sc 2 | import pandas as pd 3 | import numpy as np 4 | import scipy 5 | import os 6 | from anndata import AnnData,read_csv,read_text,read_mtx 7 | from scipy.sparse import issparse 8 | 9 | def prefilter_cells(adata,min_counts=None,max_counts=None,min_genes=200,max_genes=None): 10 | if min_genes is None and min_counts is None and max_genes is None and max_counts is None: 11 | raise ValueError('Provide one of min_counts, min_genes, max_counts or max_genes.') 12 | id_tmp=np.asarray([True]*adata.shape[0],dtype=bool) 13 | id_tmp=np.logical_and(id_tmp,sc.pp.filter_cells(adata.X,min_genes=min_genes)[0]) if min_genes is not None else id_tmp 14 | id_tmp=np.logical_and(id_tmp,sc.pp.filter_cells(adata.X,max_genes=max_genes)[0]) if max_genes is not None else id_tmp 15 | id_tmp=np.logical_and(id_tmp,sc.pp.filter_cells(adata.X,min_counts=min_counts)[0]) if min_counts is not None else id_tmp 16 | id_tmp=np.logical_and(id_tmp,sc.pp.filter_cells(adata.X,max_counts=max_counts)[0]) if max_counts is not None else id_tmp 17 | adata._inplace_subset_obs(id_tmp) 18 | adata.raw=sc.pp.log1p(adata,copy=True) #check the rowname 19 | print("the var_names of adata.raw: adata.raw.var_names.is_unique=:",adata.raw.var_names.is_unique) 20 | 21 | 22 | def prefilter_genes(adata,min_counts=None,max_counts=None,min_cells=10,max_cells=None): 23 | if min_cells is None and min_counts is None and max_cells is None and max_counts is None: 24 | raise ValueError('Provide one of min_counts, min_genes, max_counts or max_genes.') 25 | id_tmp=np.asarray([True]*adata.shape[1],dtype=bool) 26 | id_tmp=np.logical_and(id_tmp,sc.pp.filter_genes(adata.X,min_cells=min_cells)[0]) if min_cells is not None else id_tmp 27 | id_tmp=np.logical_and(id_tmp,sc.pp.filter_genes(adata.X,max_cells=max_cells)[0]) if max_cells is not None else id_tmp 28 | id_tmp=np.logical_and(id_tmp,sc.pp.filter_genes(adata.X,min_counts=min_counts)[0]) if min_counts is not None else id_tmp 29 | id_tmp=np.logical_and(id_tmp,sc.pp.filter_genes(adata.X,max_counts=max_counts)[0]) if max_counts is not None else id_tmp 30 | adata._inplace_subset_var(id_tmp) 31 | 32 | 33 | def prefilter_specialgenes(adata,Gene1Pattern="ERCC",Gene2Pattern="MT-"): 34 | id_tmp1=np.asarray([not str(name).startswith(Gene1Pattern) for name in adata.var_names],dtype=bool) 35 | id_tmp2=np.asarray([not str(name).startswith(Gene2Pattern) for name in adata.var_names],dtype=bool) 36 | id_tmp=np.logical_and(id_tmp1,id_tmp2) 37 | adata._inplace_subset_var(id_tmp) 38 | 39 | def relative_func(expres): 40 | #expres: an array counts expression for a gene 41 | maxd = np.max(expres) - np.min(expres) 42 | min_exp=np.min(expres) 43 | rexpr = (expres - min_exp)/maxd 44 | return rexpr 45 | 46 | def plot_relative_exp(input_adata, gene, x_name, y_name,color,use_raw=False, spot_size=200000): 47 | adata=input_adata.copy() 48 | if use_raw: 49 | X=adata.raw.X 50 | else: 51 | X=adata.X 52 | if issparse(X): 53 | X=pd.DataFrame(X.A) 54 | else: 55 | X=pd.DataFrame(X) 56 | X.index=adata.obs.index 57 | X.columns=adata.var.index 58 | rexpr=relative_func(X.loc[:,gene]) 59 | adata.obs["rexpr"]=rexpr 60 | fig=sc.pl.scatter(adata,x=x_name,y=y_name,color="rexpr",title=gene+"_rexpr",color_map=color,show=False,size=spot_size/adata.shape[0]) 61 | return fig 62 | 63 | def plot_log_exp(input_adata, gene, x_name, y_name,color,use_raw=False): 64 | adata=input_adata.copy() 65 | if use_raw: 66 | X=adata.X 67 | else: 68 | X=adata.raw.X 69 | if issparse(X): 70 | X=pd.DataFrame(X.A) 71 | else: 72 | X=pd.DataFrame(X) 73 | X.index=adata.obs.index 74 | X.columns=adata.var.index 75 | adata.obs["log"]=np.log((X.loc[:,gene]+1).tolist()) 76 | fig=sc.pl.scatter(adata,x=x_name,y=y_name,color="log",title=gene+"_log",color_map=color,show=False,size=200000/adata.shape[0]) 77 | return fig 78 | 79 | def refine_clusters(pred, resize_height, resize_width, threshold, radius): 80 | pixel_num=pd.Series(pred).value_counts() 81 | clusters=pixel_num.index.tolist() 82 | reorder_map={} 83 | for i in range(pixel_num.shape[0]): 84 | reorder_map[clusters[i]]=i 85 | pred_reordered=pd.Series(pred).replace(reorder_map).to_numpy() 86 | pixel_num=pd.Series(pred_reordered).value_counts() 87 | # Number of clusters 88 | nLabels = len(np.unique(pred_reordered)) 89 | # Number of main clusters 90 | mainLabels=(pd.Series(pred_reordered).value_counts()>=threshold).sum() 91 | #------------- Refine clusters --------------------- 92 | main_clusters=pixel_num.index[pixel_num>=threshold].tolist() 93 | minor_clusters=pixel_num.index[pixel_num0: 105 | replace_map[i]=nbs_num.index[ nbs_num.index.isin(main_clusters) ][ 0 ] 106 | pred_refined=pd.Series(pred_reordered).replace(replace_map).to_numpy() 107 | return pred_refined 108 | -------------------------------------------------------------------------------- /TESLA_package/dist/TESLA-1.0.0-py3.7.egg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianhuupenn/TESLA/8c4dcf896497dd4a34a6e720f03ec68c1502d571/TESLA_package/dist/TESLA-1.0.0-py3.7.egg -------------------------------------------------------------------------------- /TESLA_package/dist/TESLAforST-1.2.0-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianhuupenn/TESLA/8c4dcf896497dd4a34a6e720f03ec68c1502d571/TESLA_package/dist/TESLAforST-1.2.0-py3-none-any.whl -------------------------------------------------------------------------------- /TESLA_package/dist/TESLAforST-1.2.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianhuupenn/TESLA/8c4dcf896497dd4a34a6e720f03ec68c1502d571/TESLA_package/dist/TESLAforST-1.2.0.tar.gz -------------------------------------------------------------------------------- /TESLA_package/dist/TESLAforST-1.2.1-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianhuupenn/TESLA/8c4dcf896497dd4a34a6e720f03ec68c1502d571/TESLA_package/dist/TESLAforST-1.2.1-py3-none-any.whl -------------------------------------------------------------------------------- /TESLA_package/dist/TESLAforST-1.2.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianhuupenn/TESLA/8c4dcf896497dd4a34a6e720f03ec68c1502d571/TESLA_package/dist/TESLAforST-1.2.1.tar.gz -------------------------------------------------------------------------------- /TESLA_package/dist/TESLAforST-1.2.2-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianhuupenn/TESLA/8c4dcf896497dd4a34a6e720f03ec68c1502d571/TESLA_package/dist/TESLAforST-1.2.2-py3-none-any.whl -------------------------------------------------------------------------------- /TESLA_package/dist/TESLAforST-1.2.2.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianhuupenn/TESLA/8c4dcf896497dd4a34a6e720f03ec68c1502d571/TESLA_package/dist/TESLAforST-1.2.2.tar.gz -------------------------------------------------------------------------------- /TESLA_package/setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | setuptools.setup( 7 | name="TESLAforST", 8 | version="1.2.4", 9 | author="Jian Hu", 10 | author_email="jianhu@pennmedicine.upenn.edu", 11 | description="TESLA: Deciphering tumor ecosystems at super-resolution from spatial transcriptomics", 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | url="https://github.com/jianhuupenn/TESLA", 15 | packages=setuptools.find_packages(), 16 | install_requires=["torch","pandas","numpy","scipy","scanpy","anndata","scikit-learn", "numba"], 17 | #install_requires=[], 18 | classifiers=[ 19 | "Programming Language :: Python :: 3", 20 | "License :: OSI Approved :: MIT License", 21 | "Operating System :: OS Independent", 22 | ], 23 | python_requires='>=3.6', 24 | ) 25 | -------------------------------------------------------------------------------- /docs/asserts/images/applications.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianhuupenn/TESLA/8c4dcf896497dd4a34a6e720f03ec68c1502d571/docs/asserts/images/applications.jpg -------------------------------------------------------------------------------- /docs/asserts/images/applications_new.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianhuupenn/TESLA/8c4dcf896497dd4a34a6e720f03ec68c1502d571/docs/asserts/images/applications_new.jpg -------------------------------------------------------------------------------- /docs/asserts/images/workflow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianhuupenn/TESLA/8c4dcf896497dd4a34a6e720f03ec68c1502d571/docs/asserts/images/workflow.jpg -------------------------------------------------------------------------------- /tutorial/output_19_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianhuupenn/TESLA/8c4dcf896497dd4a34a6e720f03ec68c1502d571/tutorial/output_19_0.jpg -------------------------------------------------------------------------------- /tutorial/output_23_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianhuupenn/TESLA/8c4dcf896497dd4a34a6e720f03ec68c1502d571/tutorial/output_23_0.png -------------------------------------------------------------------------------- /tutorial/output_28_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianhuupenn/TESLA/8c4dcf896497dd4a34a6e720f03ec68c1502d571/tutorial/output_28_1.jpg -------------------------------------------------------------------------------- /tutorial/output_35_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianhuupenn/TESLA/8c4dcf896497dd4a34a6e720f03ec68c1502d571/tutorial/output_35_0.jpg -------------------------------------------------------------------------------- /tutorial/output_37_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianhuupenn/TESLA/8c4dcf896497dd4a34a6e720f03ec68c1502d571/tutorial/output_37_2.jpg -------------------------------------------------------------------------------- /tutorial/output_43_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianhuupenn/TESLA/8c4dcf896497dd4a34a6e720f03ec68c1502d571/tutorial/output_43_0.jpg -------------------------------------------------------------------------------- /tutorial/output_44_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianhuupenn/TESLA/8c4dcf896497dd4a34a6e720f03ec68c1502d571/tutorial/output_44_0.jpg -------------------------------------------------------------------------------- /tutorial/output_51_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianhuupenn/TESLA/8c4dcf896497dd4a34a6e720f03ec68c1502d571/tutorial/output_51_0.jpg -------------------------------------------------------------------------------- /tutorial/tutorial.md: -------------------------------------------------------------------------------- 1 |

TESLA Tutorial

2 | 3 | 4 |
Author: Jian Hu*, Kyle Coleman, Edward B. Lee, Humam Kadara, Linghua Wang*, Mingyao Li* 5 | 6 | ## Outline 7 | #### 1. [Installation](https://github.com/jianhuupenn/TESLA/blob/main/tutorial/tutorial.md#1-installation-1) 8 | #### 2. [Import modules](https://github.com/jianhuupenn/TESLA/blob/main/tutorial/tutorial.md#2-import-python-modules) 9 | #### 3. [Read in data](https://github.com/jianhuupenn/TESLA/blob/main/tutorial/tutorial.md#3-read-in-data-1) 10 | #### 4. [Gene expression enhancement](https://github.com/jianhuupenn/TESLA/blob/main/tutorial/tutorial.md#4-gene-expression-enhancement-1) 11 | #### 5. [Region annotation](https://github.com/jianhuupenn/TESLA/blob/main/tutorial/tutorial.md#5-region-annotation-1) 12 | #### 6. [Characterize the intra-tumor heterogeneity](https://github.com/jianhuupenn/TESLA/blob/main/tutorial/tutorial.md#6-characterize-the-intra-tumor-heterogeneity-1) 13 | #### 7. [TLS detection](https://github.com/jianhuupenn/TESLA/blob/main/tutorial/tutorial.md#7-tls-detection-1) 14 | 15 | ### 1. Installation 16 | To install TESLA package you must make sure that your python version is over 3.5.=. If you don’t know the version of python you can check it by: 17 | 18 | 19 | ```python 20 | import platform 21 | platform.python_version() 22 | ``` 23 | 24 | 25 | 26 | 27 | '3.8.8' 28 | 29 | 30 | 31 | Note: Because TESLA pends on pytorch, you should make sure torch is correctly installed. 32 |
33 | Now you can install the current release of TESLA by the following three ways: 34 | #### 1.1 PyPI: Directly install the package from PyPI. 35 | 36 | 37 | ```python 38 | pip3 install TESLAforST 39 | #If you do not have permission (when you get a permission denied error), you should install TESLA by 40 | pip3 install --user TESLAforST 41 | ``` 42 | 43 | #### 1.2 Github 44 | Download the package from Github and install it locally: 45 | 46 | 47 | ```python 48 | git clone https://github.com/jianhuupenn/TESLA 49 | cd ./TESLA/TESLA_package/ 50 | python3 setup.py install --user 51 | ``` 52 | 53 | #### 1.3 Anaconda () 54 | If you do not have Python3.5 or Python3.6 installed, consider installing Anaconda (see Installing Anaconda). After installing Anaconda, you can create a new environment, for example, TESLA (you can change to any name you like). 55 | 56 | 57 | ```python 58 | #create an environment called TESLA 59 | conda create -n TESLA python=3.7.9 60 | #activate your environment 61 | conda activate TESLA 62 | git clone https://github.com/jianhuupenn/TESLA 63 | cd TESLA/TESLA_package/ 64 | python3 setup.py build 65 | python3 setup.py install 66 | conda deactivate 67 | ``` 68 | 69 | ### 2. Import python modules 70 | 71 | 72 | ```python 73 | import os,csv,re, time 74 | import pickle 75 | import random 76 | import warnings 77 | warnings.filterwarnings('ignore') 78 | import pandas as pd 79 | import numpy as np 80 | from scipy import stats 81 | from scipy.sparse import issparse 82 | import scanpy as sc 83 | import matplotlib.colors as clr 84 | import matplotlib.pyplot as plt 85 | import cv2 86 | import TESLA as tesla 87 | from IPython.display import Image 88 | ``` 89 | 90 | 91 | ```python 92 | tesla.__version__ 93 | ``` 94 | 95 | 96 | 97 | 98 | '1.2.2' 99 | 100 | 101 | 102 | ### 3. Read in data 103 | The current version of TESLA requres three input data: 104 | 105 |
106 | 1. The gene expression matrix(n by k): expression_matrix.h5; 107 |
108 | 2. Spatial coordinateds of samplespositions.txt; 109 |
110 | 3. Histology image(optional): histology.tif, can be tif or png or jepg. 111 |
112 | The gene expreesion data can be stored as an AnnData object. AnnData stores a data matrix .X together with annotations of observations .obs, variables .var and unstructured annotations .uns. 113 | 114 | 115 | ```python 116 | """ 117 | #Read original 10x_h5 data and save it to h5ad 118 | from scanpy import read_10x_h5 119 | adata = read_10x_h5("../tutorial/data/151673/expression_matrix.h5") 120 | spatial=pd.read_csv("../tutorial/data/151673/positions.txt",sep=",",header=None,na_filter=False,index_col=0) 121 | adata.obs["x1"]=spatial[1] 122 | adata.obs["x2"]=spatial[2] 123 | adata.obs["x3"]=spatial[3] 124 | adata.obs["x4"]=spatial[4] 125 | adata.obs["x5"]=spatial[5] 126 | #Select captured samples 127 | adata=adata[adata.obs["x1"]==1] 128 | adata.var_names=[i.upper() for i in list(adata.var_names)] 129 | adata.var["genename"]=adata.var.index.astype("str") 130 | adata.write_h5ad("../tutorial/data/151673/sample_data.h5ad") 131 | """ 132 | #Read in gene expression and spatial location 133 | counts=sc.read("./data/sample_data.h5ad") 134 | #Read in hitology image 135 | img=cv2.imread("./data/sample_H&E.jpg") 136 | ``` 137 | 138 | ### 4. Gene expression enhancement 139 | 140 | #### 4.1 Preprocessing 141 | 142 | 143 | ```python 144 | resize_factor=1000/np.min(img.shape[0:2]) 145 | resize_width=int(img.shape[1]*resize_factor) 146 | resize_height=int(img.shape[0]*resize_factor) 147 | counts.var.index=[i.upper() for i in counts.var.index] 148 | counts.var_names_make_unique() 149 | counts.raw=counts 150 | sc.pp.log1p(counts) # impute on log scale 151 | if issparse(counts.X):counts.X=counts.X.A.copy() 152 | ``` 153 | 154 | #### 4.2 Contour detection 155 | 156 | 157 | ```python 158 | #Three different algorithms to detect contour, select the best one.Here we use cv2. 159 | #Important note: If you get incorrect contour for all of the 3 three methods, please double check your array_x, array_y, pixel_x, pixel_y are matched correctly. 160 | 161 | #-----------------1. Detect contour using cv2----------------- 162 | cnt=tesla.cv2_detect_contour(img, apertureSize=5,L2gradient = True) 163 | 164 | #-----------------2. Scan contour by x----------------- 165 | spots=counts.obs.loc[:, ['pixel_x', 'pixel_y', "array_x", "array_y"]] 166 | #shape="hexagon" for 10X Vsium, shape="square" for ST 167 | cnt=tesla.scan_contour(spots, scan_x=True, shape="hexagon") 168 | 169 | #-----------------3. Scan contour by y----------------- 170 | spots=counts.obs.loc[:, ['pixel_x', 'pixel_y', "array_x", "array_y"]] 171 | #shape="hexagon" for 10X Vsium, shape="square" for ST 172 | cnt=tesla.scan_contour(spots, scan_x=False, shape="hexagon") 173 | 174 | binary=np.zeros((img.shape[0:2]), dtype=np.uint8) 175 | cv2.drawContours(binary, [cnt], -1, (1), thickness=-1) 176 | #Enlarged filter 177 | cnt_enlarged = tesla.scale_contour(cnt, 1.05) 178 | binary_enlarged = np.zeros(img.shape[0:2]) 179 | cv2.drawContours(binary_enlarged, [cnt_enlarged], -1, (1), thickness=-1) 180 | img_new = img.copy() 181 | cv2.drawContours(img_new, [cnt], -1, (255), thickness=50) 182 | img_new=cv2.resize(img_new, ((resize_width, resize_height))) 183 | cv2.imwrite('./results/cnt.jpg', img_new) 184 | Image(filename='./results/cnt.jpg') 185 | ``` 186 | 187 | 188 | 189 | 190 | 191 | ![jpeg](output_19_0.jpg) 192 | 193 | 194 | 195 | 196 | #### 4.3 Gene expression enhancement 197 | 198 | 199 | ```python 200 | #Set size of superpixel 201 | res=50 202 | # Note, if the numer of superpixels is too large and take too long, you can increase the res to 100 203 | enhanced_exp_adata=tesla.imputation(img=img, raw=counts, cnt=cnt, genes=counts.var.index.tolist(), shape="None", res=res, s=1, k=2, num_nbs=10) 204 | ``` 205 | 206 | Trying to set attribute `.obs` of view, copying. 207 | 208 | 209 | Total number of sudo points: 19988 210 | Calculating spot 0 211 | Calculating spot 1000 212 | Calculating spot 2000 213 | Calculating spot 3000 214 | Calculating spot 4000 215 | Calculating spot 5000 216 | Calculating spot 6000 217 | Calculating spot 7000 218 | Calculating spot 8000 219 | Calculating spot 9000 220 | Calculating spot 10000 221 | Calculating spot 11000 222 | Calculating spot 12000 223 | Calculating spot 13000 224 | Calculating spot 14000 225 | Calculating spot 15000 226 | Calculating spot 16000 227 | Calculating spot 17000 228 | Calculating spot 18000 229 | Calculating spot 19000 230 | --- 76.97813606262207 seconds --- 231 | Imputing spot 0 232 | Imputing spot 1000 233 | Imputing spot 2000 234 | Imputing spot 3000 235 | Imputing spot 4000 236 | Imputing spot 5000 237 | Imputing spot 6000 238 | Imputing spot 7000 239 | Imputing spot 8000 240 | Imputing spot 9000 241 | Imputing spot 10000 242 | Imputing spot 11000 243 | Imputing spot 12000 244 | Imputing spot 13000 245 | Imputing spot 14000 246 | Imputing spot 15000 247 | Imputing spot 16000 248 | Imputing spot 17000 249 | Imputing spot 18000 250 | Imputing spot 19000 251 | 252 | 253 | #### 4.4 Plot gene expression image 254 | 255 | 256 | ```python 257 | cnt_color = clr.LinearSegmentedColormap.from_list('magma', ["#000003", "#3b0f6f", "#8c2980", "#f66e5b", "#fd9f6c", "#fbfcbf"], N=256) 258 | g="CD151" 259 | enhanced_exp_adata.obs[g]=enhanced_exp_adata.X[:,enhanced_exp_adata.var.index==g] 260 | fig=sc.pl.scatter(enhanced_exp_adata,alpha=1,x="y",y="x",color=g,color_map=cnt_color,show=False,size=10) 261 | fig.set_aspect('equal', 'box') 262 | fig.invert_yaxis() 263 | plt.gcf().set_dpi(600) 264 | fig.figure.show() 265 | ``` 266 | 267 | 268 | 269 | ![png](output_23_0.png) 270 | 271 | 272 | 273 | #### 4.5 Save results 274 | 275 | 276 | ```python 277 | enhanced_exp_adata.write_h5ad("./results/enhanced_exp.h5ad") 278 | ``` 279 | 280 | ### 5. Region annotation 281 | 282 | #### 5.1 Target region annotation 283 | Prefer choosing 5 to 10 marker genes. 284 |
285 | The default num_required=1. If you include one gene that is not that specific (also highly expressed in other cell types), num_required+=1. For example, if you include 2 non-specific markers, please set num_required=3. 286 | Please drop genes with all 0 expression from the list since they are not infomrative. 287 | 288 | ```python 289 | #Select your gene list 290 | #For example, if we want to annotate tumor region, use tumor markers 291 | genes=['BUB1B', 'KIF1C','TOP2A', 'CD151', 'MMP10', 'PTHLH','FEZ1','IL24','KCNMA','INHBA','MAGEA4','NT5E','LAMC2','SLITRK6'] 292 | genes=list(set([i for i in genes if i in enhanced_exp_adata.var.index ])) 293 | #target_size can be set to "small" or "large". 294 | pred_refined, target_clusters, c_m=tesla.annotation(img=img, 295 | binary=binary, 296 | sudo_adata=enhanced_exp_adata, 297 | genes=genes, 298 | resize_factor=resize_factor, 299 | num_required=1, 300 | target_size="small") 301 | #Plot 302 | ret_img=tesla.visualize_annotation(img=img, 303 | binary=binary, 304 | resize_factor=resize_factor, 305 | pred_refined=pred_refined, 306 | target_clusters=target_clusters, 307 | c_m=c_m) 308 | 309 | cv2.imwrite('./results/tumor.jpg', ret_img) 310 | Image(filename='./results/tumor.jpg') 311 | ``` 312 | 313 | Computing image band... 314 | Computing gene band... 315 | Running TESLA... 316 | 0 / 30 | label num : 100 | main clusters : 65 | feature loss : 6.286277770996094 | spatial loss : 2.477134943008423 317 | --- 79.8510057926178 seconds --- 318 | 1 / 30 | label num : 75 | main clusters : 30 | feature loss : 4.300119400024414 | spatial loss : 0.44662031531333923 319 | --- 136.36640787124634 seconds --- 320 | mainLabels 30 reached minLabels 30 . 321 | Finding target clusters... 322 | c_m: 323 | [(6, 0.660496668592895), (2, 0.5849543163588522), (15, 0.5158497030203698), (19, 0.48606369815923994), (12, 0.4802130752317957), (10, 0.4580134991936973), (4, 0.45732190334046835), (14, 0.43235966925018005), (17, 0.3388222879213612), (13, 0.3331709370762233), (16, 0.3160079580858375), (9, 0.2478758789018975), (8, 0.21917223597411475), (7, 0.20651287130232146), (3, 0.19796864337328288), (11, 0.19742332922793465), (20, 0.15788060926103378), (1, 0.14535831267938887), (5, 0.1265518524797657), (18, 0.024549401709053432), (0, 0.0)] 324 | Target clusters: 325 | [6, 2, 15, 19, 12, 10, 4, 14, 17, 13] 326 | 327 | 328 | 329 | 330 | 331 | 332 | ![jpeg](output_28_1.jpg) 333 | 334 | 335 | 336 | 337 | #### 5.2 Save results 338 | 339 | 340 | ```python 341 | #Save 342 | np.save("./results/tumor_annotation.npy", pred_refined) 343 | print("Target_clusters: ", target_clusters, "\n") 344 | #Save the cluster density information 345 | c_d={i[0]:i[1] for i in c_m[0:len(target_clusters)]} 346 | print("Cluster_density : ", c_d) 347 | with open('./results/tumor_annotation_c_d.pkl', 'wb') as f: pickle.dump(c_d, f) 348 | 349 | ``` 350 | 351 | Target_clusters: [6, 2, 15, 19, 12, 10, 4, 14, 17, 13] 352 | 353 | Cluster_density : {6: 0.660496668592895, 2: 0.5849543163588522, 15: 0.5158497030203698, 19: 0.48606369815923994, 12: 0.4802130752317957, 10: 0.4580134991936973, 4: 0.45732190334046835, 14: 0.43235966925018005, 17: 0.3388222879213612, 13: 0.3331709370762233} 354 | 355 | 356 | ### 6. Characterize the intra-tumor heterogeneity 357 | 358 | #### 6.1 Read in saved results 359 | 360 | 361 | ```python 362 | enhanced_exp_adata=sc.read("./results/enhanced_exp.h5ad") 363 | pred_refined=np.load("./results/tumor_annotation.npy") 364 | target_clusters=[6, 2, 15, 19, 12, 10, 4, 14, 17, 13] 365 | c_m= [(6, 0.660496668592895), (2, 0.5849543163588522), (15, 0.5158497030203698), (19, 0.48606369815923994), (12, 0.4802130752317957), (10, 0.4580134991936973), (4, 0.45732190334046835), (14, 0.43235966925018005), (17, 0.3388222879213612), (13, 0.3331709370762233), (16, 0.3160079580858375), (9, 0.2478758789018975), (8, 0.21917223597411475), (7, 0.20651287130232146), (3, 0.19796864337328288), (11, 0.19742332922793465), (20, 0.15788060926103378), (1, 0.14535831267938887), (5, 0.1265518524797657), (18, 0.024549401709053432), (0, 0.0)] 366 | ``` 367 | 368 | #### 6.2 Leading edge detection 369 | 370 | 371 | ```python 372 | ret_img=tesla.leading_edge_detection(img=img, 373 | pred_refined=pred_refined, 374 | resize_factor=resize_factor, 375 | target_clusters=target_clusters, 376 | binary=binary) 377 | 378 | cv2.imwrite('./results/leading_edge.jpg', ret_img) 379 | Image(filename='./results/leading_edge.jpg') 380 | ``` 381 | 382 | 383 | 384 | 385 | 386 | ![jpeg](output_35_0.jpg) 387 | 388 | 389 | 390 | 391 | #### 6.3 Tumor edge and core separation 392 | 393 | 394 | ```python 395 | shrink_rate=0.8 396 | res=50 397 | enhanced_exp_adata.obs.index = enhanced_exp_adata.obs.index.astype(str) 398 | binary_tumor, binary_core, core_edge_exp=tesla.tumor_edge_core_separation(img=img, 399 | binary=binary, 400 | resize_factor=resize_factor, 401 | pred_refined=pred_refined, 402 | target_clusters=target_clusters, 403 | sudo_adata=enhanced_exp_adata, 404 | res=res, 405 | shrink_rate=shrink_rate) 406 | 407 | ret_img=tesla.plot_tumor_edge_core(img=img, 408 | resize_factor=resize_factor, 409 | binary=binary, 410 | binary_tumor=binary_tumor, 411 | binary_core=binary_core, 412 | color_edge=[66, 50, 225], 413 | color_core=[62, 25, 53]) 414 | 415 | cv2.imwrite('./results/core_edge.jpg', ret_img) 416 | Image(filename='./results/core_edge.jpg') 417 | ``` 418 | 419 | Running Connected Components ... 420 | Running Select biggest Tumor region ... 421 | Running Core and edge separation ... 422 | Running Create Gene Expression adata for tumor edge vs. core ... 423 | 424 | 425 | Trying to set attribute `.obs` of view, copying. 426 | 427 | 428 | 429 | 430 | 431 | 432 | ![jpeg](output_37_2.jpg) 433 | 434 | 435 | 436 | 437 | #### 6.4. Core vs core DE analysis 438 | 439 | 440 | ```python 441 | df_core, df_edge=tesla.tumor_edge_core_DE(core_edge_exp) 442 | df_edge_filtered=tesla.filter_DE_genes(df=df_edge, min_all_mean_exp=0.1, min_in_out_group_ratio=1, min_in_group_fraction=0.5, min_fold_change=1.2) 443 | df_core_filtered=tesla.filter_DE_genes(df=df_core, min_all_mean_exp=0.1, min_in_out_group_ratio=1, min_in_group_fraction=0.5, min_fold_change=1.2) 444 | df_edge_filtered 445 | ``` 446 | 447 | 448 | 449 | 450 |
451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 |
edge_genesin_group_fractionout_group_fractionin_out_group_ratioin_group_mean_expout_group_mean_expfold_changepvals_adjall_mean_exp
B3GALT4B3GALT40.8878260.7302571.2157720.1596300.0991141.6105701.289481e-900.121456
RHOFRHOF0.8254700.6377681.2943110.1682030.1129651.4889779.696451e-660.133358
DMXL2DMXL20.9247770.7928171.1664450.1874020.1278811.4654402.114505e-650.149855
PTGDSPTGDS0.7749920.6211621.2476480.1943280.1101191.7647012.475420e-640.141208
FYBFYB0.9333550.8582741.0874800.3256640.2290771.4216334.381763e-640.264736
..............................
ZBTB18ZBTB180.8370170.8343311.0032200.1360650.1087721.2509201.998338e-070.118848
TMEM176ATMEM176A0.7980860.7957131.0029820.1758900.1399691.2566301.528395e-060.153230
ARMC7ARMC70.8634110.8399301.0279560.1445600.1196691.2079973.360836e-060.128859
PTAFRPTAFR0.7802710.7708051.0122800.2031420.1685371.2053244.606770e-040.181313
CXCL13CXCL130.6064010.5794551.0465010.1548830.1171371.3222466.435324e-040.131072
601 |

104 rows × 9 columns

602 |
603 | 604 | 605 | 606 | #### 6.5. Plot core/edge enriched genes 607 | 608 | 609 | ```python 610 | cnt_color = clr.LinearSegmentedColormap.from_list('magma', ["#000003", "#3b0f6f", "#8c2980", "#f66e5b", "#fd9f6c", "#fbfcbf"], N=256) 611 | genes=["IGFBP2", "CXCL12"] 612 | plot_dir="./results/" 613 | tesla.plot_edge_core_enrichd_genes(img=img, 614 | resize_factor=resize_factor, 615 | binary=binary, 616 | binary_tumor=binary_tumor, 617 | sudo_core_edge=core_edge_exp, 618 | genes=genes, 619 | cnt_color=cnt_color, 620 | plot_dir=plot_dir, 621 | res=res) 622 | 623 | ``` 624 | 625 | ### 6.6 Plot ome examples 626 | 627 | 628 | ```python 629 | # Core enriched gene 630 | Image(filename='./results/IGFBP2.jpg') 631 | ``` 632 | 633 | 634 | 635 | 636 | 637 | ![jpeg](output_43_0.jpg) 638 | 639 | 640 | 641 | 642 | 643 | ```python 644 | # Edge enriched gene 645 | Image(filename='./results/CXCL12.jpg') 646 | ``` 647 | 648 | 649 | 650 | 651 | 652 | ![jpeg](output_44_0.jpg) 653 | 654 | 655 | 656 | 657 | ### 7. TLS detection 658 | 659 | #### 7.1 Read in cell type annotations 660 | ##### Before runing TLS detection, please detect B, CD4+T, DC and CXCL13. 661 | 662 | 663 | ```python 664 | cnt_color = clr.LinearSegmentedColormap.from_list('red', ["#EAE7CC", '#BA0000'], N=256) 665 | pred_refined1=np.load("./results/B_annotation.npy") 666 | pred_refined2=np.load("./results/CD4+T_annotation.npy") 667 | pred_refined3=np.load("./results/DC_annotation.npy") 668 | pred_refined4=np.load("./results/CXCL13_annotation.npy") 669 | pred_refined_list=[pred_refined1, pred_refined2, pred_refined3, pred_refined4] 670 | #Read in cluster_density information 671 | with open('./results/B_annotation_c_d.pkl', 'rb') as f: c_d1 = pickle.load(f) 672 | 673 | with open('./results/CD4+T_annotation_c_d.pkl', 'rb') as f: c_d2 = pickle.load(f) 674 | 675 | with open('./results/DC_annotation_c_d.pkl', 'rb') as f: c_d3 = pickle.load(f) 676 | 677 | with open('./results/CXCL13_annotation_c_d.pkl', 'rb') as f: c_d4 = pickle.load(f) 678 | 679 | cluster_density_list=[c_d1, c_d2, c_d3, c_d4] 680 | ``` 681 | 682 | #### 7.2 Calculate TLS score 683 | 684 | 685 | ```python 686 | cnt_color = clr.LinearSegmentedColormap.from_list('red', ["#EAE7CC", '#BA0000'], N=256) 687 | num_required=3 688 | tls_score=tesla.TLS_detection(pred_refined_list, cluster_density_list, num_required, cnt_color) 689 | img_tls=tesla.plot_TLS_score(img, resize_factor, binary,tls_score, cnt_color) 690 | ``` 691 | 692 | #### 7.3 Plot TLS score 693 | 694 | 695 | ```python 696 | cv2.imwrite('./results/TLS_score.jpg', img_tls) 697 | Image(filename='./results/TLS_score.jpg') 698 | ``` 699 | 700 | 701 | 702 | 703 | 704 | ![jpeg](output_51_0.jpg) 705 | 706 | 707 | 708 | 709 | 710 | ```python 711 | 712 | ``` 713 | --------------------------------------------------------------------------------