├── LICENSE ├── METI_packages ├── METI.egg-info │ ├── PKG-INFO │ ├── SOURCES.txt │ ├── dependency_links.txt │ ├── requires.txt │ └── top_level.txt ├── METI │ ├── METI.py │ └── __init__.py ├── README.md ├── dist │ └── METI-0.1-py3.9.egg └── setup.py ├── README.md ├── doc └── P1-Fig1.png └── tutorial ├── METI_tutorial.ipynb ├── data └── seg_results │ └── mask │ ├── Goblet_spot_GE.png │ ├── Goblet_spot_combined.png │ ├── Goblet_spot_seg.png │ ├── mask0.png │ ├── mask1.png │ ├── mask2.png │ ├── mask3.png │ ├── mask4.png │ └── mask5.png ├── sample_results ├── 2D_density_plot.jpg ├── 3D_density_plot.jpg ├── ABCG2.png ├── CAF.jpg ├── CAF_subtypes.jpg ├── CD3DE_CD4.jpg ├── CD3DE_CD4_Treg.jpg ├── CD3D_CD3E.jpg ├── CD4.jpg ├── Goblet.jpg ├── Goblet_meta.png ├── Goblet_seg.png ├── T_reg.jpg ├── UMI_count_Scanpy_54078.png ├── goblet.png ├── nuclei_filtered1.png ├── nuclei_filtered2.png └── nuclei_filtered_white.png ├── scalefactors_json ├── BLCA_B1_scalefactors_json.json ├── BLCA_B2_scalefactors_json.json ├── STAD_G1_scalefactors_json.json ├── STAD_G2_scalefactors_json.json ├── STAD_G3_scalefactors_json.json └── STAD_G4_scalefactors_json.json └── tutorial.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Jiahui Jiang 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 | -------------------------------------------------------------------------------- /METI_packages/METI.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: METI 3 | Version: 0.1 4 | -------------------------------------------------------------------------------- /METI_packages/METI.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | README.md 2 | setup.py 3 | METI.egg-info/PKG-INFO 4 | METI.egg-info/SOURCES.txt 5 | METI.egg-info/dependency_links.txt 6 | METI.egg-info/requires.txt 7 | METI.egg-info/top_level.txt -------------------------------------------------------------------------------- /METI_packages/METI.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /METI_packages/METI.egg-info/requires.txt: -------------------------------------------------------------------------------- 1 | TESLAforST>=1.0.0 2 | -------------------------------------------------------------------------------- /METI_packages/METI.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /METI_packages/METI/METI.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import csv,re, time 3 | import pickle 4 | import random 5 | import warnings 6 | warnings.filterwarnings('ignore') 7 | import pandas as pd 8 | import numpy as np 9 | from scipy import stats 10 | from scipy.sparse import issparse 11 | import scanpy as sc 12 | import matplotlib.colors as clr 13 | import matplotlib.pyplot as plt 14 | import cv2 15 | import TESLA as tesla 16 | from IPython.display import Image 17 | import scipy.sparse 18 | import scanpy as sc 19 | import pandas as pd 20 | import matplotlib.pyplot as plt 21 | import seaborn as sns 22 | from scanpy import read_10x_h5 23 | import PIL 24 | from PIL import Image as IMAGE 25 | import os 26 | os.environ['KMP_DUPLICATE_LIB_OK']='True' 27 | 28 | def meta_gene_plot(img, 29 | sudo_adata, 30 | genes, 31 | resize_factor, 32 | binary, 33 | res=50, 34 | num_required=1, 35 | pooling="min", 36 | rseed=100, 37 | tseed=100, 38 | nseed=100, 39 | nChannel=100, 40 | threshold=1000, 41 | radius=3, 42 | minLabels=30, 43 | train_refine=True, 44 | plot_intermedium=False, 45 | target_size="small", 46 | min_UMI=5): 47 | #-------------------------------Image band-------------------------------------------------# 48 | if target_size=="small": 49 | target_ratio=2/3 50 | elif target_size=="large": 51 | target_ratio=1/3 52 | else: 53 | print("target_size not valid, Please specify (small / large).Use default small") 54 | target_ratio=1/2 55 | print("Computing image band...") 56 | resize_width=int(img.shape[1]*resize_factor) 57 | resize_height=int(img.shape[0]*resize_factor) 58 | img_resized=(img * np.dstack([(binary!=0)]*3)).astype(np.uint8) 59 | img_resized = cv2.resize(img_resized, (resize_width, resize_height), interpolation = cv2.INTER_AREA) 60 | gray_resized = cv2.cvtColor(img_resized,cv2.COLOR_BGR2GRAY) 61 | gray_resized=gray_resized.reshape(list(gray_resized.shape)+[1]) 62 | binary_resized=cv2.resize(binary, (resize_width, resize_height), interpolation = cv2.INTER_AREA) 63 | img_band1=(gray_resized-np.min(gray_resized))/(np.max(gray_resized)-np.min(gray_resized)) 64 | #-------------------------------Gene band-------------------------------------------------# 65 | print("Computing gene band...") 66 | genes=list(set([i for i in genes if i in sudo_adata.var.index ])) 67 | assert num_required<=len(genes) 68 | tmp=sudo_adata.X[:,sudo_adata.var.index.isin(genes)] 69 | tmp=(tmp-np.min(tmp, 0))/(np.max(tmp, 0)-np.min(tmp, 0)) 70 | tmp = np.partition(tmp, -num_required, axis=1)[:,-num_required:] #Select top num_required 71 | if pooling=="mean": 72 | sudo_adata.obs["meta"]=np.mean(tmp, axis=1) 73 | elif pooling=="min": 74 | sudo_adata.obs["meta"]=np.min(tmp, axis=1) 75 | else: 76 | print("Error! Pooling logic not understood.") 77 | gene_img=np.zeros(list(img.shape[0:2])+[1]) 78 | for _, row in sudo_adata.obs.iterrows(): 79 | x=row["x"] 80 | y=row["y"] 81 | exp=row["meta"] 82 | gene_img[int(x-res/2):int(x+res/2), int(y-res/2):int(y+res/2),0]=exp 83 | gene_img[binary==0]=0 84 | return sudo_adata 85 | 86 | 87 | def annotation(img, 88 | sudo_adata, 89 | genes, 90 | resize_factor, 91 | binary, 92 | res=50, 93 | num_required=1, 94 | pooling="min", 95 | rseed=100, 96 | tseed=100, 97 | nseed=100, 98 | nChannel=100, 99 | threshold=1000, 100 | radius=3, 101 | minLabels=30, 102 | train_refine=True, 103 | plot_intermedium=False, 104 | target_size="small", 105 | min_UMI=1): 106 | #-------------------------------Image band-------------------------------------------------# 107 | if target_size=="small": 108 | target_ratio=0.7 109 | elif target_size=="large": 110 | target_ratio=1/3 111 | else: 112 | print("target_size not valid, Please specify (small / large).Use default small") 113 | target_ratio=1/2 114 | print("Computing image band...") 115 | resize_width=int(img.shape[1]*resize_factor) 116 | resize_height=int(img.shape[0]*resize_factor) 117 | img_resized=(img * np.dstack([(binary!=0)]*3)).astype(np.uint8) 118 | img_resized = cv2.resize(img_resized, (resize_width, resize_height), interpolation = cv2.INTER_AREA) 119 | gray_resized = cv2.cvtColor(img_resized,cv2.COLOR_BGR2GRAY) 120 | gray_resized=gray_resized.reshape(list(gray_resized.shape)+[1]) 121 | binary_resized=cv2.resize(binary, (resize_width, resize_height), interpolation = cv2.INTER_AREA) 122 | img_band1=(gray_resized-np.min(gray_resized))/(np.max(gray_resized)-np.min(gray_resized)) 123 | #-------------------------------Gene band-------------------------------------------------# 124 | print("Computing gene band...") 125 | genes=list(set([i for i in genes if i in sudo_adata.var.index ])) 126 | assert num_required<=len(genes) 127 | tmp=sudo_adata.X[:,sudo_adata.var.index.isin(genes)] 128 | tmp=(tmp-np.min(tmp, 0))/(np.max(tmp, 0)-np.min(tmp, 0)) 129 | tmp = np.partition(tmp, -num_required, axis=1)[:,-num_required:] #Select top num_required 130 | if pooling=="mean": 131 | sudo_adata.obs["meta"]=np.mean(tmp, axis=1) 132 | elif pooling=="min": 133 | sudo_adata.obs["meta"]=np.min(tmp, axis=1) 134 | else: 135 | print("Error! Pooling logic not understood.") 136 | gene_img=np.zeros(list(img.shape[0:2])+[1]) 137 | for _, row in sudo_adata.obs.iterrows(): 138 | x=row["x"] 139 | y=row["y"] 140 | exp=row["meta"] 141 | gene_img[int(x-res/2):int(x+res/2), int(y-res/2):int(y+res/2),0]=exp 142 | gene_img[binary==0]=0 143 | gene_img_scaled = (gene_img * 255).astype(np.uint8) 144 | gene_band1 = gene_img_scaled[:,:,0] 145 | gene_band1 = gene_band1/255 146 | #Filter on min UMI 147 | # gene_band1[gene_band1<=np.log(min_UMI+1)]=0 148 | gene_band1=cv2.resize(gene_band1, (resize_width, resize_height), interpolation = cv2.INTER_AREA) 149 | gene_band1=(gene_band1-np.min(gene_band1))/(np.max(gene_band1)-np.min(gene_band1)) 150 | gene_band1=gene_band1.reshape(list(gene_band1.shape)+[1]) 151 | #-------------------------------TESLA-------------------------------------------------# 152 | print("Running TESLA...") 153 | assert np.max(img_band1)==1 and np.min(img_band1)==0 and np.max(gene_band1)==1 and np.min(gene_band1)==0 154 | data=np.concatenate((img_band1, gene_band1), axis=2) 155 | random.seed(rseed) 156 | torch.manual_seed(tseed) 157 | np.random.seed(nseed) 158 | from TESLA import TESLA 159 | from TESLA import refine_clusters 160 | tesla=TESLA() 161 | tesla.train(input=data, use_cuda=False, train_refine=train_refine, radius=radius, nChannel=nChannel, lr=0.1, 162 | minLabels=minLabels, maxIter=30, stepsize_sim=1, stepsize_con=5, threshold=threshold, plot_intermedium=plot_intermedium, plot_dir=None) 163 | prob, pred = tesla.predict(data) 164 | pred_refined=refine_clusters(pred=pred, resize_height=resize_height, resize_width=resize_width, threshold=threshold, radius=radius) 165 | nLabels=len(np.unique(pred)) 166 | mainLabels=len(np.unique(pred_refined)) 167 | #-----------------------------------Find target cluster---------------------------# 168 | print("Finding target clusters...") 169 | marker=gene_band1.flatten() 170 | clusters=pred_refined.flatten() 171 | c_m={} #cluster_marker expression 172 | for i in np.unique(clusters): 173 | c_m[i]=np.mean(marker[clusters==i]) 174 | c_m = sorted(c_m.items(), key=lambda x: x[1], reverse=True) 175 | target_clusters=list(filter(lambda x: (x[1] > c_m[0][1]*target_ratio), c_m)) 176 | target_clusters=[x[0] for x in target_clusters] 177 | print("c_m:\n", c_m, "\n", "Target clusters:\n", target_clusters, sep = '') 178 | return pred_refined, target_clusters, c_m 179 | 180 | def patch_split_for_ST(img, patch_size, spot_info, x_name="pixel_x", y_name="pixel_y"): 181 | assert patch_size%2==0 182 | patches=np.zeros((spot_info.shape[0], patch_size, patch_size, 3), dtype=np.uint8) 183 | counter=0 184 | for _, row in spot_info.iterrows(): 185 | x_tmp=int(row[x_name]) 186 | y_tmp=int(row[y_name]) 187 | patches[counter, :, :, :]=img[int(x_tmp-patch_size/2):int(x_tmp+patch_size/2),int(y_tmp-patch_size/2):int(y_tmp+patch_size/2), :] 188 | filename = os.path.join('/rsrch4/home/genomic_med/jjiang6/Project1/S1_54078/Segmentation/NC_review_Goblet_seg/patches/', f"patch_{counter}.png") 189 | cv2.imwrite(filename, cv2.cvtColor(patches[counter, :, :, :], cv2.COLOR_RGB2BGR)) 190 | counter+=1 191 | return patches 192 | 193 | def Segment_Patches(patches, save_dir="./seg_results",n_clusters=10): 194 | patch_size=patches.shape[1] 195 | for i in range(patches.shape[0]): 196 | print("Doing: ", i, "/", patches.shape[0]) 197 | patch=patches[i] 198 | pixel_values = patch.reshape((-1, 3)) 199 | #convert to float 200 | pixel_values = np.float32(pixel_values) 201 | seed=100 202 | random.seed(seed) 203 | np.random.seed(seed) 204 | kmeans = KMeans(n_clusters=n_clusters, random_state=0).fit(pixel_values) 205 | pred=kmeans.labels_ 206 | #centroids=kmeans.cluster_centers_ 207 | np.save(save_dir+"/patch"+str(i)+"_pred.npy", pred) 208 | 209 | def get_color_dic(patches, seg_dir, refine=True): 210 | patch_index=list(range(patches.shape[0])) 211 | patch_size=patches.shape[1] 212 | dic_list=[] 213 | for i in patch_index: 214 | pred_refined=np.load(seg_dir+"/patch"+str(i)+"_pred.npy") 215 | patch=patches[i].copy() 216 | c_m={} #cluster_marker expression 217 | c_a=[(j, np.sum(pred_refined==j)) for j in np.unique(pred_refined)] #cluster_area 218 | c_a=sorted(c_a, key=lambda x: x[1], reverse=True) 219 | c_a=dict((x, y) for x, y in c_a) 220 | clusters=pred_refined.reshape(patch_size, patch_size) 221 | for j, area_ratio in c_a.items(): 222 | c_m[j]=np.median(patch[clusters==j], 0).tolist() 223 | dic_list.append(c_m) 224 | return dic_list 225 | 226 | def Match_Masks(dic_list, num_mask_each=10, mapping_threshold1=30, mapping_threshold2=60, min_no_mach_rate=0.2, min_unused_channel_rate=0.8): 227 | masks_index=[] #list of list of list, [mask[patch[cluster]]] 228 | used_channel=[] 229 | for i in range(len(dic_list)): 230 | print("Doing ", i) 231 | c_m0=dic_list[i] 232 | for c0 in list(c_m0.keys())[0:np.min([num_mask_each, len(c_m0)])]: 233 | if str(i)+"_"+str(c0) not in used_channel: #Start matching 234 | no_match=0 235 | masks_index_tmp=[] 236 | used_channel_tmp=[str(i)+"_"+str(c0)] 237 | for j in range(len(dic_list)): 238 | c_m1=dic_list[j] 239 | tmp1=[(k, np.max(np.abs(np.array(v)-np.array(c_m0[c0])))) for k, v in c_m1.items()] 240 | tmp2=list(filter(lambda x: x[1]0: 242 | masks_index_tmp.append([x[0] for x in tmp2]) 243 | used_channel_tmp+=[str(j)+"_"+str(x[0])for x in tmp2] 244 | #print("Mapping: patch "+str(i)+" mask "+str(c0)+" to patch "+str(j)+" mask "+str(tmp[0][0])+"diff = "+str(np.round(tmp[0][1], 3))) 245 | else: 246 | tmp2=list(filter(lambda x: x[1]0: 248 | tmp2.sort(key=lambda x: x[1]) 249 | masks_index_tmp.append([tmp2[0][0]]) 250 | used_channel_tmp.append(str(j)+"_"+str(tmp2[0][0])) 251 | else: 252 | masks_index_tmp.append([]) 253 | no_match+=1 254 | new_rate=1-len(set(used_channel_tmp)&set(used_channel))/len(used_channel_tmp) 255 | if no_match<(len(dic_list)*min_no_mach_rate) and (new_rate >= min_unused_channel_rate): 256 | print("Adding, ", "no match counts:",no_match, "new rate:",new_rate) 257 | masks_index.append(masks_index_tmp) 258 | used_channel+=used_channel_tmp 259 | else: 260 | print("Not adding, ", "no match counts:",no_match, "new rate:",new_rate) 261 | return masks_index 262 | 263 | def Extract_Masks(masks_index, pred_file_locs, patch_size): 264 | num_masks=len(masks_index) 265 | masks=np.zeros([num_masks, len(masks_index[0]), patch_size, patch_size]) 266 | for i in range(num_masks): 267 | print("Extracting mask ", i) 268 | mask_index=masks_index[i] 269 | for j in range(len(mask_index)): 270 | pred=np.load(pred_file_locs[j]) 271 | mask = pred.reshape(patch_size, patch_size).astype(np.uint8) 272 | mask=1*np.isin(mask, mask_index[j]) 273 | masks[i, j, :, :]=mask 274 | return masks 275 | 276 | def Combine_Masks(masks, patch_info, img_size0, img_size1): 277 | #Combine masks to WSI 278 | patch_size=masks.shape[2] 279 | d0=int(np.ceil(img_size0/patch_size)*patch_size) 280 | d1=int(np.ceil(img_size1/patch_size)*patch_size) 281 | combined_masks=np.zeros([masks.shape[0], d0, d1]) 282 | for i in range(masks.shape[0]): #Each mask 283 | print("Combining mask ", i) 284 | for j in range(masks.shape[1]): #Each patch 285 | info=patch_info.iloc[j] 286 | x, y=int(info["pixel_x"]), int(info["pixel_y"]) 287 | combined_masks[i, int(x-patch_size/2):int(x+patch_size/2), int(y-patch_size/2):int(y+patch_size/2)]=masks[i, j, :, :] 288 | combined_masks=combined_masks[:, 0:img_size0, 0:img_size1] 289 | return combined_masks 290 | 291 | def Extract_CC_Features_each_CC(labels): 292 | #Check all attr 293 | #[attr for attr in dir(region_props[0]) if not attr.startswith('__')] 294 | #labels size1 x size2 295 | pnames=["label","area", "major_axis_length", "minor_axis_length", "solidity"] 296 | region_props = regionprops(labels.astype(int)) 297 | label_area=[(prop.label, prop.area) for prop in region_props] 298 | ret={} 299 | for name in pnames: 300 | ret[name]=[] 301 | for i in range(len(region_props)): 302 | ret[name].append(getattr(region_props[i],name)) 303 | ret=pd.DataFrame(ret) 304 | return ret 305 | 306 | 307 | # Convert to spot level¶ 308 | def extract_color(x_pixel=None, y_pixel=None, image=None, beta=49): 309 | beta_half=round(beta/2) 310 | g=[] 311 | for i in range(len(x_pixel)): 312 | max_x=image.shape[0] 313 | max_y=image.shape[1] 314 | 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)] 315 | g.append(np.mean(nbs)) 316 | c3=np.array(g) 317 | return c3 318 | 319 | -------------------------------------------------------------------------------- /METI_packages/METI/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.1' 2 | from . METI import * 3 | # from . metagene_annotation import * 4 | # from . calculate_moran_I import * 5 | # from . calculate_adj import * 6 | # from . ez_mode import * -------------------------------------------------------------------------------- /METI_packages/README.md: -------------------------------------------------------------------------------- 1 | # METI 2 | Morphology-Enhanced Spatial Transcriptome Analysis Integrator 3 | -------------------------------------------------------------------------------- /METI_packages/dist/METI-0.1-py3.9.egg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/METI_packages/dist/METI-0.1-py3.9.egg -------------------------------------------------------------------------------- /METI_packages/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | # In[2]: 5 | 6 | 7 | import setuptools 8 | 9 | with open("README.md", "r") as fh: 10 | long_description = fh.read() 11 | 12 | setuptools.setup( 13 | name='METI', 14 | version='0.1', 15 | author="Jiahui Jiang", 16 | author_email="jjiang6@mdanderson.org", 17 | description="METI: Deep profiling of tumor ecosystems by integrating cell morphology and spatial transcriptomics", 18 | long_description=long_description, 19 | long_description_content_type="text/markdown", 20 | url="https://github.com/Flashiness/METI", 21 | packages=setuptools.find_packages(), 22 | install_requires=["python-igraph","torch","pandas","numpy","scipy","scanpy","anndata","louvain","scikit-learn", "numba", "TESLAforST"], 23 | ) 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # METI 2 | 3 | ## METI: Deep profiling of tumor ecosystems by integrating cell morphology and spatial transcriptomics 4 | 5 | METI (Morphology-Enhanced Spatial Transcriptome Analysis Integrator) is an novel analytic framework that systematically analyzes cancer cells and cells of the TME by incorporating spatial gene expression, tissue histology, and prior knowledge of cancer and TME cells. METI starts with the identification of key cellular components and their states within the TME, including various immune cells and their transcriptional states, tumor stromal components such as cancer-associated fibroblasts (CAFs), and the epithelial compartment. Meanwhile, METI offers complementary information on cell morphology for various cell type from the H&E images. The combined results from gene expression and histology features provide a comprehensive understanding of the spatial cellular composition and organization within the tissue. 6 |
7 | 8 | ![METI workflow](doc/P1-Fig1.png) 9 | 10 |
11 | 12 | ## Usage 13 | 14 | With [**METI**](https://github.com/Flashiness/METI) package, you can: 15 | - Map normal and premalignant cells; 16 | - Identify cancer cell domain and heterogeneity; 17 | - Map and phenotype T cell; 18 | - Analyze other immune cells including B cells, plasma cells, neutrophils; 19 | - Analyze stromal cells. 20 |
21 | For tutorial, please refer to: https://github.com/Flashiness/METI/blob/main/tutorial/tutorial.md 22 |
23 | 24 | ## System Requirements 25 | Python support packages: torch, pandas, numpy, scipy, scanpy > 1.5, anndata, sklearn, cv2, TESLAforST. 26 | 27 | ## Versions the software has been tested on 28 | - System: Anaconda 29 | - Python: 3.7.9 30 | - 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, TESLAforST=1.2.4. 31 |
32 | 33 | ## Contributing 34 | 35 | Source code: [Github](https://github.com/Flashiness/METI) 36 | 37 | We are continuing adding new features. Bug reports or feature requests are welcome. 38 | 39 | ## References 40 | 41 | Please consider citing the following reference: 42 | 43 | - https://doi.org/10.1101/2023.10.06.561287 44 |
45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /doc/P1-Fig1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/doc/P1-Fig1.png -------------------------------------------------------------------------------- /tutorial/data/seg_results/mask/Goblet_spot_GE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/data/seg_results/mask/Goblet_spot_GE.png -------------------------------------------------------------------------------- /tutorial/data/seg_results/mask/Goblet_spot_combined.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/data/seg_results/mask/Goblet_spot_combined.png -------------------------------------------------------------------------------- /tutorial/data/seg_results/mask/Goblet_spot_seg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/data/seg_results/mask/Goblet_spot_seg.png -------------------------------------------------------------------------------- /tutorial/data/seg_results/mask/mask0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/data/seg_results/mask/mask0.png -------------------------------------------------------------------------------- /tutorial/data/seg_results/mask/mask1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/data/seg_results/mask/mask1.png -------------------------------------------------------------------------------- /tutorial/data/seg_results/mask/mask2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/data/seg_results/mask/mask2.png -------------------------------------------------------------------------------- /tutorial/data/seg_results/mask/mask3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/data/seg_results/mask/mask3.png -------------------------------------------------------------------------------- /tutorial/data/seg_results/mask/mask4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/data/seg_results/mask/mask4.png -------------------------------------------------------------------------------- /tutorial/data/seg_results/mask/mask5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/data/seg_results/mask/mask5.png -------------------------------------------------------------------------------- /tutorial/sample_results/2D_density_plot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/sample_results/2D_density_plot.jpg -------------------------------------------------------------------------------- /tutorial/sample_results/3D_density_plot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/sample_results/3D_density_plot.jpg -------------------------------------------------------------------------------- /tutorial/sample_results/ABCG2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/sample_results/ABCG2.png -------------------------------------------------------------------------------- /tutorial/sample_results/CAF.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/sample_results/CAF.jpg -------------------------------------------------------------------------------- /tutorial/sample_results/CAF_subtypes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/sample_results/CAF_subtypes.jpg -------------------------------------------------------------------------------- /tutorial/sample_results/CD3DE_CD4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/sample_results/CD3DE_CD4.jpg -------------------------------------------------------------------------------- /tutorial/sample_results/CD3DE_CD4_Treg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/sample_results/CD3DE_CD4_Treg.jpg -------------------------------------------------------------------------------- /tutorial/sample_results/CD3D_CD3E.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/sample_results/CD3D_CD3E.jpg -------------------------------------------------------------------------------- /tutorial/sample_results/CD4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/sample_results/CD4.jpg -------------------------------------------------------------------------------- /tutorial/sample_results/Goblet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/sample_results/Goblet.jpg -------------------------------------------------------------------------------- /tutorial/sample_results/Goblet_meta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/sample_results/Goblet_meta.png -------------------------------------------------------------------------------- /tutorial/sample_results/Goblet_seg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/sample_results/Goblet_seg.png -------------------------------------------------------------------------------- /tutorial/sample_results/T_reg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/sample_results/T_reg.jpg -------------------------------------------------------------------------------- /tutorial/sample_results/UMI_count_Scanpy_54078.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/sample_results/UMI_count_Scanpy_54078.png -------------------------------------------------------------------------------- /tutorial/sample_results/goblet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/sample_results/goblet.png -------------------------------------------------------------------------------- /tutorial/sample_results/nuclei_filtered1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/sample_results/nuclei_filtered1.png -------------------------------------------------------------------------------- /tutorial/sample_results/nuclei_filtered2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/sample_results/nuclei_filtered2.png -------------------------------------------------------------------------------- /tutorial/sample_results/nuclei_filtered_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flashiness/METI/f30fc1caea0db2e8cba1c4c4179a1cbe0017fdf9/tutorial/sample_results/nuclei_filtered_white.png -------------------------------------------------------------------------------- /tutorial/scalefactors_json/BLCA_B1_scalefactors_json.json: -------------------------------------------------------------------------------- 1 | {"regist_target_img_scalef": 0.10861105, "tissue_hires_scalef": 0.036203682, "tissue_lowres_scalef": 0.010861104, "fiducial_diameter_fullres": 413.41321549533023, "spot_diameter_fullres": 236.23612833200863} -------------------------------------------------------------------------------- /tutorial/scalefactors_json/BLCA_B2_scalefactors_json.json: -------------------------------------------------------------------------------- 1 | {"regist_target_img_scalef": 0.10918024, "tissue_hires_scalef": 0.03639341, "tissue_lowres_scalef": 0.010918024, "fiducial_diameter_fullres": 416.51585872483804, "spot_diameter_fullres": 238.00905161682405} -------------------------------------------------------------------------------- /tutorial/scalefactors_json/STAD_G1_scalefactors_json.json: -------------------------------------------------------------------------------- 1 | {"tissue_hires_scalef": 0.21190931, "tissue_lowres_scalef": 0.063572794, "fiducial_diameter_fullres": 115.55588578733015, "spot_diameter_fullres": 71.53459596358533} -------------------------------------------------------------------------------- /tutorial/scalefactors_json/STAD_G2_scalefactors_json.json: -------------------------------------------------------------------------------- 1 | {"tissue_hires_scalef": 0.24414062, "tissue_lowres_scalef": 0.07324219, "fiducial_diameter_fullres": 96.03975204478644, "spot_diameter_fullres": 59.45317983724874} -------------------------------------------------------------------------------- /tutorial/scalefactors_json/STAD_G3_scalefactors_json.json: -------------------------------------------------------------------------------- 1 | {"tissue_hires_scalef": 0.10850695, "tissue_lowres_scalef": 0.032552082, "fiducial_diameter_fullres": 210.25118014605656, "spot_diameter_fullres": 130.15549247136835} -------------------------------------------------------------------------------- /tutorial/scalefactors_json/STAD_G4_scalefactors_json.json: -------------------------------------------------------------------------------- 1 | {"tissue_hires_scalef": 0.25201613, "tissue_lowres_scalef": 0.07560484, "fiducial_diameter_fullres": 96.10402062435438, "spot_diameter_fullres": 59.49296514840986} -------------------------------------------------------------------------------- /tutorial/tutorial.md: -------------------------------------------------------------------------------- 1 | 2 |

