phd-ms 1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
phd_ms-1.0/PKG-INFO ADDED
@@ -0,0 +1,34 @@
1
+ Metadata-Version: 2.4
2
+ Name: phd-ms
3
+ Version: 1.0
4
+ Summary: Multiscale domain identification for spatial transcriptomic data.
5
+ Author: Perry Beamer
6
+ Author-email: perry.beamer@gmail.com
7
+ License: MIT License
8
+ Keywords: spatial transcriptomics,topological data analysis,multiscale domains
9
+ Classifier: Programming Language :: Python :: 3.8
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Operating System :: MacOS
14
+ Classifier: Topic :: Scientific/Engineering
15
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: gudhi>=3.8.0
19
+ Requires-Dist: leidenalg>=0.9.0
20
+ Requires-Dist: matplotlib==3.8.2
21
+ Requires-Dist: numpy>=1.23.4
22
+ Requires-Dist: pandas>=2.2.0
23
+ Requires-Dist: plotly>=5.3.1
24
+ Requires-Dist: pot>=0.8.0
25
+ Requires-Dist: scanpy==1.9.6
26
+ Requires-Dist: scipy>=1.9.1
27
+ Requires-Dist: scikit-learn>=1.5.1
28
+ Requires-Dist: importlib-metadata
29
+ Dynamic: author
30
+ Dynamic: author-email
31
+ Dynamic: summary
32
+
33
+ # phd-ms00.03
34
+
phd_ms-1.0/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # phd-ms00.03
2
+
@@ -0,0 +1,8 @@
1
+ # __init__ file
2
+ import warnings
3
+ warnings.filterwarnings('ignore')
4
+
5
+ from . import tools as tl
6
+
7
+ import sys
8
+ sys.modules.update({f'{__name__}.{m}': globals()[m] for m in ['tl']})
@@ -0,0 +1,3 @@
1
+ from ._leiden import leiden
2
+ from ._filtration import filt_to_matrix, union_find_dmat,get_sub_features
3
+ from ._benchmark import clusters_to_distribution
@@ -0,0 +1,28 @@
1
+ import numpy as np
2
+ import ot
3
+ import matplotlib.pyplot as plt
4
+ from scipy.special import logit
5
+ import pandas as pd
6
+
7
+ def clusters_to_distribution(clusters):
8
+ if isinstance(clusters,pd.Series):
9
+ distribution = np.zeros((len(clusters.cat.categories.tolist()),len(clusters)))
10
+ for i in range(len(clusters)):
11
+ if isinstance(clusters.iloc[i],float):
12
+ continue
13
+ index = next(j for j in range(len(clusters.cat.categories.tolist())) if clusters.cat.categories.tolist()[j] == clusters.iloc[i])
14
+ distribution[index,i] = 1
15
+ cmat = distribution.transpose().copy()
16
+
17
+ elif isinstance(clusters,np.ndarray):
18
+ cmat = clusters.copy()
19
+ distribution = np.zeros((clusters.shape[1],clusters.shape[0]))
20
+ for i in range(clusters.shape[0]): # Iterate over rows
21
+ for j in range(clusters.shape[1]): # Iterate over columns
22
+ if clusters[i,j] < 0.01:
23
+ distribution[j,i] = 0
24
+ else:
25
+ distribution[j,i] = 1
26
+
27
+ distribution /= np.sum(distribution,axis=1,keepdims=True)
28
+ return distribution,cmat
@@ -0,0 +1,141 @@
1
+ import numpy as np
2
+ import gudhi as gd
3
+
4
+ def filt_to_matrix(simp_tree,threshold=.05):
5
+ matrix = np.ones((simp_tree.num_vertices(),simp_tree.num_vertices()))
6
+ #print(simp_tree.num_vertices())
7
+ max_node = np.max(list(x[1] for x in simp_tree.get_filtration() if len(x[0])==1))
8
+ for simplex in simp_tree.get_filtration():
9
+
10
+ if len(simplex[0]) == 2:
11
+
12
+ #if simp_tree.filtration([simplex[0][0]]) < threshold and simp_tree.filtration([simplex[0][1]]) < threshold:
13
+ matrix[simplex[0][0],simplex[0][1]] = simplex[1]
14
+
15
+ if matrix[simplex[0][0],simplex[0][1]] == 0:
16
+ matrix[simplex[0][0],simplex[0][1]] = matrix[simplex[0][0],simplex[0][1]]+.0001
17
+
18
+ matrix[simplex[0][1],simplex[0][0]] = matrix[simplex[0][0],simplex[0][1]]
19
+
20
+ return matrix
21
+
22
+ def union_find_dmat(dmat, edge_cut):
23
+
24
+ N = dmat.shape[0]
25
+
26
+ class Node:
27
+ def __init__ (self, loc, birth = 0):
28
+ self.loc = loc
29
+ self.parent = self.loc
30
+ self.birth = birth
31
+ def __str__(self):
32
+ return self.label
33
+ def __int__(self):
34
+ return self.loc
35
+
36
+ def Union(x, y, L):
37
+ xRoot = Find(x,L)
38
+ yRoot = Find(y,L)
39
+ if xRoot.loc != yRoot.loc:
40
+ if xRoot.birth > yRoot.birth:
41
+ xRoot.parent = yRoot.loc
42
+ return xRoot.loc # This should return the one that got killed
43
+ else:
44
+ yRoot.parent = xRoot.loc
45
+ return yRoot.loc # This should return the one that got killed
46
+
47
+ def Find(x,L):
48
+ if x.parent == x.loc:
49
+ return L[x.parent]
50
+ else:
51
+ return Find(L[x.parent], L)
52
+
53
+ # A list of allowed neighbors for each node
54
+ nb_dic = {}
55
+ for i in range(N):
56
+ tmp_list = []
57
+ for j in range(N):
58
+ if dmat[i][j] <= edge_cut and i!=j:
59
+ tmp_list.append(j)
60
+ nb_dic[i] = tmp_list
61
+
62
+ simplex_collection = []
63
+ simplex_index = {}
64
+ for i in range(N):
65
+ simplex_collection.append( ( [i], 0.0 ) )
66
+ for i in range(N-1):
67
+ for j in range(i+1, N):
68
+ if j in nb_dic[i]:
69
+ simplex_collection.append( ([i,j], dmat[i][j]))
70
+
71
+ filtration = []
72
+ tmp_list = []
73
+ for s in simplex_collection:
74
+ tmp_list.append(s[1])
75
+ simplex_order = np.argsort(tmp_list)
76
+ simplex_order[:N] = np.arange(N)[:]
77
+ cnt = 0; complex_filtration_detail = [];
78
+ for i in simplex_order:
79
+ if len(simplex_collection[i][0]) == 1:
80
+ complex_filtration_detail.append( [ (0,[]), simplex_collection[i][1], set(simplex_collection[i][0]) ] )
81
+ simplex_index[(i)] = cnt
82
+ cnt += 1
83
+ elif len(simplex_collection[i][0]) == 2:
84
+ n1 = simplex_collection[i][0][0];
85
+ n2 = simplex_collection[i][0][1];
86
+ complex_filtration_detail.append( [ (1, [n1,n2]), simplex_collection[i][1], set(simplex_collection[i][0]) ] )
87
+ simplex_index[(n1,n2)] = cnt
88
+ cnt += 1
89
+ persDgm_pairs = []
90
+ L = {0: Node(0, birth = 0)}
91
+ Cocycles = [[i] for i in range(N)]
92
+ Cocycle_fvalues = [[0.0] for i in range(N)]
93
+ for i in range(1, len(complex_filtration_detail)):
94
+ f = complex_filtration_detail[i]
95
+ if f[0][0] == 0:
96
+ L[i] = Node(i, birth=i)
97
+ else:
98
+ [n1,n2] = f[0][1]
99
+ killed = Union(L[n1], L[n2], L)
100
+ if n1 == killed:
101
+ lived = n2;
102
+ else:
103
+ lived = n1
104
+ if killed != None:
105
+ pair = (L[killed].birth, i)
106
+ persDgm_pairs.append(pair)
107
+ # print killed, n1, n2, dmat[n1,n2], L[killed].parent
108
+ Cocycles[L[killed].parent].extend(Cocycles[killed])
109
+ Cocycle_fvalues[L[killed].parent].extend([dmat[n1,n2] for i in range(len(Cocycles[killed]))])
110
+
111
+ setReps = [Find(L[v],L).loc for v in L.keys()]
112
+ persDgm = []
113
+ pair_births = []
114
+ for d in persDgm_pairs:
115
+ persDgm.append([complex_filtration_detail[d[0]][1], complex_filtration_detail[d[1]][1]])
116
+ pair_births.append(d[0])
117
+ unpaired = list(set(setReps))
118
+ for i in unpaired:
119
+ persDgm.append([complex_filtration_detail[i][1], np.inf])
120
+ pair_births.append(i)
121
+ pair_births = np.asarray(pair_births)
122
+ pair_births_index = np.argsort(pair_births)
123
+ diagram_0d = []
124
+ for i in pair_births_index:
125
+ d = persDgm[i]
126
+ diagram_0d.append([0,d[0],d[1]])
127
+ # print diagram_0d
128
+
129
+ # print Cocycles
130
+ # print Cocycle_fvalues
131
+ return [diagram_0d, Cocycles, Cocycle_fvalues]
132
+
133
+ def get_sub_features(cocycles,diagram_0d,feature,feature_list):
134
+
135
+ t,j = next(((set(cocycles[j]),j) for j in range(len(cocycles)) if set(cocycles[j])<feature),([],[]))
136
+ if t != []:
137
+ feature_list.append((feature-t,diagram_0d[j][2]))
138
+ #feature_list.append((t,diagram_0d[j][2]))
139
+ #cocycles.remove(cocycles[j])
140
+ feature_list = get_sub_features(cocycles,diagram_0d,feature-t,feature_list)
141
+ return feature_list
@@ -0,0 +1,41 @@
1
+ import numpy as np
2
+ import scanpy as sc
3
+ from sklearn.metrics import adjusted_rand_score,adjusted_mutual_info_score
4
+
5
+ def leiden(adata,res=np.linspace(start=0.2,stop=1.2,num=6),show=True,scores=False,embedding='X_gst',res_keys=[],ground_truth='cluster'):
6
+ if not res_keys:
7
+ res_keys = ['leiden_'+str(r) for r in res]
8
+ sc.pp.neighbors(adata,use_rep=embedding,n_neighbors=5)
9
+
10
+ sc.tl.umap(adata)
11
+ for i in range(len(res)):
12
+ sc.tl.leiden(
13
+ adata,
14
+ key_added=res_keys[i],
15
+ resolution=res[i],
16
+ n_iterations=2,
17
+ directed=False,
18
+ )
19
+ if show:
20
+ sc.pl.umap(adata, color=res_keys[i])
21
+ sc.pl.embedding(adata, basis="spatial", color=res_keys[i])
22
+ if scores:
23
+ print('Resolution, adjusted mutual info, adjusted rand:')
24
+ print(cluster_metrics(adata.obs[ground_truth],adata.obs[res_keys[i]]))
25
+
26
+ return adata
27
+
28
+ def cluster_metrics(cluster,r,truth=None):
29
+ clusters = set(int(j) for j in cluster.cat.categories.tolist())
30
+ if not(truth is None):
31
+ l1 = np.zeros(len(truth))
32
+ l2 = np.zeros(len(truth))
33
+ for i in range(0,len(truth.cat.categories.tolist())):
34
+ guy = list(n for n in range(0,len(truth)) if truth.iloc[n]==truth.cat.categories.tolist()[i])
35
+ l1[guy] = i
36
+ for i in range(0,len(cluster.cat.categories.tolist())):
37
+ guy = list(n for n in range(0,len(cluster)) if cluster.iloc[n]==cluster.cat.categories.tolist()[i])
38
+ l2[guy] = i
39
+ return r,len(clusters),adjusted_mutual_info_score(l1,l2),adjusted_rand_score(l1,l2)
40
+ else:
41
+ return r,len(clusters)
@@ -0,0 +1,8 @@
1
+
2
+ from ._phdms import map_multiscale
3
+ from ._phdms import preprocess_leiden
4
+ from ._phdms import cluster_filtration
5
+ from ._phdms import ground_truth_benchmark
6
+ from ._phdms import plot_multiscale
7
+ from ._phdms import construct_clustering
8
+ from ._phdms import point_click_multiscale
@@ -0,0 +1,293 @@
1
+
2
+
3
+ import numpy as np
4
+ import scanpy as sc
5
+ import gudhi as gd
6
+ from .._utils import leiden, filt_to_matrix, union_find_dmat, get_sub_features, clusters_to_distribution
7
+ import matplotlib.pyplot as plt
8
+ from scipy.special import logit
9
+ import ot
10
+ import pandas as pd
11
+
12
+ def preprocess_leiden(input_file,output_file='',emb='X_gst',resolution=np.linspace(start=0.05,stop=.95,num=10),res_keys=[],ground_truth='cluster'):
13
+
14
+ adata = sc.read_h5ad(input_file+'.h5ad')
15
+ leiden(adata,res=resolution,show=False,embedding=emb,res_keys=res_keys,ground_truth=ground_truth)
16
+ adata.write_h5ad(output_file+'.h5ad')
17
+
18
+ def cluster_filtration(adata,res_keys,index='containment',order=[]):
19
+
20
+ num_cells = adata.obsm['spatial'].shape[0]
21
+
22
+ #Reorder resolutions if order is given
23
+ if order:
24
+ res_keys = [res_keys[i] for i in reversed(order)]
25
+
26
+ #Create the filtration
27
+ leiden_complex = gd.SimplexTree()
28
+
29
+ #List of number of clusters at each resolution
30
+ num_clusters = []
31
+ #List of sets of indices for each cluster at each resolution
32
+ clusters = []
33
+
34
+ #We want to iterate through pairs of neighboring resolutions
35
+ for i in range(0,len(res_keys)-1):
36
+
37
+ #Initialize the pair of resolutions we look at
38
+ fine = adata.obs[res_keys[i]]
39
+ coarse = adata.obs[res_keys[i+1]]
40
+ fine_clusters = [int(j) for j in fine.cat.categories.tolist()]
41
+ coarse_clusters = [int(j) for j in coarse.cat.categories.tolist()]
42
+
43
+ #Update number of clusters and clusters
44
+ #We want to add this information only on the first iteration
45
+ if i == 0:
46
+ num_clusters.append(len(fine_clusters))
47
+ for j in fine_clusters:
48
+ fine_j = set(n for n in range(0,len(fine)) if int(fine.iloc[n])==j)
49
+ clusters.append(fine_j)
50
+
51
+ for k in coarse_clusters:
52
+ coarse_k = set(n for n in range(0,len(coarse)) if int(coarse.iloc[n])==k)
53
+ clusters.append(coarse_k)
54
+ num_clusters.append(len(coarse_clusters))
55
+
56
+ #Now, we compare every cluster in the fine resolution with every cluster in the coarse resolution
57
+ for j in fine_clusters:
58
+
59
+ #The j-th cluster in the fine resolution
60
+ fine_j = set(n for n in range(0,len(fine)) if int(fine.iloc[n])==j)
61
+
62
+ for k in coarse_clusters:
63
+ #The k-th cluster in the coarse resolution
64
+ coarse_k = set(n for n in range(0,len(coarse)) if int(coarse.iloc[n])==k)
65
+
66
+ #Compute union and intersection of the fine and coarse clusters
67
+ intersection = fine_j.intersection(coarse_k)
68
+ union = fine_j.union(coarse_k)
69
+
70
+ #If they intersect, we compute a filtration value
71
+ if intersection:
72
+ #Compute filtration value as either containment or Jaccard index
73
+ if index == 'containment':
74
+ filt = 1 - len(intersection)/len(fine_j)
75
+ elif index == 'jaccard':
76
+ filt = 1 - len(intersection)/len(union)
77
+
78
+ #Add edge between clusters with this filtration value
79
+ leiden_complex.insert([sum(num_clusters[:-2])+j,sum(num_clusters[0:-1])+k],filt)
80
+
81
+ #Make sure that all the clusters have filtration value 0
82
+ for i in range(0,sum(num_clusters)):
83
+ leiden_complex.assign_filtration([i],0)
84
+ return leiden_complex,clusters
85
+
86
+ def map_multiscale(spatial,cluster_complex,clusterings,num_domains=0,filt=0,plots="on",order='persistence',redundant_filter=False):
87
+
88
+ dmat = filt_to_matrix(cluster_complex)
89
+ diagram_0d,cocycles,_= union_find_dmat(dmat,edge_cut=1)
90
+ if num_domains == 0:
91
+ num_domains = len(cocycles)
92
+
93
+ #Sort persistent homology results by death time
94
+ diagram_0d,cocycles = zip(*sorted(zip(diagram_0d,cocycles),key=lambda x: x[0][2],reverse=True))
95
+ #Filter out non-persistent results if desired.
96
+ if filt > 0:
97
+ index = next(i for i in range(len(diagram_0d)) if diagram_0d[i][2]<filt)
98
+ diagram_0d = diagram_0d[:index]
99
+ cocycles = list(cocycles[:index])
100
+ domains = []
101
+
102
+ #Iterate through persistent components
103
+ for n in range(len(cocycles)+1):
104
+ if n == 0:
105
+ feature_list = [(set(cocycles[n]),1)]
106
+ elif n == len(cocycles):
107
+ break
108
+ else:
109
+ feature_list = [(set(cocycles[n]),diagram_0d[n][2])]
110
+ #find all the clusters that belong to the multiscale domain
111
+ feature_list = get_sub_features(cocycles,diagram_0d,feature_list[0][0],feature_list)
112
+
113
+
114
+ x = spatial[:,0]
115
+ y = spatial[:,1]
116
+ coreness = np.ones(len(x))
117
+ #Compute coreness score for each point in the tissue
118
+ #iterate backwards through features to find filtration value where point first appears in multiscale domain
119
+ feature_list = sorted(feature_list,key= lambda x: x[1],reverse=True)
120
+ tracker = set()
121
+ for i in range(len(feature_list)-1, -1, -1):
122
+
123
+ spots = set()
124
+ for clust in feature_list[i][0]:
125
+ spots = set.union(spots,clusterings[clust])
126
+
127
+ coreness[list(spots-tracker)] = feature_list[i][1]
128
+ tracker = set.union(tracker,spots)
129
+
130
+ nontrivial = True
131
+ #Filter out results which share too similar proportions to previously examined domains
132
+ if redundant_filter:
133
+ for guy in domains:
134
+ zz = set(i for i in range(len(coreness)) if guy[i] < 1)
135
+ gg = set(i for i in range(len(coreness)) if coreness[i] < 1)
136
+ if len(gg.intersection(zz)) > redundant_filter*len(gg.union(zz)):
137
+ nontrivial = False
138
+ break
139
+ if nontrivial:
140
+ domains.append(coreness)
141
+
142
+ #Default order is by death time
143
+ #Ordered by size of domain if specified
144
+ if order == 'size':
145
+ domains = sorted(domains,key = lambda x : sum(x))
146
+ elif order == 'persistence':
147
+ domains = domains
148
+
149
+ if len(domains) > num_domains:
150
+ domains = domains[:num_domains]
151
+
152
+ #Normalize the coreness values to be between 0 and 1
153
+ for i in range(len(domains)):
154
+ max = np.max(list(z for z in domains[i]))
155
+ min = np.min(list(z for z in domains[i]))
156
+ z = list(1-(domains[i][j]-min)/(max-min) for j in range(len(domains[i])))
157
+
158
+ domains[i] = np.array(z)
159
+
160
+ if plots == 'on':
161
+ for d in domains:
162
+ plot_multiscale(d,spatial)
163
+ plt.show()
164
+
165
+ return np.array(domains).transpose()
166
+
167
+
168
+ #Currently broken
169
+ def point_click_multiscale(spatial,cluster_complex,clusterings,filt=0,order='persistence',redundant_filter=True):
170
+ from mpl_point_clicker import clicker
171
+
172
+ domains = map_multiscale(spatial,cluster_complex,clusterings,num_domains=0,filt=filt,plots='off',order=order,redundant_filter=redundant_filter)
173
+ tracker = []
174
+ exit = False
175
+ while not exit:
176
+
177
+ plt.figure(figsize=[15,15])
178
+ plt.rcParams.update({'font.size': 25})
179
+ ax = plt.gca()
180
+ ax.scatter(spatial[:,0],spatial[:,1],c='k')
181
+ ax.set_title('Click once to specify spot. Close to visualize domains')
182
+ ax.axes.xaxis.set_ticklabels([])
183
+ ax.axes.yaxis.set_ticklabels([])
184
+ klicker = clicker(ax, ["spot"], markers=["*"],markersize=20,colors=['red'])
185
+
186
+
187
+ plt.tight_layout()
188
+ plt.show()
189
+ xy = klicker.get_positions()['spot']
190
+
191
+ input_ind = np.argmin((spatial[:,0]-xy[-1][0])**2+(spatial[:,1]-xy[-1][1])**2)
192
+ #print(input_ind)
193
+ input_cluster = list(i for i in range(len(clusterings)) if input_ind in clusterings[i])
194
+ #print(input_cluster)
195
+ x = spatial[:,0]
196
+ y = spatial[:,1]
197
+
198
+ for d in domains.transpose():
199
+ if d[input_ind] > .95:
200
+ tracker.append(d)
201
+ plot_multiscale(d,spatial,marker=xy[-1])
202
+ plt.show()
203
+
204
+ exit = input('Press E to exit.')
205
+ if exit == 'E':
206
+ return tracker
207
+
208
+
209
+ def ground_truth_benchmark(ground_truth,multiscale,spatial,plots=False,conversion_factor=1):
210
+
211
+ ground_truth_distributions,gmat = clusters_to_distribution(ground_truth)
212
+ multiscale_distributions,mmat = clusters_to_distribution(multiscale)
213
+
214
+ dmat = ot.dist(spatial*conversion_factor,spatial*conversion_factor,metric='euclidean')
215
+ ma = np.max(dmat)
216
+ M = dmat/ma
217
+ optimal_costs = []
218
+ print('Wasserstein distance, index of optimal multiscale domain:')
219
+ for g in ground_truth_distributions:
220
+ g_cost = []
221
+
222
+ for m in multiscale_distributions:
223
+
224
+ d = ot.emd2(g,m,M)
225
+ g_cost.append(d)
226
+ optimal_costs.append((min(g_cost)*ma,np.argmin(g_cost)))
227
+ print((min(g_cost)*ma,int(np.argmin(g_cost))))
228
+
229
+ if plots:
230
+ for j in range(len(optimal_costs)):
231
+ plot_multiscale(gmat[:,j],spatial,title='Ground truth domain '+str(j))
232
+ plot_multiscale(mmat[:,optimal_costs[j][1]],spatial,title='Best match '+str(j))
233
+ plt.show()
234
+ return optimal_costs,ma
235
+
236
+ def construct_clustering(adata,domains):
237
+ category = np.zeros(adata.shape[0])
238
+ spatial = adata.obsm['spatial']
239
+ #Identify cell spots by the domain they most belong to.
240
+ for n in range(len(category)):
241
+ #Find the max scoring domain
242
+ arg_max = int(np.argmax(list(adata.obsm['multiscale'][n,domain] for domain in domains)))
243
+ #Exclude cells which don't belong to any domain
244
+ max_score = np.max(list(adata.obsm['multiscale'][n,domain] for domain in domains))
245
+ if max_score > 0.05:
246
+ category[n] = str(arg_max+1)
247
+ else:
248
+ category[n] = str(len(domains)+2)
249
+
250
+ # We want to get rid of unassigned spots
251
+ unassigned = list(n for n in range(len(category)) if category[n] == str(len(domains)+2))
252
+ new_spatial = spatial.copy()
253
+ new_spatial[unassigned,0] = 10**10
254
+ new_spatial[unassigned,1] = 10**10
255
+ for n in unassigned:
256
+ nearest_spot = np.argmin((new_spatial[:,0]-spatial[n,0])**2+(new_spatial[:,1]-spatial[n,1])**2)
257
+ category[n] = category[nearest_spot]
258
+
259
+
260
+ plt.figure()
261
+ df = pd.DataFrame({"x":np.array(adata.obsm['spatial'][:,0]).flatten(),
262
+ "y":np.array(adata.obsm['spatial'][:,1]).flatten(),
263
+ "colors":np.array(category).flatten()})
264
+ cmap = plt.cm.Set1
265
+ norm = plt.Normalize(df['colors'].values.min(), df['colors'].values.max())
266
+ for i, dff in df.groupby("colors"):
267
+ plt.scatter(dff['x'], dff['y'], c=cmap(norm(dff['colors'])),
268
+ edgecolors='none', label="Feature {:g}".format(i))
269
+
270
+ plt.legend()
271
+ plt.show()
272
+ category = list(str(int(n)) for n in category)
273
+ #print(category)
274
+ return pd.Categorical(category,categories=list(str(i) for i in range(1,len(domains)+1)))
275
+
276
+ def plot_multiscale(multiscale,spatial,title='',marker=np.array([False])):
277
+ x = spatial[:,0]
278
+ y = spatial[:,1]
279
+ z = multiscale.copy()
280
+ z[z < .00002] = .00002
281
+ z[z > 1-.00002] = 1-.00002
282
+
283
+ plt.figure(figsize=(8, 20/3))
284
+ plt.title(title)
285
+ plt.scatter(x,y,c=logit(z),cmap='magma',s=30,edgecolors='k',linewidths=.5)
286
+ if np.any(marker):
287
+ plt.scatter(marker[0],marker[1],c='r',s=40,edgecolors='k',linewidths=.5,marker='*')
288
+ cbar = plt.colorbar()
289
+ cbar.ax.set_ylabel('logit(coreness)')
290
+ frame1 = plt.gca()
291
+ frame1.axes.xaxis.set_ticklabels([])
292
+ frame1.axes.yaxis.set_ticklabels([])
293
+
@@ -0,0 +1,34 @@
1
+ Metadata-Version: 2.4
2
+ Name: phd-ms
3
+ Version: 1.0
4
+ Summary: Multiscale domain identification for spatial transcriptomic data.
5
+ Author: Perry Beamer
6
+ Author-email: perry.beamer@gmail.com
7
+ License: MIT License
8
+ Keywords: spatial transcriptomics,topological data analysis,multiscale domains
9
+ Classifier: Programming Language :: Python :: 3.8
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Operating System :: MacOS
14
+ Classifier: Topic :: Scientific/Engineering
15
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: gudhi>=3.8.0
19
+ Requires-Dist: leidenalg>=0.9.0
20
+ Requires-Dist: matplotlib==3.8.2
21
+ Requires-Dist: numpy>=1.23.4
22
+ Requires-Dist: pandas>=2.2.0
23
+ Requires-Dist: plotly>=5.3.1
24
+ Requires-Dist: pot>=0.8.0
25
+ Requires-Dist: scanpy==1.9.6
26
+ Requires-Dist: scipy>=1.9.1
27
+ Requires-Dist: scikit-learn>=1.5.1
28
+ Requires-Dist: importlib-metadata
29
+ Dynamic: author
30
+ Dynamic: author-email
31
+ Dynamic: summary
32
+
33
+ # phd-ms00.03
34
+
@@ -0,0 +1,16 @@
1
+ README.md
2
+ setup.cfg
3
+ setup.py
4
+ phd_ms/__init__.py
5
+ phd_ms.egg-info/PKG-INFO
6
+ phd_ms.egg-info/SOURCES.txt
7
+ phd_ms.egg-info/dependency_links.txt
8
+ phd_ms.egg-info/not-zip-safe
9
+ phd_ms.egg-info/requires.txt
10
+ phd_ms.egg-info/top_level.txt
11
+ phd_ms/_utils/__init__.py
12
+ phd_ms/_utils/_benchmark.py
13
+ phd_ms/_utils/_filtration.py
14
+ phd_ms/_utils/_leiden.py
15
+ phd_ms/tools/__init__.py
16
+ phd_ms/tools/_phdms.py
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,11 @@
1
+ gudhi>=3.8.0
2
+ leidenalg>=0.9.0
3
+ matplotlib==3.8.2
4
+ numpy>=1.23.4
5
+ pandas>=2.2.0
6
+ plotly>=5.3.1
7
+ pot>=0.8.0
8
+ scanpy==1.9.6
9
+ scipy>=1.9.1
10
+ scikit-learn>=1.5.1
11
+ importlib-metadata
@@ -0,0 +1 @@
1
+ phd_ms
phd_ms-1.0/setup.cfg ADDED
@@ -0,0 +1,45 @@
1
+ [metadata]
2
+ name = phd-ms
3
+ version = 0.1
4
+ author = Perry Beamer
5
+ author_email = perry.beamer@gmail.com
6
+ description = Multiscale domain identification for spatial transcriptomic data using persistent homology
7
+ long_description_content_type = text/markdown
8
+ long_description = file: README.md
9
+ license = MIT License
10
+ classifiers =
11
+ Programming Language :: Python :: 3.8
12
+ Programming Language :: Python :: 3.9
13
+ License :: OSI Approved :: MIT License
14
+ Operating System :: POSIX :: Linux
15
+ Operating System :: MacOS
16
+ Topic :: Scientific/Engineering
17
+ Topic :: Scientific/Engineering :: Bio-Informatics
18
+ keywords = spatial transcriptomics, topological data analysis, multiscale domains
19
+
20
+ [options]
21
+ zip_safe = False
22
+ include_package_data = True
23
+ packages = find:
24
+ install_requires =
25
+ gudhi>=3.8.0
26
+ leidenalg>=0.9.0
27
+ matplotlib==3.8.2
28
+ numpy>=1.23.4
29
+ pandas>=2.2.0
30
+ plotly>=5.3.1
31
+ pot>=0.8.0
32
+ scanpy==1.9.6
33
+ scipy>=1.9.1
34
+ scikit-learn>=1.5.1
35
+ importlib-metadata
36
+ python_requires = >=3.9
37
+
38
+ [options.packages.find]
39
+ exclude =
40
+ tutorials*
41
+
42
+ [egg_info]
43
+ tag_build =
44
+ tag_date = 0
45
+
phd_ms-1.0/setup.py ADDED
@@ -0,0 +1,10 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name='phd-ms',
5
+ version='1.0',
6
+ packages=find_packages(exclude=['tests*']),
7
+ description='Multiscale domain identification for spatial transcriptomic data.',
8
+ author='Perry Beamer',
9
+ author_email='perry.beamer@gmail.com'
10
+ )