tangle-cnn 0.98.0__py3-none-any.whl

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.
Files changed (39) hide show
  1. tangle_cnn/__init__.py +0 -0
  2. tangle_cnn/__main__.py +4 -0
  3. tangle_cnn/blockface_to_multichannel.py +105 -0
  4. tangle_cnn/deepcluster/__init__.py +0 -0
  5. tangle_cnn/deepcluster/clustering.py +378 -0
  6. tangle_cnn/deepcluster/eval_linear.py +319 -0
  7. tangle_cnn/deepcluster/eval_retrieval.py +481 -0
  8. tangle_cnn/deepcluster/eval_voc_classif.py +293 -0
  9. tangle_cnn/deepcluster/main.py +327 -0
  10. tangle_cnn/deepcluster/models/__init__.py +8 -0
  11. tangle_cnn/deepcluster/models/alexnet.py +98 -0
  12. tangle_cnn/deepcluster/models/vgg16.py +94 -0
  13. tangle_cnn/deepcluster/util.py +132 -0
  14. tangle_cnn/deepcluster/visu/activ-retrieval.py +114 -0
  15. tangle_cnn/deepcluster/visu/gradient_ascent.py +139 -0
  16. tangle_cnn/levels.py +5 -0
  17. tangle_cnn/nissl_to_multichannel.py +127 -0
  18. tangle_cnn/osl_worker.py +110 -0
  19. tangle_cnn/unet_wildcat.py +206 -0
  20. tangle_cnn/unet_wildcat_gmm.py +635 -0
  21. tangle_cnn/wildcat_main.py +1066 -0
  22. tangle_cnn/wildcat_pytorch/picsl_wildcat/gmm.py +737 -0
  23. tangle_cnn/wildcat_pytorch/picsl_wildcat/losses.py +140 -0
  24. tangle_cnn/wildcat_pytorch/picsl_wildcat/models.py +245 -0
  25. tangle_cnn/wildcat_pytorch/picsl_wildcat/util.py +170 -0
  26. tangle_cnn/wildcat_pytorch/wildcat/__init__.py +0 -0
  27. tangle_cnn/wildcat_pytorch/wildcat/demo_mit67.py +80 -0
  28. tangle_cnn/wildcat_pytorch/wildcat/demo_voc2007.py +80 -0
  29. tangle_cnn/wildcat_pytorch/wildcat/engine.py +510 -0
  30. tangle_cnn/wildcat_pytorch/wildcat/mit67.py +167 -0
  31. tangle_cnn/wildcat_pytorch/wildcat/models.py +61 -0
  32. tangle_cnn/wildcat_pytorch/wildcat/pooling.py +139 -0
  33. tangle_cnn/wildcat_pytorch/wildcat/util.py +176 -0
  34. tangle_cnn/wildcat_pytorch/wildcat/voc.py +259 -0
  35. tangle_cnn-0.98.0.dist-info/METADATA +219 -0
  36. tangle_cnn-0.98.0.dist-info/RECORD +39 -0
  37. tangle_cnn-0.98.0.dist-info/WHEEL +5 -0
  38. tangle_cnn-0.98.0.dist-info/licenses/LICENSE.txt +674 -0
  39. tangle_cnn-0.98.0.dist-info/top_level.txt +1 -0