METI Tutorial

3 | 4 | ## Outline 5 | 1. [Installation] 6 | 2. [Import modules] 7 | 3. [Quality control] 8 | 4. [Read in data] 9 | 5. [Gene expression enhancement] 10 | 6. [Goblet marker gene expression] 11 | 7. [Region annotation] 12 | 8. [Segmentation] 13 | 9. [Integrarion of gene expression result with segmentation result] 14 | 10. [3D density plot] 15 | 11. [Cell type identification (T cells as an example)] 16 | 12. [Different cell type overlay (CAF subtype as an example)] 17 | 18 | ### 1. Installation 19 | The installation should take a few minutes on a normal computer. 20 | 21 | Now you can install the current release of METI by the following three ways: 22 | #### 1 PyPI: Directly install the package from PyPI. 23 | 24 | 25 | ```python 26 | # pip install METIforST 27 | pip install --no-cache-dir --user METIforST 28 | #Note: you need to make sure that the pip is for python3,or you can install METI by 29 | python3 -m pip install METIforST==0.2 30 | ``` 31 | 32 | ### 2. Import modules 33 | 34 | ```python 35 | import torch 36 | import csv,re, time 37 | import pickle 38 | import random 39 | import warnings 40 | warnings.filterwarnings('ignore') 41 | import pandas as pd 42 | import numpy as np 43 | from scipy import stats 44 | from scipy.sparse import issparse 45 | import scanpy as sc 46 | import matplotlib.colors as clr 47 | import matplotlib.pyplot as plt 48 | import cv2 49 | import TESLA as tesla 50 | from IPython.display import Image 51 | import scipy.sparse 52 | import scanpy as sc 53 | import pandas as pd 54 | import matplotlib.pyplot as plt 55 | import seaborn as sns 56 | from scanpy import read_10x_h5 57 | import PIL 58 | from PIL import Image as IMAGE 59 | import os 60 | import METI as meti 61 | import tifffile 62 | os.environ['KMP_DUPLICATE_LIB_OK']='True' 63 | ``` 64 | 65 | ```python 66 | METI.__version__ 67 | ``` 68 | ### 3. Quality control 69 | ```python 70 | adata=sc.read_visium("/tutorial/data/Spaceranger/") 71 | spatial=pd.read_csv("/tutorial/data/Spaceranger/tissue_positions_list.csv",sep=",",header=None,na_filter=False,index_col=0) 72 | 73 | adata 74 | ``` 75 | AnnData object with n_obs × n_vars = 3875 × 17943 76 | obs: 'in_tissue', 'array_row', 'array_col' 77 | var: 'gene_ids', 'feature_types', 'genome' 78 | uns: 'spatial' 79 | obsm: 'spatial' 80 | 81 | ```python 82 | adata.var_names_make_unique() 83 | adata.var["mt"] = adata.var_names.str.startswith("MT-") 84 | sc.pp.calculate_qc_metrics(adata, qc_vars=["mt"], inplace=True) 85 | 86 | adata 87 | ``` 88 | AnnData object with n_obs × n_vars = 3875 × 17943 89 | obs: 'in_tissue', 'array_row', 'array_col', 'n_genes_by_counts', 'log1p_n_genes_by_counts', 'total_counts', 'log1p_total_counts', 'pct_counts_in_top_50_genes', 'pct_counts_in_top_100_genes', 'pct_counts_in_top_200_genes', 'pct_counts_in_top_500_genes', 'total_counts_mt', 'log1p_total_counts_mt', 'pct_counts_mt' 90 | var: 'gene_ids', 'feature_types', 'genome', 'mt', 'n_cells_by_counts', 'mean_counts', 'log1p_mean_counts', 'pct_dropout_by_counts', 'total_counts', 'log1p_total_counts' 91 | uns: 'spatial' 92 | obsm: 'spatial' 93 | 94 | ```python 95 | plt.rcParams["figure.figsize"] = (8, 8) 96 | sc.pl.spatial(adata, img_key="hires", color=["total_counts", "n_genes_by_counts"], size = 1.5, save = 'UMI_count.png') 97 | ``` 98 | 99 | **UMI counts**![](./sample_results/UMI_count_Scanpy_54078.png) 100 | 101 | 102 | ### 4. Read in data 103 | The current version of METI requres three input data. 104 | 1. The gene expression matrix(n by k): expression_matrix.h5; 105 | 2. Spatial coordinateds of samplespositions.txt; 106 | 3. Histology image(optional): histology.tif, can be tif or png or jepg. 107 | 108 | ```python 109 | #================== 3. Read in data ==================# 110 | #Read original 10x_h5 data and save it to h5ad 111 | from scanpy import read_10x_h5 112 | adata = read_10x_h5("../tutorial/data/filtered_feature_bc_matrix.h5") 113 | spatial=pd.read_csv("../tutorial/data/tissue_positions_list.csv",sep=",",header=None,na_filter=False,index_col=0) 114 | adata.obs["x1"]=spatial[1] 115 | adata.obs["x2"]=spatial[2] 116 | adata.obs["x3"]=spatial[3] 117 | adata.obs["x4"]=spatial[4] 118 | adata.obs["x5"]=spatial[5] 119 | adata.obs["array_x"]=adata.obs["x2"] 120 | adata.obs["array_y"]=adata.obs["x3"] 121 | adata.obs["pixel_x"]=adata.obs["x4"] 122 | adata.obs["pixel_y"]=adata.obs["x5"] 123 | #Select captured samples 124 | adata=adata[adata.obs["x1"]==1] 125 | adata.var_names=[i.upper() for i in list(adata.var_names)] 126 | adata.var["genename"]=adata.var.index.astype("str") 127 | adata.write_h5ad("../tutorial/data/1957495_data.h5ad") 128 | 129 | #Read in gene expression and spatial location 130 | counts=sc.read("../tutorial/data/1957495_data.h5ad") 131 | #Read in hitology image 132 | PIL.Image.MAX_IMAGE_PIXELS = None 133 | img = IMAGE.open(r"../tutorial/data/histology.tif") 134 | img = np.array(img) 135 | 136 | #if your image has 4 dimensions, only keep first 3 dims 137 | img=img[...,:3] 138 | ``` 139 | 140 | ### 5. Gene expression enhancement 141 | #### 5.1 Preprocessing 142 | ```python 143 | resize_factor=1000/np.min(img.shape[0:2]) 144 | resize_width=int(img.shape[1]*resize_factor) 145 | resize_height=int(img.shape[0]*resize_factor) 146 | counts.var.index=[i.upper() for i in counts.var.index] 147 | counts.var_names_make_unique() 148 | counts.raw=counts 149 | sc.pp.log1p(counts) # impute on log scale 150 | if issparse(counts.X):counts.X=counts.X.A 151 | ``` 152 | #### 5.2 Contour detection 153 | ```python 154 | # Detect contour using cv2 155 | cnt=tesla.cv2_detect_contour(img, apertureSize=5,L2gradient = True) 156 | 157 | binary=np.zeros((img.shape[0:2]), dtype=np.uint8) 158 | cv2.drawContours(binary, [cnt], -1, (1), thickness=-1) 159 | #Enlarged filter 160 | cnt_enlarged = tesla.scale_contour(cnt, 1.05) 161 | binary_enlarged = np.zeros(img.shape[0:2]) 162 | cv2.drawContours(binary_enlarged, [cnt_enlarged], -1, (1), thickness=-1) 163 | img_new = img.copy() 164 | cv2.drawContours(img_new, [cnt], -1, (255), thickness=20) 165 | img_new=cv2.resize(img_new, ((resize_width, resize_height))) 166 | cv2.imwrite('../tutorial/data/cnt_1957495.jpg', img_new) 167 | Image(filename='../tutorial/data/cnt_1957495.jpg') 168 | ``` 169 | 170 | #### 5.3 Gene expression enhancement 171 | ```python 172 | #Set size of superpixel 173 | res=40 174 | # Note, if the numer of superpixels is too large and take too long, you can increase the res to 100 175 | 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) 176 | enhanced_exp_adata.write_h5ad("../tutorial/data/enhanced_exp.h5ad") 177 | ``` 178 | Total number of sudo points: 92844 179 | Calculating spot 0 180 | Calculating spot 1000 181 | Calculating spot 2000 182 | Calculating spot 3000 183 | Calculating spot 4000 184 | Calculating spot 5000 185 | ... 186 | --- 280.4760136604309 seconds --- 187 | Imputing spot 0 188 | Imputing spot 1000 189 | Imputing spot 2000 190 | Imputing spot 3000 191 | Imputing spot 4000 192 | Imputing spot 5000 193 | ... 194 | 195 | ### 6. Goblet marker gene expression 196 | ```python 197 | #================ determine if markers are in ===============# 198 | enhanced_exp_adata=sc.read("..tutorial/data/enhanced_exp.h5ad") 199 | markers = ["MS4A10", "MGAM", "CYP4F2", "XPNPEP2", "SLC5A9", "SLC13A2", "SLC28A1", "MEP1A", "ABCG2", "ACE2"] 200 | for i in range(len(markers)): 201 | if markers[i] in enhanced_exp_adata.var.index: print("yes") 202 | else: print(markers[i]) 203 | ``` 204 | 205 | ```python 206 | save_dir="..tutorial/data/Goblet/" 207 | if not os.path.exists(save_dir):os.mkdir(save_dir) 208 | ``` 209 | 210 | ```python 211 | #================ Plot gene expression image ===============# 212 | markers = ["MS4A10", "MGAM", "CYP4F2", "XPNPEP2", "SLC5A9", "SLC13A2", "SLC28A1", "MEP1A", "ABCG2", "ACE2"] 213 | for i in range(len(markers)): 214 | cnt_color = clr.LinearSegmentedColormap.from_list('magma', ["#000003", "#3b0f6f", "#8c2980", "#f66e5b", "#fd9f6c", "#fbfcbf"], N=256) 215 | g=markers[i] 216 | enhanced_exp_adata.obs[g]=enhanced_exp_adata.X[:,enhanced_exp_adata.var.index==g] 217 | fig=sc.pl.scatter(enhanced_exp_adata,alpha=1,x="y",y="x",color=g,color_map=cnt_color,show=False,size=10) 218 | fig.set_aspect('equal', 'box') 219 | fig.invert_yaxis() 220 | plt.gcf().set_dpi(600) 221 | fig.figure.show() 222 | 223 | plt.savefig(save_dir + str(markers[i]) + ".png", dpi=600) 224 | plt.close() 225 | ``` 226 | **Gene expression**![](./sample_results/ABCG2.png) 227 | 228 | ```python 229 | #================ Plot meta gene expression image ===============# 230 | enhanced_exp_adata=sc.read("/Users/jjiang6/Desktop/UTH/MDA GRA/Spatial transcriptome/Cell Segmentation/With Jian Hu/S1_54078/TESLA/enhanced_exp.h5ad") 231 | genes = ["MS4A10", "MGAM", "CYP4F2", "XPNPEP2", "SLC5A9", "SLC13A2", "SLC28A1", "MEP1A", "ABCG2", "ACE2"] 232 | 233 | sudo_adata = meti.meta_gene_plot(img=img, 234 | binary=binary, 235 | sudo_adata=enhanced_exp_adata, 236 | genes=genes, 237 | resize_factor=resize_factor, 238 | target_size="small") 239 | 240 | cnt_color = clr.LinearSegmentedColormap.from_list('magma', ["#000003", "#3b0f6f", "#8c2980", "#f66e5b", "#fd9f6c", "#fbfcbf"], N=256) 241 | fig=sc.pl.scatter(sudo_adata,alpha=1,x="y",y="x",color='meta',color_map=cnt_color,show=False,size=5) 242 | fig.set_aspect('equal', 'box') 243 | fig.invert_yaxis() 244 | plt.gcf().set_dpi(600) 245 | fig.figure.show() 246 | 247 | plt.savefig(save_dir + "Goblet_meta.png", dpi=600) 248 | plt.close() 249 | ``` 250 | **Meta gene expression**![](./sample_results/Goblet_meta.png) 251 | 252 | ### 7. Region annotation 253 | ```python 254 | genes=["MS4A10", "MGAM", "CYP4F2", "XPNPEP2", "SLC5A9", "SLC13A2", "SLC28A1", "MEP1A", "ABCG2", "ACE2"] 255 | genes=list(set([i for i in genes if i in enhanced_exp_adata.var.index ])) 256 | #target_size can be set to "small" or "large". 257 | pred_refined, target_clusters, c_m=meti.annotation(img=img, 258 | binary=binary, 259 | sudo_adata=enhanced_exp_adata, 260 | genes=genes, 261 | resize_factor=resize_factor, 262 | num_required=1, 263 | target_size="small") 264 | #Plot 265 | ret_img=tesla.visualize_annotation(img=img, 266 | binary=binary, 267 | resize_factor=resize_factor, 268 | pred_refined=pred_refined, 269 | target_clusters=target_clusters, 270 | c_m=c_m) 271 | 272 | cv2.imwrite(save_dir + 'IME.jpg', ret_img) 273 | Image(filename=save_dir + 'IME.jpg') 274 | ``` 275 | **Goblet region annotation**![](./sample_results/Goblet.jpg) 276 | 277 | ```python 278 | #=====================================Convert to spot level============================================# 279 | adata.obs["color"]=extract_color(x_pixel=(np.array(adata.obs["pixel_x"])*resize_factor).astype(np.int64), 280 | y_pixel=(np.array(adata.obs["pixel_y"])*resize_factor).astype(np.int64), image=ret_img, beta=25) 281 | 282 | type = [] 283 | for each in adata.obs["color"]: 284 | if each < adata.obs["color"].quantile(0.2): 285 | r = "yes" 286 | type.append(r) 287 | else: 288 | r = "no" 289 | type.append(r) 290 | 291 | adata.obs['Goblet_GE'] = type 292 | 293 | fig, ax = plt.subplots(figsize=(10, 10)) # Adjust the size as needed 294 | ax.imshow(img) 295 | ax.set_axis_off() 296 | sc.pl.scatter(adata, x='pixel_y', y='pixel_x', color='Goblet_GE', ax=ax, size = 150, title='Goblet GE Spot Annotations') 297 | # Save the figure 298 | plt.savefig('./sample_results/Goblet_spot_GE.png', dpi=300, bbox_inches='tight') 299 | ``` 300 | **Goblet region annotation spot level**![](./data/seg_results/mask/Goblet_spot_GE.png) 301 | 302 | ### 8. Segmentation 303 | ```python 304 | plot_dir="/rsrch4/home/genomic_med/jjiang6/Project1/S1_54078/Segmentation/NC_review_Goblet_seg/" 305 | save_dir=plot_dir+"/seg_results" 306 | adata= sc.read("/rsrch4/home/genomic_med/jjiang6/Project1/S1_54078/TESLA/54078_data.h5ad") 307 | 308 | img_path = '/rsrch4/home/genomic_med/jjiang6/Project1/S1_54078/1415785-6 Bx2.tif' 309 | img = tiff.imread(img_path) 310 | d0, d1= img.shape[0], img.shape[1] 311 | 312 | #=====================================Split into patched===================================================== 313 | patch_size=400 314 | patches=patch_split_for_ST(img=img, patch_size=patch_size, spot_info=adata.obs, x_name="pixel_x", y_name="pixel_y") 315 | patch_info=adata.obs 316 | 317 | # save results 318 | pickle.dump(patches, open(plot_dir + 'patches.pkl', 'wb')) 319 | #=================================Image Segmentation=================================== 320 | meti.Segment_Patches(patches, save_dir=save_dir,n_clusters=10) 321 | ``` 322 | Doing: 0 / 9 323 | Doing: 1 / 9 324 | Doing: 2 / 9 325 | Doing: 3 / 9 326 | Doing: 4 / 9 327 | Doing: 5 / 9 328 | Doing: 6 / 9 329 | Doing: 7 / 9 330 | Doing: 8 / 9 331 | 332 | ```python 333 | #=================================Get masks=================================# 334 | pred_file_locs=[save_dir+"/patch"+str(j)+"_pred.npy" for j in range(patch_info.shape[0])] 335 | dic_list=meti.get_color_dic(patches, seg_dir=save_dir) 336 | masks_index=meti.Match_Masks(dic_list, num_mask_each=5, mapping_threshold1=30, mapping_threshold2=60) 337 | ``` 338 | Doing 0 339 | Adding, no match counts: 0 new rate: 1.0 340 | Adding, no match counts: 1 new rate: 1.0 341 | Not adding, no match counts: 4 new rate: 1.0 342 | Doing 1 343 | Not adding, no match counts: 0 new rate: 0.6842105263157895 344 | Adding, no match counts: 0 new rate: 1.0 345 | Not adding, no match counts: 2 new rate: 0.6 346 | Doing 2 347 | Doing 3 348 | Doing 4 349 | Not adding, no match counts: 0 new rate: 0.12903225806451613 350 | Adding, no match counts: 1 new rate: 0.9 351 | Doing 5 352 | Doing 6 353 | Doing 7 354 | Doing 8 355 | 356 | ```python 357 | masks=meti.Extract_Masks(masks_index, pred_file_locs, patch_size) 358 | ``` 359 | Extracting mask 0 360 | Extracting mask 1 361 | Extracting mask 2 362 | Extracting mask 3 363 | Extracting mask 4 364 | Extracting mask 5 365 | 366 | ```python 367 | combined_masks=meti.Combine_Masks(masks, patch_info, d0, d1) 368 | ``` 369 | Combining mask 0 370 | Combining mask 1 371 | Combining mask 2 372 | Combining mask 3 373 | Combining mask 4 374 | Combining mask 5 375 | 376 | ```python 377 | #=================================Plot masks=================================# 378 | plot_dir = '../tutorial/data/seg_results/mask' 379 | 380 | for i in range(masks.shape[0]): #Each mask 381 | print("Plotting mask ", str(i)) 382 | ret=(combined_masks[i]*255) 383 | cv2.imwrite(plot_dir+'/mask'+str(i)+'.png', ret.astype(np.uint8)) 384 | ``` 385 | Plotting mask 0 386 | Plotting mask 1 387 | Plotting mask 2 388 | Plotting mask 3 389 | Plotting mask 4 390 | Plotting mask 5 391 | 392 | ```python 393 | #=================================Choose one mask to detect cells/nucleis=================================# 394 | channel=1 395 | converted_image = combined_masks[1].astype(np.uint8) 396 | ret, labels = cv2.connectedComponents(converted_image) 397 | features=meti.Extract_CC_Features_each_CC(labels) 398 | 399 | num_labels = labels.max() 400 | height, width = labels.shape 401 | 402 | colors = np.random.randint(0, 255, size=(num_labels + 1, 3), dtype=np.uint8) 403 | colors[0] = [0, 0, 0] 404 | colored_mask = np.zeros((height, width, 3), dtype=np.uint8) 405 | colored_mask = colors[labels] 406 | 407 | # save the colored nucleis channel 408 | cv2.imwrite('/rsrch4/home/genomic_med/jjiang6/Project1/S1_54078/Segmentation/NC_review_Goblet_seg/seg_results/goblet.png', colored_mask) 409 | # save nucleis label 410 | np.save('/rsrch4/home/genomic_med/jjiang6/Project1/S1_54078/Segmentation/NC_review_Goblet_seg/seg_results/labels.npy', labels) 411 | # save nucleis features, including, area, length, width 412 | features.to_csv('/rsrch4/home/genomic_med/jjiang6/Project1/S1_54078/Segmentation/NC_review_Goblet_seg/seg_results/features.csv', index=False) 413 | ``` 414 | 415 | ```python 416 | #=================================filter out goblet cells=================================# 417 | plot_dir="../tutorial/data/seg_results/mask" 418 | if not os.path.exists(plot_dir):os.mkdir(plot_dir) 419 | 420 | labels=np.load(plot_dir+"labels.npy") 421 | 422 | #Filter - different cell type needs to apply different parameter values 423 | features=pd.read_csv(plot_dir+"features.csv", header=0, index_col=0) 424 | features['mm_ratio'] = features['major_axis_length']/features['minor_axis_length'] 425 | features_sub=features[(features["area"]>120) & 426 | (features["area"]<1500) & 427 | (features["solidity"]>0.85) & 428 | (features["mm_ratio"]<2)] 429 | index=features_sub.index.tolist() 430 | labels_filtered=labels*np.isin(labels, index) 431 | 432 | np.save(plot_dir+"nuclei_filtered.npy", labels_filtered) 433 | 434 | num_labels = labels_filtered.max() 435 | height, width = labels_filtered.shape 436 | 437 | colors = np.random.randint(0, 255, size=(num_labels + 1, 3), dtype=np.uint8) 438 | colors[0] = [0, 0, 0] 439 | colored_mask = np.zeros((height, width, 3), dtype=np.uint8) 440 | colored_mask = colors[labels_filtered] 441 | 442 | cv2.imwrite(plot_dir+'/goblet_filtered.png', colored_mask) 443 | 444 | ``` 445 | 446 | **nuclei segmentation**![](./sample_results/goblet.png) 447 | 448 | Based on the segmentation results and features such as areas, width-length ratio, solidity, etc, you can filter out goblet according to their morphology. 449 | 450 | **goblet segmentation**![](./sample_results/nuclei_filtered_white.png) 451 | 452 | ```python 453 | #=====================================Convert to spot level============================================# 454 | plot_dir="./tutorial/sample_results/" 455 | img_seg = np.load(plot_dir+'nuclei_filtered_white.npy') 456 | 457 | adata.obs["color"]=meti.extract_color(x_pixel=(np.array(adata.obs["pixel_x"])).astype(np.int64), 458 | y_pixel=(np.array(adata.obs["pixel_y"])).astype(np.int64), image=img_seg, beta=49) 459 | 460 | type = [] 461 | for each in adata.obs["color"]: 462 | if each > 0: 463 | r = "yes" 464 | type.append(r) 465 | else: 466 | r = "no" 467 | type.append(r) 468 | 469 | adata.obs['Goblet_seg'] = type 470 | 471 | fig, ax = plt.subplots(figsize=(10, 10)) 472 | ax.imshow(img) 473 | ax.set_axis_off() 474 | sc.pl.scatter(adata, x='pixel_y', y='pixel_x', color='Goblet_seg', ax=ax, size = 150, title='Goblet Segmentation Spot Annotations') 475 | # Save the figure 476 | plt.savefig(plot_dir+'Goblet_spot_seg.png', format='png', dpi=300, bbox_inches='tight') 477 | ``` 478 | **goblet segmentation spot level**![](./data/seg_results/mask/Goblet_spot_seg.png) 479 | 480 | ### 9. Integrarion of gene expression result with segmentation result 481 | ```python 482 | adata.obs['Goblet_combined'] = np.where((adata.obs['Goblet_seg'] == 'yes') | (adata.obs['Goblet_GE'] == 'yes'), 'yes', 'no') 483 | 484 | fig, ax = plt.subplots(figsize=(10, 10)) 485 | ax.imshow(img) 486 | ax.set_axis_off() 487 | sc.pl.scatter(adata, x='pixel_y', y='pixel_x', color='Goblet_combined', ax=ax, size = 150,title='Goblet Combined Spot Annotations') 488 | # Save the figure 489 | plt.savefig(plot_dir+'Goblet_spot_combined.png', format='png', dpi=300, bbox_inches='tight') 490 | ``` 491 | **goblet combined result spot level**![](./data/seg_results/mask/Goblet_spot_combined.png) 492 | 493 | ### 10. 3D density plot 494 | ```python 495 | #-----------------------------------Density of nuclei------------------------------------------# 496 | ret=np.load(plot_dir+'nuclei_filtered_white.npy') 497 | # ret_sub = ret[2000:7000, 2000:7000] 498 | step = 30 499 | x_range = np.arange(1800, 7000, step) 500 | y_range = np.arange(2500, 7000, step) 501 | 502 | num_cells = np.array([np.sum(ret[x:x+step, y:y+step]) for x in x_range for y in y_range]) 503 | rows, cols = len(x_range), len(y_range) 504 | Z = num_cells.reshape(rows, cols) 505 | x_pixel, y_pixel = np.meshgrid(y_range, x_range) 506 | 507 | plt.contour(x_pixel, y_pixel, Z) 508 | plt.colorbar(label="Cell Density") 509 | plt.gca().invert_yaxis() 510 | plt.show() 511 | ``` 512 | **2D density plot**![](./sample_results/2D_density_plot.jpg) 513 | ```python 514 | coolwarm_colors = [(0, "#ffffff"), (0.25, "#3c4dc7"), (0.5, "#f5f5f5"), (0.75, "#ff8c00"), (1, "#c72222")] 515 | coolwarm_cmap = LinearSegmentedColormap.from_list('coolwarm', coolwarm_colors) 516 | 517 | def plotter(E = 30, A = 60, save_path=None): 518 | fig = plt.figure(figsize = [12,8]) 519 | ax = fig.add_subplot(111, projection='3d') 520 | 521 | smoothed_Z = gaussian_filter(Z, sigma=2) 522 | 523 | im = ax.plot_surface(x_pixel, y_pixel, smoothed_Z, cmap = coolwarm_cmap, alpha=0.85) 524 | ax.contourf(x_pixel, y_pixel, smoothed_Z, zdir = 'z', offset = -1500, cmap = coolwarm_cmap) 525 | ax.view_init(elev = E, azim = A) 526 | ax.set_zlim([-1500, 1000]) 527 | ax.set_zticks([]) 528 | ax.set_zlabel('') 529 | fig.colorbar(im, shrink=0.5) 530 | ax.grid(False) 531 | 532 | if save_path: 533 | plt.savefig(save_path, dpi=300, bbox_inches='tight') 534 | plt.show() 535 | 536 | plotter(E=30, A=60, save_path=plot_dir + '3D_Density.png') 537 | ``` 538 | **3D density plot**![](./sample_results/3D_density_plot.jpg) 539 | 540 | 541 | 542 | ### 11. Cell type identification (T cells as an example) 543 | 544 | ```python 545 | #-----------------------------------T cell aggregate------------------------------------------# 546 | genes= ["CD3D", "CD3E"] 547 | genes=list(set([i for i in genes if i in enhanced_exp_adata.var.index ])) 548 | pred_refined, target_clusters, c_m=meti.annotation(img=img, 549 | binary=binary, 550 | sudo_adata=enhanced_exp_adata, 551 | genes=genes, 552 | resize_factor=resize_factor, 553 | num_required=1, 554 | target_size="small") 555 | 556 | #Plot 557 | ret_img=tesla.visualize_annotation(img=img, 558 | binary=binary, 559 | resize_factor=resize_factor, 560 | pred_refined=pred_refined, 561 | target_clusters=target_clusters, 562 | c_m=c_m) 563 | 564 | cv2.imwrite(save_dir + "CD3D_CD3E.jpg", ret_img) 565 | Image(filename=save_dir + "CD3D_CD3E.jpg") 566 | 567 | print("Target_clusters: ", target_clusters, "\n") 568 | T_CD3DE = pred_refined 569 | ``` 570 | Target_clusters: [12, 4, 20, 3] 571 | 572 | **T cell expression plot**![](./sample_results/CD3D_CD3E.jpg) 573 | 574 | ```python 575 | #-----------------------------------CD4 expression------------------------------------------# 576 | genes= ["CD4"] 577 | genes=list(set([i for i in genes if i in enhanced_exp_adata.var.index ])) 578 | pred_refined, target_clusters, c_m=meti.annotation(img=img, 579 | binary=binary, 580 | sudo_adata=enhanced_exp_adata, 581 | genes=genes, 582 | resize_factor=resize_factor, 583 | num_required=1, 584 | target_size="small") 585 | 586 | #Plot 587 | ret_img=tesla.visualize_annotation(img=img, 588 | binary=binary, 589 | resize_factor=resize_factor, 590 | pred_refined=pred_refined, 591 | target_clusters=target_clusters, 592 | c_m=c_m) 593 | 594 | cv2.imwrite(save_dir + "CD4.jpg", ret_img) 595 | Image(filename=save_dir + "CD4.jpg") 596 | 597 | print("Target_clusters: ", target_clusters, "\n") 598 | T_CD4 = pred_refined 599 | ``` 600 | Target_clusters: [7, 9, 17, 10, 12, 13, 14, 5, 1] 601 | 602 | **CD4 expression plot**![](./sample_results/CD4.jpg) 603 | 604 | 605 | ```python 606 | #-----------------------------------CD4 T cell aggregate------------------------------------------# 607 | T_CD3_CD4 = np.array([True if each in [12, 4, 20, 3] else False for each in T_CD3DE]) & np.array([True if each in [7, 9, 17, 10, 12, 13, 14, 5, 1] else False for each in T_CD4]) 608 | T_CD3_CD4 = T_CD3_CD4.astype('int') 609 | Counter(T_CD3_CD4) 610 | ret_img=tesla.visualize_annotation(img=img, 611 | binary=binary, 612 | resize_factor=resize_factor, 613 | pred_refined=T_CD3_CD4, 614 | target_clusters=[1], 615 | c_m=c_m) 616 | 617 | cv2.imwrite(save_dir + "CD3DE_CD4.jpg", ret_img) 618 | Image(filename=save_dir + "CD3DE_CD4.jpg") 619 | ``` 620 | **CD4 T cell plot**![](./sample_results/CD3DE_CD4.jpg) 621 | 622 | 623 | ```python 624 | #-----------------------------------Treg marker gene expression------------------------------------------# 625 | genes= ["FOXP3", "IL2RA"] 626 | genes=list(set([i for i in genes if i in enhanced_exp_adata.var.index ])) 627 | #target_size can be set to "small" or "large". 628 | pred_refined, target_clusters, c_m=meti.annotation(img=img, 629 | binary=binary, 630 | sudo_adata=enhanced_exp_adata, 631 | genes=genes, 632 | resize_factor=resize_factor, 633 | num_required=1, 634 | target_size="small") 635 | 636 | #Plot 637 | ret_img=tesla.visualize_annotation(img=img, 638 | binary=binary, 639 | resize_factor=resize_factor, 640 | pred_refined=pred_refined, 641 | target_clusters=target_clusters, 642 | c_m=c_m) 643 | 644 | cv2.imwrite(save_dir + "T_reg.jpg", ret_img) 645 | Image(filename=save_dir + "T_reg.jpg") 646 | 647 | print("Target_clusters: ", target_clusters, "\n") 648 | T_reg = pred_refined 649 | ``` 650 | Target_clusters: [5, 4, 16] 651 | 652 | **Treg marker expression plot**![](./sample_results/T_reg.jpg) 653 | 654 | 655 | ```python 656 | #-----------------------------------Treg aggregate------------------------------------------# 657 | T_CD3_CD4_reg = np.array([True if each in [12, 4, 20, 3] else False for each in T_CD3DE]) & \ 658 | np.array([True if each in [7, 9, 17, 10, 12, 13, 14, 5, 1] else False for each in T_CD4]) & \ 659 | np.array([True if each in [5, 4, 16] else False for each in T_reg]) 660 | T_CD3_CD4_reg = T_CD3_CD4_reg.astype('int') 661 | Counter(T_CD3_CD4_reg) 662 | 663 | ret_img=tesla.visualize_annotation(img=img, 664 | binary=binary, 665 | resize_factor=resize_factor, 666 | pred_refined=T_CD3_CD4_reg, 667 | target_clusters=[1], 668 | c_m=c_m) 669 | 670 | cv2.imwrite(save_dir + "CD3DE_CD4_Treg.jpg", ret_img) 671 | Image(filename=save_dir + "CD3DE_CD4_Treg.jpg") 672 | ``` 673 | **Treg plot**![](./sample_results/CD3DE_CD4_Treg.jpg) 674 | 675 | ### 12. Different cell type overlay (CAF subtype as an example) 676 | 677 | ```python 678 | #=============================CAF===========================# 679 | genes= ["COL1A1", "COL1A2", "COL6A1", "COL6A2"] 680 | genes=list(set([i for i in genes if i in enhanced_exp_adata.var.index ])) 681 | #target_size can be set to "small" or "large". 682 | pred_refined, target_clusters, c_m=meti.annotation(img=img, 683 | binary=binary, 684 | sudo_adata=enhanced_exp_adata, 685 | genes=genes, 686 | resize_factor=resize_factor, 687 | num_required=1, 688 | target_size="small") 689 | 690 | ret_img=tesla.visualize_annotation(img=img, 691 | binary=binary, 692 | resize_factor=resize_factor, 693 | pred_refined=pred_refined, 694 | target_clusters=target_clusters, 695 | c_m=c_m) 696 | 697 | cv2.imwrite(save_dir + "CAF.jpg", ret_img) 698 | Image(filename=save_dir + "CAF.jpg") 699 | 700 | print("Target_clusters: ", target_clusters, "\n") 701 | T_CAF = pred_refined 702 | ``` 703 | Target_clusters: [5, 6, 7, 19] 704 | 705 | **CAF plot**![](./sample_results/CAF.jpg) 706 | 707 | ```python 708 | #=============================mCAF===========================# 709 | genes= ["TGFB1", "ACTA2"] 710 | genes=list(set([i for i in genes if i in enhanced_exp_adata.var.index ])) 711 | #target_size can be set to "small" or "large". 712 | pred_refined, target_clusters, c_m=annotation(img=img, 713 | binary=binary, 714 | sudo_adata=enhanced_exp_adata, 715 | genes=genes, 716 | resize_factor=resize_factor, 717 | num_required=1, 718 | target_size="small") 719 | 720 | T_mCAF = pred_refined 721 | print("Target_clusters: ", target_clusters, "\n") 722 | # Target_clusters: [18, 3, 8] 723 | 724 | #find the overlay of CAF and mCAF 725 | T_CAF_mCAF = np.array([True if each in [5, 6, 7, 19] else False for each in T_CAF]) & \ 726 | np.array([True if each in [18, 3, 8] else False for each in T_mCAF]) 727 | T_CAF_mCAF = T_CAF_mCAF.astype('int') 728 | Counter(T_CAF_mCAF) 729 | 730 | #=============================iCAF===========================# 731 | genes= ["CXCL12", "CXCL13", "CXCL14"] 732 | genes=list(set([i for i in genes if i in enhanced_exp_adata.var.index ])) 733 | #target_size can be set to "small" or "large". 734 | pred_refined, target_clusters, c_m=annotation(img=img, 735 | binary=binary, 736 | sudo_adata=enhanced_exp_adata, 737 | genes=genes, 738 | resize_factor=resize_factor, 739 | num_required=1, 740 | target_size="small") 741 | 742 | T_iCAF = pred_refined 743 | print("Target_clusters: ", target_clusters, "\n") 744 | # Target_clusters: [11] 745 | 746 | #find the overlay of CAF and iCAF 747 | T_CAF_iCAF = np.array([True if each in [5, 6, 7, 19] else False for each in T_CAF]) & \ 748 | np.array([True if each in [11] else False for each in T_iCAF]) 749 | T_CAF_iCAF = T_CAF_iCAF.astype('int') 750 | Counter(T_CAF_mCAF) 751 | 752 | #=============================apCAF===========================# 753 | genes= ["HLA-DQA1", "HLA-DPB1"] 754 | genes=list(set([i for i in genes if i in enhanced_exp_adata.var.index ])) 755 | #target_size can be set to "small" or "large". 756 | pred_refined, target_clusters, c_m=annotation(img=img, 757 | binary=binary, 758 | sudo_adata=enhanced_exp_adata, 759 | genes=genes, 760 | resize_factor=resize_factor, 761 | num_required=1, 762 | target_size="small") 763 | 764 | T_apCAF = pred_refined 765 | print("Target_clusters: ", target_clusters, "\n") 766 | # Target_clusters: [2, 5, 8] 767 | 768 | #find the overlay of CAF and iCAF 769 | T_CAF_apCAF = np.array([True if each in [5, 6, 7, 19] else False for each in T_CAF]) & \ 770 | np.array([True if each in [2, 5, 8] else False for each in T_apCAF]) 771 | T_CAF_apCAF = T_CAF_apCAF.astype('int') 772 | Counter(T_CAF_apCAF) 773 | 774 | #========================CAF subtypes overlay=====================# 775 | ret_img=tesla.visualize_annotation(img=img, 776 | binary=binary, 777 | resize_factor=resize_factor, 778 | pred_refined=pred_refined, 779 | target_clusters=[0], 780 | c_m=c_m) 781 | 782 | ret_img_new = ret_img.copy() 783 | resize_height = ret_img_new.shape[0] 784 | resize_width = ret_img_new.shape[1] 785 | alpha = 1 786 | 787 | # CAF - red 788 | target_clusters1=[5, 6, 7, 19] 789 | target_img = np.array([True if each in target_clusters1 else False for each in T_CAF]) 790 | color = [43, 57, 192] 791 | target_img = target_img.astype(int).reshape(resize_height, resize_width) 792 | ret_img_new[target_img!=0]=np.array(color) 793 | 794 | 795 | # mCAF - blue 796 | target_clusters2 = [1] 797 | target_img = np.array([True if each in target_clusters2 else False for each in T_CAF_mCAF]) 798 | color = [180, 130, 70] 799 | target_img = target_img.astype(int).reshape(resize_height, resize_width) 800 | ret_img_new[target_img!=0]=np.array(color) 801 | 802 | 803 | # iCAF - yellow 804 | target_clusters3 = [1] 805 | target_img = np.array([True if each in target_clusters3 else False for each in T_CAF_iCAF]) 806 | color = [15, 196, 241] 807 | target_img = target_img.astype(int).reshape(resize_height, resize_width) 808 | ret_img_new[target_img!=0]=np.array(color) 809 | 810 | 811 | # apCAF - green 812 | target_clusters4 = [1] 813 | target_img = np.array([True if each in target_clusters4 else False for each in T_CAF_apCAF]) 814 | color = [113, 204, 46] 815 | target_img = target_img.astype(int).reshape(resize_height, resize_width) 816 | ret_img_new[target_img!=0]=np.array(color) 817 | 818 | cv2.imwrite(save_dir + 'CAF_subtypes.jpg', ret_img_new) 819 | Image(filename=save_dir + "CAF_subtypes.jpg") 820 | ``` 821 | **CAF subtypes overlay plot**![](./sample_results/CAF_subtypes.jpg) 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | --------------------------------------------------------------------------------