cell-data-loader 0.0.32__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.
- cell_data_loader/.cell_data_loader.py.swp +0 -0
- cell_data_loader/__init__.py +3 -0
- cell_data_loader/base_dataset.py +47 -0
- cell_data_loader/cell_data_loader.py +622 -0
- cell_data_loader/util.py +892 -0
- cell_data_loader-0.0.32.dist-info/METADATA +148 -0
- cell_data_loader-0.0.32.dist-info/RECORD +8 -0
- cell_data_loader-0.0.32.dist-info/WHEEL +4 -0
|
Binary file
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
|
|
3
|
+
import torch.utils.data as data
|
|
4
|
+
from PIL import Image
|
|
5
|
+
import torchvision.transforms as transforms
|
|
6
|
+
|
|
7
|
+
class BaseDataset(data.Dataset):
|
|
8
|
+
def __init__(self):
|
|
9
|
+
super(BaseDataset, self).__init__()
|
|
10
|
+
|
|
11
|
+
def name(self):
|
|
12
|
+
return 'BaseDataset'
|
|
13
|
+
|
|
14
|
+
def initialize(self, opt):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
def get_transform(opt):
|
|
18
|
+
transform_list = []
|
|
19
|
+
if opt.resize_or_crop == 'resize_and_crop':
|
|
20
|
+
osize = [opt.loadSize, opt.loadSize]
|
|
21
|
+
transform_list.append(transforms.Scale(osize, Image.BICUBIC))
|
|
22
|
+
transform_list.append(transforms.RandomCrop(opt.fineSize))
|
|
23
|
+
elif opt.resize_or_crop == 'crop':
|
|
24
|
+
transform_list.append(transforms.RandomCrop(opt.fineSize))
|
|
25
|
+
elif opt.resize_or_crop == 'scale_width':
|
|
26
|
+
transform_list.append(transforms.Lambda(
|
|
27
|
+
lambda img: __scale_width(img, opt.fineSize)))
|
|
28
|
+
elif opt.resize_or_crop == 'scale_width_and_crop':
|
|
29
|
+
transform_list.append(transforms.Lambda(
|
|
30
|
+
lambda img: __scale_width(img, opt.loadSize)))
|
|
31
|
+
transform_list.append(transforms.RandomCrop(opt.fineSize))
|
|
32
|
+
|
|
33
|
+
if opt.isTrain and not opt.no_flip:
|
|
34
|
+
transform_list.append(transforms.RandomHorizontalFlip())
|
|
35
|
+
|
|
36
|
+
transform_list += [transforms.ToTensor(),
|
|
37
|
+
transforms.Normalize((0.5, 0.5, 0.5),
|
|
38
|
+
(0.5, 0.5, 0.5))]
|
|
39
|
+
return transforms.Compose(transform_list)
|
|
40
|
+
|
|
41
|
+
def __scale_width(img, target_width):
|
|
42
|
+
ow, oh = img.size
|
|
43
|
+
if (ow == target_width):
|
|
44
|
+
return img
|
|
45
|
+
w = target_width
|
|
46
|
+
h = int(target_width * oh / ow)
|
|
47
|
+
return img.resize((w, h), Image.BICUBIC)
|
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
|
|
3
|
+
import os,sys,json,csv,cv2,glob,random,re,warnings
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pickle
|
|
6
|
+
from numpy.random import choice,shuffle
|
|
7
|
+
from math import floor,ceil,sqrt
|
|
8
|
+
from PIL import Image,ImageEnhance
|
|
9
|
+
from scipy import ndimage
|
|
10
|
+
from scipy.signal import resample
|
|
11
|
+
from scipy.ndimage.interpolation import map_coordinates
|
|
12
|
+
from scipy.ndimage.filters import gaussian_filter
|
|
13
|
+
from .base_dataset import BaseDataset
|
|
14
|
+
import torch,torchvision
|
|
15
|
+
#torchvision.disable_beta_transforms_warning()
|
|
16
|
+
from .util import *
|
|
17
|
+
import torchvision.transforms as transforms
|
|
18
|
+
|
|
19
|
+
#import openslide
|
|
20
|
+
#import czifile
|
|
21
|
+
try:
|
|
22
|
+
import slideio
|
|
23
|
+
except:
|
|
24
|
+
warnings.warn("""No valid slideio installed, SVS and CZI files cannot
|
|
25
|
+
be read in -- run `pip install slideio` for support""")
|
|
26
|
+
|
|
27
|
+
class ImageLabelObject(BaseDataset):
|
|
28
|
+
def __init__(self,
|
|
29
|
+
filename,
|
|
30
|
+
mode="whole",
|
|
31
|
+
dtype="torch",
|
|
32
|
+
gpu_ids="",
|
|
33
|
+
dim=(64,64),
|
|
34
|
+
n_channels = None):
|
|
35
|
+
self.filename = filename
|
|
36
|
+
self.image = None
|
|
37
|
+
self.label = None
|
|
38
|
+
self.boxlabel = None
|
|
39
|
+
self.mode = mode
|
|
40
|
+
self.dtype = dtype
|
|
41
|
+
self.dim = dim
|
|
42
|
+
self.gpu_ids = gpu_ids
|
|
43
|
+
self.n_channels = n_channels
|
|
44
|
+
def im_type(self):
|
|
45
|
+
_,ext = os.path.splitext(self.filename)
|
|
46
|
+
ext = ext.lower()
|
|
47
|
+
if ext in [".png",".jpg",".jpeg",".tiff",".tif"]:
|
|
48
|
+
return "regular"
|
|
49
|
+
elif ext in [".svs"]:
|
|
50
|
+
#pip install aicspylibczi>=3.1.1
|
|
51
|
+
return "svs"
|
|
52
|
+
elif ext in [".czi"]:
|
|
53
|
+
return "czi"
|
|
54
|
+
else:
|
|
55
|
+
raise Exception("Image type unsupported: %s" % ext)
|
|
56
|
+
def get_image(self):
|
|
57
|
+
if self.image is None:
|
|
58
|
+
if self.im_type() == "regular":
|
|
59
|
+
self.image = cv2.imread(self.filename)
|
|
60
|
+
#elif self.im_type() == "svs":
|
|
61
|
+
#n = openslide.OpenSlide(self.filename)
|
|
62
|
+
#levels = n.level_dimensions
|
|
63
|
+
#level=len(levels)-1
|
|
64
|
+
#width0,height0 = levels[level]
|
|
65
|
+
#self.image = n.read_region((0,0),level,(width0,height0))
|
|
66
|
+
#self.image = np.array(self.image)
|
|
67
|
+
elif self.im_type() == "czi" or self.im_type() == "svs":
|
|
68
|
+
#https://towardsdatascience.com/slideio-a-new-python-library-for-reading-medical-images-11858a522059
|
|
69
|
+
#self.image = czifile.imread(self.filename)
|
|
70
|
+
try:
|
|
71
|
+
self.image = slideio.open_slide(self.filename,
|
|
72
|
+
self.im_type().upper())
|
|
73
|
+
except:
|
|
74
|
+
raise Exception(
|
|
75
|
+
"SVS/CZI read on %s failed -- check slideio install" %\
|
|
76
|
+
self.filename
|
|
77
|
+
)
|
|
78
|
+
n_scenes = self.image.num_scenes
|
|
79
|
+
self.image = self.image.get_scene(n_scenes-1)
|
|
80
|
+
self.image = self.image.read_block(
|
|
81
|
+
slices=(0,self.image.num_z_slices)
|
|
82
|
+
)
|
|
83
|
+
self.image = np.squeeze(self.image)
|
|
84
|
+
#self.image = np.moveaxis(self.image,0,-1)
|
|
85
|
+
if self.image.dtype != np.uint8:
|
|
86
|
+
self.image = self.image.astype(np.uint8)
|
|
87
|
+
if self.mode == "whole" and self.im_type() == "regular":
|
|
88
|
+
self.image = cv2.resize(self.image,self.dim[::-1])
|
|
89
|
+
assert(not np.isnan(self.image[0,0,0]))
|
|
90
|
+
elif self.mode == "whole" and self.im_type() in ["czi","svs"]:
|
|
91
|
+
#self.image = resize(self.image,self.dim)
|
|
92
|
+
self.image = cv2.resize(self.image,self.dim[::-1])
|
|
93
|
+
if len(self.image.shape) < 3:
|
|
94
|
+
self.image = np.expand_dims(self.image,axis=2)
|
|
95
|
+
s = self.image.shape
|
|
96
|
+
assert(len(s) == 3)
|
|
97
|
+
if s[0] < s[1] and s[0] < s[2]:
|
|
98
|
+
self.image = np.moveaxis(self.image,0,-1)
|
|
99
|
+
if len(self.image.shape) != 3:
|
|
100
|
+
self.image = np.expand_dims(self.image,axis=2)
|
|
101
|
+
assert(len(self.image.shape) == 3)
|
|
102
|
+
if self.n_channels is not None and \
|
|
103
|
+
self.image.shape[2] != self.n_channels:
|
|
104
|
+
self.image = resample(self.image,
|
|
105
|
+
self.n_channels,axis=2)
|
|
106
|
+
if self.dtype == "torch":
|
|
107
|
+
self.image = torch.tensor(self.image)
|
|
108
|
+
assert(len(self.image.size()) == 3)
|
|
109
|
+
elif self.dtype == "numpy":
|
|
110
|
+
assert(len(self.image.shape) == 3)
|
|
111
|
+
else:
|
|
112
|
+
raise Exception("Unimplemented dtype: %s" % self.dtype)
|
|
113
|
+
return self.image
|
|
114
|
+
def get_cell_box_label(self):
|
|
115
|
+
if self.boxlabel is None:
|
|
116
|
+
self.boxlabel,_ = get_cell_boxes_from_image(self.get_image())
|
|
117
|
+
return self.boxlabel
|
|
118
|
+
def get_orig_size(self):
|
|
119
|
+
if self.dtype == "torch":
|
|
120
|
+
s = self.get_image().size()
|
|
121
|
+
elif self.dtype == "numpy":
|
|
122
|
+
s = self.get_image().shape
|
|
123
|
+
else:
|
|
124
|
+
raise Exception("Unimplemented dtype: %s" % self.dtype)
|
|
125
|
+
return s
|
|
126
|
+
def get_n_channels(self):
|
|
127
|
+
if self.n_channels is not None:
|
|
128
|
+
return self.n_channels
|
|
129
|
+
s = self.get_orig_size()
|
|
130
|
+
if len(s) == 2: return 1
|
|
131
|
+
s_sorted = sorted(s,reverse=True)
|
|
132
|
+
return s_sorted[2]
|
|
133
|
+
def get_orig_dims(self):
|
|
134
|
+
s = self.get_orig_size()
|
|
135
|
+
s_sorted = sorted(s,reverse=True)
|
|
136
|
+
x,y = s_sorted[0],s_sorted[1]
|
|
137
|
+
if x == y: return x,y
|
|
138
|
+
if s.index(x) > s.index(y):
|
|
139
|
+
return y,x
|
|
140
|
+
else:
|
|
141
|
+
return x,y
|
|
142
|
+
def get_scaled_dims(self):
|
|
143
|
+
x,y = self.get_orig_dims()
|
|
144
|
+
return x // self.dim[0], y // self.dim[1]
|
|
145
|
+
def get_box_label_filename(self):
|
|
146
|
+
name,ext = os.path.splitext(self.filename)
|
|
147
|
+
filename = os.path.join(os.path.dirname(name),
|
|
148
|
+
".%s_boxlabel.csv" % os.path.basename(name))
|
|
149
|
+
return filename
|
|
150
|
+
def read_box_label(self,box_label_file=None):
|
|
151
|
+
if box_label_file is None:
|
|
152
|
+
box_label_file = self.get_box_label_filename()
|
|
153
|
+
assert(os.path.isfile(box_label_file))
|
|
154
|
+
self.boxlabel = read_in_csv(box_label_file)
|
|
155
|
+
def __len__(self):
|
|
156
|
+
if self.mode == "whole":
|
|
157
|
+
return 1
|
|
158
|
+
elif self.mode == "sliced":
|
|
159
|
+
x,y = self.get_scaled_dims()
|
|
160
|
+
return x*y
|
|
161
|
+
elif self.mode == "cell":
|
|
162
|
+
return len(self.get_cell_box_label())
|
|
163
|
+
else:
|
|
164
|
+
raise Exception("Invalid mode: %s" % self.mode)
|
|
165
|
+
def __getitem__(self,index):
|
|
166
|
+
if self.mode == "whole":
|
|
167
|
+
im = self.get_image()
|
|
168
|
+
return im
|
|
169
|
+
elif self.mode == "sliced":
|
|
170
|
+
x_dim,y_dim = self.get_scaled_dims()
|
|
171
|
+
x = index % (x_dim)
|
|
172
|
+
y = (index // (x_dim)) % (y_dim)
|
|
173
|
+
imslice = self.get_image()[x * self.dim[0]:(x+1)*self.dim[0],
|
|
174
|
+
y * self.dim[1]:(y+1)*self.dim[1],...]
|
|
175
|
+
return imslice
|
|
176
|
+
elif self.mode == "cell":
|
|
177
|
+
if len(self.get_cell_box_label()) == 0:
|
|
178
|
+
#print("len self: %d" % len(self))
|
|
179
|
+
return -1
|
|
180
|
+
#raise Exception("No cells in this image — should not call")
|
|
181
|
+
#warnings.warn("No cells found in %s" % self.filename)
|
|
182
|
+
#return -1
|
|
183
|
+
index = index % len(self.get_cell_box_label())
|
|
184
|
+
x,y,l,w = self.get_cell_box_label()[index]
|
|
185
|
+
return slice_and_augment(self.get_image(),x,y,l,w,
|
|
186
|
+
out_size=self.dim)
|
|
187
|
+
raise Exception("Unimplemented")
|
|
188
|
+
else:
|
|
189
|
+
raise Exception("Invalid mode: %s" % self.mode)
|
|
190
|
+
def clear(self):
|
|
191
|
+
del self.image
|
|
192
|
+
self.image = None
|
|
193
|
+
|
|
194
|
+
class CellDataloader():#BaseDataset):
|
|
195
|
+
def __init__(self,
|
|
196
|
+
*image_folders,
|
|
197
|
+
label_regex = None,
|
|
198
|
+
label_file = None,
|
|
199
|
+
segment_image = "whole",
|
|
200
|
+
augment_image = True,
|
|
201
|
+
dim = (64,64),
|
|
202
|
+
batch_size = 64,
|
|
203
|
+
verbose = True,
|
|
204
|
+
dtype = "torch",
|
|
205
|
+
gpu_ids = None,
|
|
206
|
+
label_balance = True,
|
|
207
|
+
cell_box_regex = None,
|
|
208
|
+
cell_box_filelist = None,
|
|
209
|
+
n_channels = None,
|
|
210
|
+
channels_first = True,
|
|
211
|
+
match_labels=False,
|
|
212
|
+
normalize=True,
|
|
213
|
+
split = None,
|
|
214
|
+
return_filenames = False,
|
|
215
|
+
sample_output_folder = None):
|
|
216
|
+
|
|
217
|
+
self.verbose = verbose
|
|
218
|
+
self.label_balance = label_balance
|
|
219
|
+
self.segment_image = segment_image.lower()
|
|
220
|
+
self.dim = dim
|
|
221
|
+
self.batch_size = batch_size
|
|
222
|
+
self.dtype = dtype
|
|
223
|
+
self.index = 0
|
|
224
|
+
self.im_index = 0
|
|
225
|
+
self.gpu_ids = gpu_ids
|
|
226
|
+
self.cell_box_regex = cell_box_regex
|
|
227
|
+
self.cell_box_filelist = cell_box_filelist
|
|
228
|
+
self.dtype = dtype
|
|
229
|
+
self.n_channels = n_channels
|
|
230
|
+
self.channels_first = channels_first
|
|
231
|
+
self.augment_image = augment_image
|
|
232
|
+
self.normalize = normalize
|
|
233
|
+
self.return_filenames = return_filenames
|
|
234
|
+
self.sample_output_folder = sample_output_folder
|
|
235
|
+
|
|
236
|
+
if self.augment_image and self.dtype == "torch":
|
|
237
|
+
self.augment = transforms.Compose([
|
|
238
|
+
transforms.RandomHorizontalFlip(0.5),
|
|
239
|
+
transforms.RandomVerticalFlip(0.5),
|
|
240
|
+
transforms.GaussianBlur(5),
|
|
241
|
+
transforms.ColorJitter(brightness=(0.5,1.5),
|
|
242
|
+
contrast=(1), saturation=(0.5,1.5), hue=(-0.1,0.1)),
|
|
243
|
+
transforms.RandomResizedCrop(self.dim, antialias=True)])#,
|
|
244
|
+
#transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))])
|
|
245
|
+
self.augment2 = transforms.Compose([
|
|
246
|
+
transforms.ElasticTransform()])
|
|
247
|
+
"""
|
|
248
|
+
If true, outputs given labels equally. This may repeat images if the
|
|
249
|
+
counts of each don't line up.
|
|
250
|
+
"""
|
|
251
|
+
self.match_labels = match_labels
|
|
252
|
+
"""
|
|
253
|
+
Determines if image folders or file lists exist, then reads them in
|
|
254
|
+
"""
|
|
255
|
+
all_filename_lists = []
|
|
256
|
+
duplicate_test = set()
|
|
257
|
+
for img in image_folders:
|
|
258
|
+
flist = get_file_list(img)
|
|
259
|
+
flist_set = set(flist)
|
|
260
|
+
if len(flist_set.intersection(duplicate_test))>0:
|
|
261
|
+
raise Exception("Intersecting files found between labels")
|
|
262
|
+
duplicate_test = duplicate_test.union(flist_set)
|
|
263
|
+
all_filename_lists.append(flist)
|
|
264
|
+
|
|
265
|
+
if self.segment_image not in ["whole","cell","sliced"]:
|
|
266
|
+
raise Exception(
|
|
267
|
+
"""
|
|
268
|
+
%s is not a valid option for segment_image.
|
|
269
|
+
Must be 'whole','cell', or 'sliced'
|
|
270
|
+
""" % self.segment_image)
|
|
271
|
+
|
|
272
|
+
"""
|
|
273
|
+
Determines the format of the labels in the fed-in data, if they're
|
|
274
|
+
present at all.
|
|
275
|
+
"""
|
|
276
|
+
self.label_input_format = "None"
|
|
277
|
+
self.n_labels = 0
|
|
278
|
+
if label_file is not None and label_regex is not None:
|
|
279
|
+
raise Exception('Cannot have a label file and regex')
|
|
280
|
+
if label_file is not None:
|
|
281
|
+
if not os.path.isfile(label_file):
|
|
282
|
+
raise Exception("No label file: %s" % label_file)
|
|
283
|
+
self.label_input_format = "List"
|
|
284
|
+
elif label_regex is not None:
|
|
285
|
+
assert(isinstance(label_regex,list) or isinstance(label_regex,str))
|
|
286
|
+
if isinstance(label_regex,list):
|
|
287
|
+
assert(len(label_regex) > 0)
|
|
288
|
+
self.label_regex = []
|
|
289
|
+
for l in label_regex:
|
|
290
|
+
if isinstance(l,str):
|
|
291
|
+
self.label_regex.append(re.compile(l))
|
|
292
|
+
elif isinstance(l,list):
|
|
293
|
+
self.label_regex.append([re.compile(_) for _ in l])
|
|
294
|
+
else:
|
|
295
|
+
raise Exception(
|
|
296
|
+
f"Label regex {l} must be string or list"
|
|
297
|
+
)
|
|
298
|
+
if len(self.label_regex) == 1:
|
|
299
|
+
self.label_regex = self.label_regex[0]
|
|
300
|
+
self.n_labels = len(self.label_regex) + 1
|
|
301
|
+
else:
|
|
302
|
+
self.label_regex = re.compile(label_regex)
|
|
303
|
+
self.n_labels = 1
|
|
304
|
+
self.label_input_format = "Regex"
|
|
305
|
+
elif len(all_filename_lists) > 1:
|
|
306
|
+
self.label_input_format = "Folder"
|
|
307
|
+
self.n_labels = len(all_filename_lists)
|
|
308
|
+
if self.verbose:
|
|
309
|
+
print("Detected label format: %s" % self.label_input_format)
|
|
310
|
+
if self.segment_image == "cell":
|
|
311
|
+
"""
|
|
312
|
+
The case when cells are sliced out of images individually. Requires
|
|
313
|
+
Cellpose.
|
|
314
|
+
"""
|
|
315
|
+
try:
|
|
316
|
+
from cellpose import models,io
|
|
317
|
+
except:
|
|
318
|
+
raise Exception("Cellpose import failed -- need valid "+\
|
|
319
|
+
"cellpose version to use cell segmentation option.")
|
|
320
|
+
if self.cell_box_regex is not None:
|
|
321
|
+
assert(len(all_filename_lists) == 1)
|
|
322
|
+
self.cell_box_regex = re.compile(self.cell_box_regex)
|
|
323
|
+
self.label_input_format = "Cell_Box_Regex"
|
|
324
|
+
elif self.cell_box_filelist is not None:
|
|
325
|
+
assert(len(all_filename_lists) == 1)
|
|
326
|
+
if isinstance(self.cell_box_filelist,str): # One image
|
|
327
|
+
self.cell_box_filelist = [[self.cell_box_filelist]]
|
|
328
|
+
elif isinstance(self.cell_box_filelist,list):
|
|
329
|
+
assert(len(self.cell_box_filelist) > 0)
|
|
330
|
+
if isinstance(self.cell_box_filelist[0],str):
|
|
331
|
+
self.cell_box_filelist = [self.cell_box_filelist]
|
|
332
|
+
assert(isinstance(self.cell_box_filelist[0][0],str))
|
|
333
|
+
for i,l in enumerate(self.cell_box_filelist):
|
|
334
|
+
assert(len(all_filenames_list[i]) == len(l))
|
|
335
|
+
for filename in l:
|
|
336
|
+
if not os.path.isfile(filename):
|
|
337
|
+
raise Exception(
|
|
338
|
+
"File doesn't exist: %s" % filename)
|
|
339
|
+
self.label_input_format = "Cell_Box_Filelist"
|
|
340
|
+
|
|
341
|
+
"""
|
|
342
|
+
Reads in and determines makeup of image folder
|
|
343
|
+
"""
|
|
344
|
+
|
|
345
|
+
self.image_objects = []
|
|
346
|
+
for j,all_filename_list in enumerate(all_filename_lists):
|
|
347
|
+
for i,filename in enumerate(all_filename_list):
|
|
348
|
+
if is_image_file(filename):
|
|
349
|
+
if split is not None:
|
|
350
|
+
s1 = split[0] # List of numbers
|
|
351
|
+
s2 = split[1] # Max number
|
|
352
|
+
assert isinstance(s1,list)
|
|
353
|
+
assert isinstance(s2,int)
|
|
354
|
+
assert all([isinstance(_,int) for _ in s1])
|
|
355
|
+
assert all([ _ < s2 for _ in s1])
|
|
356
|
+
assert all([ _ >= 0 for _ in s1])
|
|
357
|
+
h = hash(filename)
|
|
358
|
+
if h % s2 not in s1:
|
|
359
|
+
continue
|
|
360
|
+
skip = False
|
|
361
|
+
imlabel = ImageLabelObject(filename,
|
|
362
|
+
gpu_ids=self.gpu_ids,
|
|
363
|
+
dtype=self.dtype,
|
|
364
|
+
dim=self.dim,
|
|
365
|
+
mode=self.segment_image)
|
|
366
|
+
if self.label_input_format == "Folder":
|
|
367
|
+
imlabel.label = j
|
|
368
|
+
elif self.label_input_format == "Regex":
|
|
369
|
+
imlabel.label = self.__matchitem__(filename)
|
|
370
|
+
if imlabel.label < 0: skip = True
|
|
371
|
+
elif self.label_input_format == "List":
|
|
372
|
+
imlabel.label = label_file_dict[filename]
|
|
373
|
+
elif self.label_input_format == "Cell_Box_Filelist":
|
|
374
|
+
imlabel.read_box_label(self.cell_box_filelist[j][i])
|
|
375
|
+
elif self.label_input_format == "Cell_Box_Regex":
|
|
376
|
+
raise Exception("Unimplemented")
|
|
377
|
+
imlabel.boxlabel = self.cell_im_regex(filename)
|
|
378
|
+
if skip: continue
|
|
379
|
+
self.image_objects.append(imlabel)
|
|
380
|
+
random.shuffle(self.image_objects)
|
|
381
|
+
if self.match_labels:
|
|
382
|
+
self.sort_to_match_labels()
|
|
383
|
+
if self.verbose:
|
|
384
|
+
print("%d image paths read" % len(self.image_objects))
|
|
385
|
+
|
|
386
|
+
"""
|
|
387
|
+
Acts on the above commands to read in the labels as necessary
|
|
388
|
+
"""
|
|
389
|
+
|
|
390
|
+
if self.label_input_format == "List":
|
|
391
|
+
label_list = read_label_file(label_file)
|
|
392
|
+
|
|
393
|
+
"""
|
|
394
|
+
Makes batch array
|
|
395
|
+
"""
|
|
396
|
+
self.batch = None
|
|
397
|
+
self.label_batch = None # [ 0 for _ in range(self.batch_size) ]
|
|
398
|
+
if self.return_labels():
|
|
399
|
+
if self.dtype == "numpy":
|
|
400
|
+
self.label_batch = np.zeros((self.batch_size,self.n_labels))
|
|
401
|
+
elif self.dtype == "torch":
|
|
402
|
+
self.label_batch = torch.zeros((self.batch_size,self.n_labels),
|
|
403
|
+
device = self.gpu_ids)
|
|
404
|
+
sample = self.image_objects[0]
|
|
405
|
+
if n_channels is None:
|
|
406
|
+
self.n_channels = sample.get_n_channels()
|
|
407
|
+
else:
|
|
408
|
+
self.n_channels = n_channels
|
|
409
|
+
if self.normalize and self.dtype == "torch":
|
|
410
|
+
from torchvision.transforms import Normalize
|
|
411
|
+
self.normalizer = Normalize(
|
|
412
|
+
tuple([0.5 for _ in range(self.n_channels)]),
|
|
413
|
+
tuple([0.5 for _ in range(self.n_channels)]))
|
|
414
|
+
for image_object in self.image_objects:
|
|
415
|
+
image_object.n_channels = self.n_channels
|
|
416
|
+
if self.verbose:
|
|
417
|
+
print("%d Channels Detected" % self.n_channels)
|
|
418
|
+
if self.dtype == "torch":
|
|
419
|
+
self.batch = torch.zeros(self.batch_size,self.dim[0],self.dim[1],
|
|
420
|
+
self.n_channels,device=self.gpu_ids)
|
|
421
|
+
elif self.dtype == "numpy":
|
|
422
|
+
self.batch = np.zeros((self.batch_size,self.dim[0],self.dim[1],
|
|
423
|
+
self.n_channels))
|
|
424
|
+
else:
|
|
425
|
+
raise Exception("Unimplemented dtype: %s" % self.dtype)
|
|
426
|
+
|
|
427
|
+
if self.sample_output_folder is not None:
|
|
428
|
+
if self.verbose:
|
|
429
|
+
print("Initiating sample output folder at %s" % \
|
|
430
|
+
self.sample_output_folder)
|
|
431
|
+
os.makedirs(self.sample_output_folder,exist_ok=True)
|
|
432
|
+
self.imcount = 0
|
|
433
|
+
def sort_to_match_labels(self):
|
|
434
|
+
assert(self.match_labels)
|
|
435
|
+
if not self.return_labels():
|
|
436
|
+
warnings.warn("Match labels is set to true but no labels found")
|
|
437
|
+
elif "cell" in self.label_input_format.lower():
|
|
438
|
+
warnings.warn("Match labels is set to true but cell-specific" +\
|
|
439
|
+
" matching unimplemented")
|
|
440
|
+
else:
|
|
441
|
+
assert(self.n_labels) > 1
|
|
442
|
+
label_sorter = {}
|
|
443
|
+
for i in range(self.n_labels):
|
|
444
|
+
label_sorter[i] = []
|
|
445
|
+
label_counter = 0
|
|
446
|
+
image_objects_matched = []
|
|
447
|
+
for obj in self.image_objects:
|
|
448
|
+
label_sorter[obj.label].append(obj)
|
|
449
|
+
while not np.all([label_counter >= len(label_sorter[i])\
|
|
450
|
+
for i in range(self.n_labels)]):
|
|
451
|
+
for j in range(self.n_labels):
|
|
452
|
+
image_objects_matched.append(
|
|
453
|
+
label_sorter[j][label_counter % len(label_sorter[j])])
|
|
454
|
+
label_counter += 1
|
|
455
|
+
self.image_objects = image_objects_matched
|
|
456
|
+
obj_counts = [0 for _ in range(self.n_labels)]
|
|
457
|
+
for obj in self.image_objects:
|
|
458
|
+
obj_counts[obj.label] += 1
|
|
459
|
+
assert(np.all([o == obj_counts[0] for o in obj_counts]))
|
|
460
|
+
assert(len(self.image_objects) // self.n_labels == obj_counts[0])
|
|
461
|
+
def __matchitem__(self,image_file):
|
|
462
|
+
if self.label_input_format != "Regex":
|
|
463
|
+
raise Exception("""
|
|
464
|
+
Label input format must be regex, is currently %s
|
|
465
|
+
""" % self.label_input_format)
|
|
466
|
+
if isinstance(self.label_regex,list):
|
|
467
|
+
m = -1
|
|
468
|
+
for i,reg in enumerate(self.label_regex):
|
|
469
|
+
if isinstance(reg,list):
|
|
470
|
+
expr = np.any(
|
|
471
|
+
[ bool(reg_.search(image_file)) for reg_ in reg ]
|
|
472
|
+
)
|
|
473
|
+
else: expr = bool(reg.search(image_file))
|
|
474
|
+
if expr:
|
|
475
|
+
if m > -1:
|
|
476
|
+
warnings.warn(
|
|
477
|
+
("Image file %s matches at"+\
|
|
478
|
+
" least two regular expressions") % image_file)
|
|
479
|
+
m = i
|
|
480
|
+
return m
|
|
481
|
+
else:
|
|
482
|
+
if bool(self.label_regex.search(image_file)):
|
|
483
|
+
return 1
|
|
484
|
+
else:
|
|
485
|
+
return 0
|
|
486
|
+
def __iter__(self):
|
|
487
|
+
return self
|
|
488
|
+
def return_labels(self):
|
|
489
|
+
"""
|
|
490
|
+
Boolean determining whether labels or just the image should be returned
|
|
491
|
+
"""
|
|
492
|
+
if self.label_input_format == "None":
|
|
493
|
+
assert(self.n_labels == 0)
|
|
494
|
+
return False
|
|
495
|
+
elif self.segment_image in ["whole","sliced"]:
|
|
496
|
+
assert(self.n_labels > 0)
|
|
497
|
+
return True
|
|
498
|
+
assert(self.n_labels > 0)
|
|
499
|
+
return True
|
|
500
|
+
def next_im(self):
|
|
501
|
+
"""
|
|
502
|
+
Returns the next single image
|
|
503
|
+
"""
|
|
504
|
+
#self.index = (self.index + 1) #% len(self)
|
|
505
|
+
if self.index >= len(self.image_objects):
|
|
506
|
+
self.index = 0
|
|
507
|
+
self.im_index = 0
|
|
508
|
+
raise StopIteration
|
|
509
|
+
#assert(len(self.image_objects[self.index]) > 0)
|
|
510
|
+
im = self.image_objects[self.index][self.im_index]
|
|
511
|
+
label = self.image_objects[self.index].label
|
|
512
|
+
fname = self.image_objects[self.index].filename
|
|
513
|
+
self.im_index += 1
|
|
514
|
+
while self.im_index >= len(self.image_objects[self.index]) or \
|
|
515
|
+
(len(self.image_objects[self.index]) == 0):
|
|
516
|
+
self.image_objects[self.index].clear()
|
|
517
|
+
self.im_index = 0
|
|
518
|
+
self.index += 1
|
|
519
|
+
if self.index >= len(self.image_objects): break
|
|
520
|
+
#if self.augment_image and self.dtype == "torch":
|
|
521
|
+
#imdim = im.size()
|
|
522
|
+
#print("imdim: %s" % str(imdim))
|
|
523
|
+
#im = torch.permute(im,[len(imdim)-1]+list(range(0,len(imdim)-1)))
|
|
524
|
+
#print("imdim: %s" % str(im.size()))
|
|
525
|
+
#im = self.augment(im)
|
|
526
|
+
#im = torch.permute(im,list(range(1,len(imdim))) + [0])
|
|
527
|
+
#print("imdim: %s"%str(im.size()))
|
|
528
|
+
return im,label,fname
|
|
529
|
+
def __next__(self):
|
|
530
|
+
"""
|
|
531
|
+
Returns the next batch of images
|
|
532
|
+
"""
|
|
533
|
+
if self.return_filenames:
|
|
534
|
+
fnames = []
|
|
535
|
+
for i in range(self.batch_size):
|
|
536
|
+
if self.return_labels():
|
|
537
|
+
im,y,fname = self.next_im()
|
|
538
|
+
|
|
539
|
+
while isinstance(im,int) and im == -1:
|
|
540
|
+
im,y,fname = self.next_im()
|
|
541
|
+
if self.n_labels == 0:
|
|
542
|
+
raise Exception(
|
|
543
|
+
"Cannot return labels with self.n_labels as 0")
|
|
544
|
+
elif self.n_labels == 1:
|
|
545
|
+
self.label_batch[i,0] = y
|
|
546
|
+
else:
|
|
547
|
+
for j in range(self.n_labels):
|
|
548
|
+
self.label_batch[i,j] = 0
|
|
549
|
+
self.label_batch[i,y] = 1
|
|
550
|
+
else:
|
|
551
|
+
im,y,fname = self.next_im()
|
|
552
|
+
while isinstance(im,int) and im == -1:
|
|
553
|
+
im,y,fname = self.next_im()
|
|
554
|
+
if self.return_filenames: fnames.append(fname)
|
|
555
|
+
assert(not isinstance(im,int))
|
|
556
|
+
if self.dtype == "torch":
|
|
557
|
+
if len(im.size()) == 2 and self.n_channels == 1:
|
|
558
|
+
im = torch.unsqueeze(im,2)
|
|
559
|
+
self.batch[i,...] = torch.unsqueeze(im,0)
|
|
560
|
+
if self.channels_first:
|
|
561
|
+
b = torch.moveaxis(self.batch,-1,1)
|
|
562
|
+
else:
|
|
563
|
+
b = self.batch
|
|
564
|
+
if (self.augment_image and self.n_channels <= 3) or self.normalize:
|
|
565
|
+
if not self.channels_first:
|
|
566
|
+
b = torch.permute(b,[0,-1] + list(range(1,len(b.size())-1)))
|
|
567
|
+
#print("b.size(): %s" % str(b.size()))
|
|
568
|
+
if self.n_channels <= 3 and self.augment_image:
|
|
569
|
+
b = self.augment(b)
|
|
570
|
+
if self.dim[0] > 32 and self.dim[1] > 32:
|
|
571
|
+
b = self.augment2(b)
|
|
572
|
+
#print("b.size(): %s" % str(b.size()))
|
|
573
|
+
#print([0]+list(range(2,len(b.size()))) + [1])
|
|
574
|
+
if self.normalize:
|
|
575
|
+
b = self.normalizer(b)
|
|
576
|
+
if not self.channels_first:
|
|
577
|
+
b = torch.permute(b,[0]+list(range(2,len(b.size()))) + [1])
|
|
578
|
+
#print("b.size(): %s" % str(b.size()))
|
|
579
|
+
#print("sss")
|
|
580
|
+
elif self.dtype == "numpy":
|
|
581
|
+
if len(im.shape) == 2 and self.n_channels == 1:
|
|
582
|
+
im = np.expand_dims(im,axis=2)
|
|
583
|
+
self.batch[i,...] = np.expand_dims(im,axis=0)
|
|
584
|
+
if self.channels_first:
|
|
585
|
+
b = np.moveaxis(self.batch,-1,1)
|
|
586
|
+
else:
|
|
587
|
+
b = self.batch
|
|
588
|
+
else:
|
|
589
|
+
raise Exception("Unimplemented dtype: %s" % self.dtype)
|
|
590
|
+
if self.return_filenames: assert len(fnames) == self.batch_size
|
|
591
|
+
r = [b]
|
|
592
|
+
if self.sample_output_folder is not None:
|
|
593
|
+
for i in range(b.size()[0]):
|
|
594
|
+
out_file_name = os.path.join(self.sample_output_folder,
|
|
595
|
+
"%.8d" % self.imcount)
|
|
596
|
+
if self.return_labels():
|
|
597
|
+
out_file_name = "%s_%s" % (out_file_name,
|
|
598
|
+
str(self.label_batch[i]))
|
|
599
|
+
if self.return_filenames:
|
|
600
|
+
out_file_name = "%s_%s" % (out_file_name,
|
|
601
|
+
os.path.basename(os.path.splitext(fnames[i])[0]))
|
|
602
|
+
out_file_name = "%s.png" % out_file_name
|
|
603
|
+
self.imcount += 1
|
|
604
|
+
im = b[i,...]
|
|
605
|
+
im = im - im.min()
|
|
606
|
+
if im.max() > 0:
|
|
607
|
+
im = im / im.max()
|
|
608
|
+
if self.dtype == "torch":
|
|
609
|
+
im = im.cpu().detach().numpy()
|
|
610
|
+
if self.channels_first:
|
|
611
|
+
im = np.moveaxis(im, 0, -1)
|
|
612
|
+
im = im * 255
|
|
613
|
+
im = im.astype(np.uint8)
|
|
614
|
+
Image.fromarray(im).save(out_file_name)
|
|
615
|
+
|
|
616
|
+
if self.return_labels():
|
|
617
|
+
r.append(self.label_batch)
|
|
618
|
+
if self.return_filenames:
|
|
619
|
+
r.append(fnames)
|
|
620
|
+
if len(r) == 1: r = r[0]
|
|
621
|
+
else: r = tuple(r)
|
|
622
|
+
return r
|