tangle_cnn/__init__.py ADDED
File without changes
tangle_cnn/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .wildcat_main import parser
2
+
3
+ args = parser.parse_args()
4
+ args.func(args)
@@ -0,0 +1,105 @@
1
+ # Set up
2
+ import torchvision.transforms as transforms
3
+ import torch.utils.data
4
+ import numpy as np
5
+ import argparse
6
+ import SimpleITK as sitk
7
+ from PIL import Image
8
+ import sys
9
+
10
+ sys.path.append("deepcluster")
11
+ from deepcluster.util import load_model
12
+
13
+
14
+ # Process a slide
15
+ def apply_to_slide(args):
16
+
17
+ # Read the trained model
18
+ model = load_model(args.network)
19
+ model.cuda()
20
+ model.eval()
21
+
22
+ # Read the slide
23
+ I = Image.open(args.slide)
24
+ h,w=I.size
25
+
26
+ # Split the transforms
27
+ tran_tt = transforms.ToTensor()
28
+ tran_model=transforms.Compose([
29
+ transforms.ToPILImage(),
30
+ transforms.Resize(256),
31
+ transforms.CenterCrop(224),
32
+ transforms.ToTensor(),
33
+ transforms.Normalize(mean=[0.485, 0.456, 0.406],
34
+ std=[0.229, 0.224, 0.225])])
35
+
36
+ # Get the patch size and downsample factor
37
+ patch_size, ds = int(args.patch), int(args.downsample)
38
+
39
+ # Image as tensor
40
+ I_crop=I.crop((0, 0, ds*int(h/ds), ds*int(w/ds)))
41
+ IT = tran_tt(I_crop)
42
+
43
+ # Image unfolded into patches
44
+ IT_unfold = IT.unfold(1, patch_size, ds).unfold(2, patch_size, ds).permute(1, 2, 0, 3, 4)
45
+
46
+ # Output dimensions (before padding)
47
+ (ow, oh) = IT_unfold.shape[0], IT_unfold.shape[1]
48
+
49
+ # Flat array of inputs
50
+ batches = IT_unfold.reshape(-1, 3, patch_size, patch_size)
51
+
52
+ # Break into digestable batches
53
+ bs = int(args.batch_size)
54
+ for k in range(0, batches.shape[0], bs):
55
+ k_end = min(k + bs, batches.shape[0])
56
+ batch=torch.zeros((k_end-k,3,224,224))
57
+ for j in range(k,k_end):
58
+ batch[j-k,:,:,:]=tran_model(batches[j,:,:,:])
59
+ with torch.no_grad():
60
+ res_batch=model(batch.cuda()).detach().cpu()
61
+ if k == 0:
62
+ result = res_batch
63
+ else:
64
+ result = torch.cat((result, res_batch), 0)
65
+ print('Batch %d of %d' % (k / bs, batches.shape[0] / bs))
66
+
67
+ # Reformat into a 20xWxH image
68
+ Z = result.permute(1, 0).reshape(-1, ow, oh)
69
+
70
+ # Pad the result to desired size
71
+ owp, ohp = int(w * 1.0 / ds + 0.5), int(h * 1.0 / ds + 0.5)
72
+ pw0, ph0 = int((owp - ow) / 2), int((ohp - oh) / 2)
73
+ pw1, ph1 = owp - ow - pw0, ohp - oh - ph0
74
+ Z = torch.nn.functional.pad(Z, (ph0, ph1, pw0, pw1, 0, 0), 'constant', 0)
75
+
76
+ # Write the result as a NIFTI file
77
+ nii = sitk.GetImageFromArray(Z.permute(1,2,0), True)
78
+ nii.SetSpacing((ds, ds))
79
+ sitk.WriteImage(nii, args.output)
80
+
81
+ # Write the optional thumb
82
+ if args.thumb is not None:
83
+ rgb = np.asarray(I.resize((ohp,owp),Image.LANCZOS))
84
+ nii = sitk.GetImageFromArray(rgb, True)
85
+ nii.SetSpacing((ds, ds))
86
+ sitk.WriteImage(nii, args.thumb)
87
+
88
+
89
+ # Set up an argument parser
90
+ parser = argparse.ArgumentParser()
91
+ subparsers = parser.add_subparsers()
92
+
93
+ apply_parser = subparsers.add_parser('apply')
94
+ apply_parser.add_argument('--slide', help='Input histology slide to process')
95
+ apply_parser.add_argument('--output', help='Where to store the output density map')
96
+ apply_parser.add_argument('--thumb', help='Optional thumbnail output', default=None)
97
+ apply_parser.add_argument('--network', help='Network saved during training')
98
+ apply_parser.add_argument('--patch', help='Patch size used during training', default=64)
99
+ apply_parser.add_argument('--downsample', help='Input to output downsampling ratio', default=16)
100
+ apply_parser.add_argument('--batch-size', help='GPU batch size', default=256)
101
+ apply_parser.set_defaults(func=apply_to_slide)
102
+
103
+ if __name__ == '__main__':
104
+ args = parser.parse_args()
105
+ args.func(args)
File without changes
@@ -0,0 +1,378 @@
1
+ # Copyright (c) 2017-present, Facebook, Inc.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+ import time
8
+
9
+ import faiss
10
+ import numpy as np
11
+ from PIL import Image
12
+ from PIL import ImageFile
13
+ from scipy.sparse import csr_matrix, find
14
+ import torch
15
+ import torch.utils.data as data
16
+ import torchvision.transforms as transforms
17
+
18
+ ImageFile.LOAD_TRUNCATED_IMAGES = True
19
+
20
+ __all__ = ['PIC', 'Kmeans', 'cluster_assign', 'arrange_clustering']
21
+
22
+
23
+ def pil_loader(path):
24
+ """Loads an image.
25
+ Args:
26
+ path (string): path to image file
27
+ Returns:
28
+ Image
29
+ """
30
+ with open(path, 'rb') as f:
31
+ img = Image.open(f)
32
+ return img.convert('RGB')
33
+
34
+
35
+ class ReassignedDataset(data.Dataset):
36
+ """A dataset where the new images labels are given in argument.
37
+ Args:
38
+ image_indexes (list): list of data indexes
39
+ pseudolabels (list): list of labels for each data
40
+ dataset (list): list of tuples with paths to images
41
+ transform (callable, optional): a function/transform that takes in
42
+ an PIL image and returns a
43
+ transformed version
44
+ """
45
+
46
+ def __init__(self, image_indexes, pseudolabels, dataset, transform=None):
47
+ self.imgs = self.make_dataset(image_indexes, pseudolabels, dataset)
48
+ self.transform = transform
49
+
50
+ def make_dataset(self, image_indexes, pseudolabels, dataset):
51
+ label_to_idx = {label: idx for idx, label in enumerate(set(pseudolabels))}
52
+ images = []
53
+ for j, idx in enumerate(image_indexes):
54
+ path = dataset[idx][0]
55
+ pseudolabel = label_to_idx[pseudolabels[j]]
56
+ images.append((path, pseudolabel))
57
+ return images
58
+
59
+ def __getitem__(self, index):
60
+ """
61
+ Args:
62
+ index (int): index of data
63
+ Returns:
64
+ tuple: (image, pseudolabel) where pseudolabel is the cluster of index datapoint
65
+ """
66
+ path, pseudolabel = self.imgs[index]
67
+ img = pil_loader(path)
68
+ if self.transform is not None:
69
+ img = self.transform(img)
70
+ return img, pseudolabel
71
+
72
+ def __len__(self):
73
+ return len(self.imgs)
74
+
75
+
76
+ def preprocess_features(npdata, pca=256):
77
+ """Preprocess an array of features.
78
+ Args:
79
+ npdata (np.array N * ndim): features to preprocess
80
+ pca (int): dim of output
81
+ Returns:
82
+ np.array of dim N * pca: data PCA-reduced, whitened and L2-normalized
83
+ """
84
+ _, ndim = npdata.shape
85
+ npdata = npdata.astype('float32')
86
+
87
+ # Apply PCA-whitening with Faiss
88
+ mat = faiss.PCAMatrix (ndim, pca, eigen_power=-0.5)
89
+ mat.train(npdata)
90
+ assert mat.is_trained
91
+ npdata = mat.apply_py(npdata)
92
+
93
+ # L2 normalization
94
+ row_sums = np.linalg.norm(npdata, axis=1)
95
+ npdata = npdata / row_sums[:, np.newaxis]
96
+
97
+ return npdata
98
+
99
+
100
+ def make_graph(xb, nnn):
101
+ """Builds a graph of nearest neighbors.
102
+ Args:
103
+ xb (np.array): data
104
+ nnn (int): number of nearest neighbors
105
+ Returns:
106
+ list: for each data the list of ids to its nnn nearest neighbors
107
+ list: for each data the list of distances to its nnn NN
108
+ """
109
+ N, dim = xb.shape
110
+
111
+ # we need only a StandardGpuResources per GPU
112
+ res = faiss.StandardGpuResources()
113
+
114
+ # L2
115
+ flat_config = faiss.GpuIndexFlatConfig()
116
+ flat_config.device = int(torch.cuda.device_count()) - 1
117
+ index = faiss.GpuIndexFlatL2(res, dim, flat_config)
118
+ index.add(xb)
119
+ D, I = index.search(xb, nnn + 1)
120
+ return I, D
121
+
122
+
123
+ def cluster_assign(images_lists, dataset):
124
+ """Creates a dataset from clustering, with clusters as labels.
125
+ Args:
126
+ images_lists (list of list): for each cluster, the list of image indexes
127
+ belonging to this cluster
128
+ dataset (list): initial dataset
129
+ Returns:
130
+ ReassignedDataset(torch.utils.data.Dataset): a dataset with clusters as
131
+ labels
132
+ """
133
+ assert images_lists is not None
134
+ pseudolabels = []
135
+ image_indexes = []
136
+ for cluster, images in enumerate(images_lists):
137
+ image_indexes.extend(images)
138
+ pseudolabels.extend([cluster] * len(images))
139
+
140
+ normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406],
141
+ std=[0.229, 0.224, 0.225])
142
+ t = transforms.Compose([transforms.RandomResizedCrop(224),
143
+ transforms.RandomHorizontalFlip(),
144
+ transforms.ToTensor(),
145
+ normalize])
146
+
147
+ return ReassignedDataset(image_indexes, pseudolabels, dataset, t)
148
+
149
+
150
+ def run_kmeans(x, nmb_clusters, verbose=False):
151
+ """Runs kmeans on 1 GPU.
152
+ Args:
153
+ x: data
154
+ nmb_clusters (int): number of clusters
155
+ Returns:
156
+ list: ids of data in each cluster
157
+ """
158
+ n_data, d = x.shape
159
+
160
+ # faiss implementation of k-means
161
+ clus = faiss.Clustering(d, nmb_clusters)
162
+
163
+ # Change faiss seed at each k-means so that the randomly picked
164
+ # initialization centroids do not correspond to the same feature ids
165
+ # from an epoch to another.
166
+ clus.seed = np.random.randint(1234)
167
+
168
+ clus.niter = 20
169
+ clus.max_points_per_centroid = 10000000
170
+ res = faiss.StandardGpuResources()
171
+ flat_config = faiss.GpuIndexFlatConfig()
172
+ flat_config.useFloat16 = False
173
+ flat_config.device = 0
174
+ index = faiss.GpuIndexFlatL2(res, d, flat_config)
175
+
176
+ # perform the training
177
+ clus.train(x, index)
178
+ _, I = index.search(x, 1)
179
+ losses = faiss.vector_to_array(clus.obj)
180
+ if verbose:
181
+ print('k-means loss evolution: {0}'.format(losses))
182
+
183
+ return [int(n[0]) for n in I], losses[-1]
184
+
185
+
186
+ def arrange_clustering(images_lists):
187
+ pseudolabels = []
188
+ image_indexes = []
189
+ for cluster, images in enumerate(images_lists):
190
+ image_indexes.extend(images)
191
+ pseudolabels.extend([cluster] * len(images))
192
+ indexes = np.argsort(image_indexes)
193
+ return np.asarray(pseudolabels)[indexes]
194
+
195
+
196
+ class Kmeans(object):
197
+ def __init__(self, k):
198
+ self.k = k
199
+
200
+ def cluster(self, data, verbose=False):
201
+ """Performs k-means clustering.
202
+ Args:
203
+ x_data (np.array N * dim): data to cluster
204
+ """
205
+ end = time.time()
206
+
207
+ # PCA-reducing, whitening and L2-normalization
208
+ xb = preprocess_features(data)
209
+
210
+ # cluster the data
211
+ I, loss = run_kmeans(xb, self.k, verbose)
212
+ self.images_lists = [[] for i in range(self.k)]
213
+ for i in range(len(data)):
214
+ self.images_lists[I[i]].append(i)
215
+
216
+ if verbose:
217
+ print('k-means time: {0:.0f} s'.format(time.time() - end))
218
+
219
+ return loss
220
+
221
+
222
+ def make_adjacencyW(I, D, sigma):
223
+ """Create adjacency matrix with a Gaussian kernel.
224
+ Args:
225
+ I (numpy array): for each vertex the ids to its nnn linked vertices
226
+ + first column of identity.
227
+ D (numpy array): for each data the l2 distances to its nnn linked vertices
228
+ + first column of zeros.
229
+ sigma (float): Bandwidth of the Gaussian kernel.
230
+
231
+ Returns:
232
+ csr_matrix: affinity matrix of the graph.
233
+ """
234
+ V, k = I.shape
235
+ k = k - 1
236
+ indices = np.reshape(np.delete(I, 0, 1), (1, -1))
237
+ indptr = np.multiply(k, np.arange(V + 1))
238
+
239
+ def exp_ker(d):
240
+ return np.exp(-d / sigma**2)
241
+
242
+ exp_ker = np.vectorize(exp_ker)
243
+ res_D = exp_ker(D)
244
+ data = np.reshape(np.delete(res_D, 0, 1), (1, -1))
245
+ adj_matrix = csr_matrix((data[0], indices[0], indptr), shape=(V, V))
246
+ return adj_matrix
247
+
248
+
249
+ def run_pic(I, D, sigma, alpha):
250
+ """Run PIC algorithm"""
251
+ a = make_adjacencyW(I, D, sigma)
252
+ graph = a + a.transpose()
253
+ cgraph = graph
254
+ nim = graph.shape[0]
255
+
256
+ W = graph
257
+ t0 = time.time()
258
+
259
+ v0 = np.ones(nim) / nim
260
+
261
+ # power iterations
262
+ v = v0.astype('float32')
263
+
264
+ t0 = time.time()
265
+ dt = 0
266
+ for i in range(200):
267
+ vnext = np.zeros(nim, dtype='float32')
268
+
269
+ vnext = vnext + W.transpose().dot(v)
270
+
271
+ vnext = alpha * vnext + (1 - alpha) / nim
272
+ # L1 normalize
273
+ vnext /= vnext.sum()
274
+ v = vnext
275
+
276
+ if i == 200 - 1:
277
+ clust = find_maxima_cluster(W, v)
278
+
279
+ return [int(i) for i in clust]
280
+
281
+
282
+ def find_maxima_cluster(W, v):
283
+ n, m = W.shape
284
+ assert (n == m)
285
+ assign = np.zeros(n)
286
+ # for each node
287
+ pointers = list(range(n))
288
+ for i in range(n):
289
+ best_vi = 0
290
+ l0 = W.indptr[i]
291
+ l1 = W.indptr[i + 1]
292
+ for l in range(l0, l1):
293
+ j = W.indices[l]
294
+ vi = W.data[l] * (v[j] - v[i])
295
+ if vi > best_vi:
296
+ best_vi = vi
297
+ pointers[i] = j
298
+ n_clus = 0
299
+ cluster_ids = -1 * np.ones(n)
300
+ for i in range(n):
301
+ if pointers[i] == i:
302
+ cluster_ids[i] = n_clus
303
+ n_clus = n_clus + 1
304
+ for i in range(n):
305
+ # go from pointers to pointers starting from i until reached a local optim
306
+ current_node = i
307
+ while pointers[current_node] != current_node:
308
+ current_node = pointers[current_node]
309
+
310
+ assign[i] = cluster_ids[current_node]
311
+ assert (assign[i] >= 0)
312
+ return assign
313
+
314
+
315
+ class PIC(object):
316
+ """Class to perform Power Iteration Clustering on a graph of nearest neighbors.
317
+ Args:
318
+ args: for consistency with k-means init
319
+ sigma (float): bandwidth of the Gaussian kernel (default 0.2)
320
+ nnn (int): number of nearest neighbors (default 5)
321
+ alpha (float): parameter in PIC (default 0.001)
322
+ distribute_singletons (bool): If True, reassign each singleton to
323
+ the cluster of its closest non
324
+ singleton nearest neighbors (up to nnn
325
+ nearest neighbors).
326
+ Attributes:
327
+ images_lists (list of list): for each cluster, the list of image indexes
328
+ belonging to this cluster
329
+ """
330
+
331
+ def __init__(self, args=None, sigma=0.2, nnn=5, alpha=0.001, distribute_singletons=True):
332
+ self.sigma = sigma
333
+ self.alpha = alpha
334
+ self.nnn = nnn
335
+ self.distribute_singletons = distribute_singletons
336
+
337
+ def cluster(self, data, verbose=False):
338
+ end = time.time()
339
+
340
+ # preprocess the data
341
+ xb = preprocess_features(data)
342
+
343
+ # construct nnn graph
344
+ I, D = make_graph(xb, self.nnn)
345
+
346
+ # run PIC
347
+ clust = run_pic(I, D, self.sigma, self.alpha)
348
+ images_lists = {}
349
+ for h in set(clust):
350
+ images_lists[h] = []
351
+ for data, c in enumerate(clust):
352
+ images_lists[c].append(data)
353
+
354
+ # allocate singletons to clusters of their closest NN not singleton
355
+ if self.distribute_singletons:
356
+ clust_NN = {}
357
+ for i in images_lists:
358
+ # if singleton
359
+ if len(images_lists[i]) == 1:
360
+ s = images_lists[i][0]
361
+ # for NN
362
+ for n in I[s, 1:]:
363
+ # if NN is not a singleton
364
+ if not len(images_lists[clust[n]]) == 1:
365
+ clust_NN[s] = n
366
+ break
367
+ for s in clust_NN:
368
+ del images_lists[clust[s]]
369
+ clust[s] = clust[clust_NN[s]]
370
+ images_lists[clust[s]].append(s)
371
+
372
+ self.images_lists = []
373
+ for c in images_lists:
374
+ self.images_lists.append(images_lists[c])
375
+
376
+ if verbose:
377
+ print('pic time: {0:.0f} s'.format(time.time() - end))
378
+ return 0