deepliif 1.1.8__py3-none-any.whl → 1.1.10__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.
- cli.py +49 -42
- deepliif/data/aligned_dataset.py +17 -0
- deepliif/models/SDG_model.py +189 -0
- deepliif/models/__init__.py +123 -55
- deepliif/options/__init__.py +62 -29
- deepliif/postprocessing.py +12 -5
- deepliif/util/__init__.py +22 -0
- deepliif/util/util.py +17 -1
- {deepliif-1.1.8.dist-info → deepliif-1.1.10.dist-info}/METADATA +171 -26
- {deepliif-1.1.8.dist-info → deepliif-1.1.10.dist-info}/RECORD +14 -13
- {deepliif-1.1.8.dist-info → deepliif-1.1.10.dist-info}/LICENSE.md +0 -0
- {deepliif-1.1.8.dist-info → deepliif-1.1.10.dist-info}/WHEEL +0 -0
- {deepliif-1.1.8.dist-info → deepliif-1.1.10.dist-info}/entry_points.txt +0 -0
- {deepliif-1.1.8.dist-info → deepliif-1.1.10.dist-info}/top_level.txt +0 -0
cli.py
CHANGED
|
@@ -14,7 +14,7 @@ from deepliif.models import init_nets, infer_modalities, infer_results_for_wsi,
|
|
|
14
14
|
from deepliif.util import allowed_file, Visualizer, get_information, test_diff_original_serialized, disable_batchnorm_tracking_stats
|
|
15
15
|
from deepliif.util.util import mkdirs, check_multi_scale
|
|
16
16
|
# from deepliif.util import infer_results_for_wsi
|
|
17
|
-
from deepliif.options import Options
|
|
17
|
+
from deepliif.options import Options, print_options
|
|
18
18
|
|
|
19
19
|
import torch.distributed as dist
|
|
20
20
|
|
|
@@ -59,29 +59,6 @@ def set_seed(seed=0,rank=None):
|
|
|
59
59
|
def ensure_exists(d):
|
|
60
60
|
if not os.path.exists(d):
|
|
61
61
|
os.makedirs(d)
|
|
62
|
-
|
|
63
|
-
def print_options(opt):
|
|
64
|
-
"""Print and save options
|
|
65
|
-
|
|
66
|
-
It will print both current options and default values(if different).
|
|
67
|
-
It will save options into a text file / [checkpoints_dir] / opt.txt
|
|
68
|
-
"""
|
|
69
|
-
message = ''
|
|
70
|
-
message += '----------------- Options ---------------\n'
|
|
71
|
-
for k, v in sorted(vars(opt).items()):
|
|
72
|
-
comment = ''
|
|
73
|
-
message += '{:>25}: {:<30}{}\n'.format(str(k), str(v), comment)
|
|
74
|
-
message += '----------------- End -------------------'
|
|
75
|
-
print(message)
|
|
76
|
-
|
|
77
|
-
# save to the disk
|
|
78
|
-
if opt.phase == 'train':
|
|
79
|
-
expr_dir = os.path.join(opt.checkpoints_dir, opt.name)
|
|
80
|
-
mkdirs(expr_dir)
|
|
81
|
-
file_name = os.path.join(expr_dir, '{}_opt.txt'.format(opt.phase))
|
|
82
|
-
with open(file_name, 'wt') as opt_file:
|
|
83
|
-
opt_file.write(message)
|
|
84
|
-
opt_file.write('\n')
|
|
85
62
|
|
|
86
63
|
|
|
87
64
|
@click.group()
|
|
@@ -212,6 +189,18 @@ def train(dataroot, name, gpu_ids, checkpoints_dir, input_nc, output_nc, ngf, nd
|
|
|
212
189
|
plot, and save models.The script supports continue/resume training.
|
|
213
190
|
Use '--continue_train' to resume your previous training.
|
|
214
191
|
"""
|
|
192
|
+
assert model in ['DeepLIIF','DeepLIIFExt','SDG'], f'model class {model} is not implemented'
|
|
193
|
+
if model == 'DeepLIIF':
|
|
194
|
+
seg_no = 1
|
|
195
|
+
elif model == 'DeepLIIFExt':
|
|
196
|
+
if seg_gen:
|
|
197
|
+
seg_no = modalities_no
|
|
198
|
+
else:
|
|
199
|
+
seg_no = 0
|
|
200
|
+
else: # SDG
|
|
201
|
+
seg_no = 0
|
|
202
|
+
seg_gen = False
|
|
203
|
+
|
|
215
204
|
d_params = locals()
|
|
216
205
|
|
|
217
206
|
if gpu_ids and gpu_ids[0] == -1:
|
|
@@ -241,11 +230,26 @@ def train(dataroot, name, gpu_ids, checkpoints_dir, input_nc, output_nc, ngf, nd
|
|
|
241
230
|
d_params['padding'] = 'zero'
|
|
242
231
|
print('padding type is forced to zero padding, because neither refection pad2d or replication pad2d has a deterministic implementation')
|
|
243
232
|
|
|
233
|
+
# infer number of input images
|
|
234
|
+
dir_data_train = dataroot + '/train'
|
|
235
|
+
fns = os.listdir(dir_data_train)
|
|
236
|
+
fns = [x for x in fns if x.endswith('.png')]
|
|
237
|
+
img = Image.open(f"{dir_data_train}/{fns[0]}")
|
|
238
|
+
|
|
239
|
+
num_img = img.size[0] / img.size[1]
|
|
240
|
+
assert int(num_img) == num_img, f'img size {img.size[0]} / {img.size[1]} = {num_img} is not an integer'
|
|
241
|
+
num_img = int(num_img)
|
|
242
|
+
|
|
243
|
+
input_no = num_img - modalities_no - seg_no
|
|
244
|
+
assert input_no > 0, f'inferred number of input images is {input_no}; should be greater than 0'
|
|
245
|
+
d_params['input_no'] = input_no
|
|
246
|
+
d_params['scale_size'] = img.size[1]
|
|
247
|
+
|
|
244
248
|
# create a dataset given dataset_mode and other options
|
|
245
249
|
# dataset = AlignedDataset(opt)
|
|
246
250
|
|
|
247
251
|
opt = Options(d_params=d_params)
|
|
248
|
-
print_options(opt)
|
|
252
|
+
print_options(opt, save=True)
|
|
249
253
|
|
|
250
254
|
dataset = create_dataset(opt)
|
|
251
255
|
# get the number of images in the dataset.
|
|
@@ -468,28 +472,30 @@ def trainlaunch(**kwargs):
|
|
|
468
472
|
|
|
469
473
|
|
|
470
474
|
@cli.command()
|
|
471
|
-
@click.option('--
|
|
475
|
+
@click.option('--model-dir', default='./model-server/DeepLIIF_Latest_Model', help='reads models from here')
|
|
472
476
|
@click.option('--output-dir', help='saves results here.')
|
|
473
|
-
|
|
474
|
-
@click.option('--device', default='cpu', type=str, help='device to load model, either cpu or gpu')
|
|
477
|
+
#@click.option('--tile-size', type=int, default=None, help='tile size')
|
|
478
|
+
@click.option('--device', default='cpu', type=str, help='device to load model for the similarity test, either cpu or gpu')
|
|
475
479
|
@click.option('--verbose', default=0, type=int,help='saves results here.')
|
|
476
|
-
def serialize(
|
|
480
|
+
def serialize(model_dir, output_dir, device, verbose):
|
|
477
481
|
"""Serialize DeepLIIF models using Torchscript
|
|
478
482
|
"""
|
|
479
|
-
if tile_size is None:
|
|
480
|
-
|
|
481
|
-
output_dir = output_dir or
|
|
483
|
+
#if tile_size is None:
|
|
484
|
+
# tile_size = 512
|
|
485
|
+
output_dir = output_dir or model_dir
|
|
482
486
|
ensure_exists(output_dir)
|
|
483
487
|
|
|
484
488
|
# copy train_opt.txt to the target location
|
|
485
489
|
import shutil
|
|
486
|
-
if
|
|
487
|
-
shutil.copy(f'{
|
|
490
|
+
if model_dir != output_dir:
|
|
491
|
+
shutil.copy(f'{model_dir}/train_opt.txt',f'{output_dir}/train_opt.txt')
|
|
488
492
|
|
|
489
|
-
|
|
493
|
+
opt = Options(path_file=os.path.join(model_dir,'train_opt.txt'), mode='test')
|
|
494
|
+
sample = transform(Image.new('RGB', (opt.scale_size, opt.scale_size)))
|
|
495
|
+
sample = torch.cat([sample]*opt.input_no, 1)
|
|
490
496
|
|
|
491
497
|
with click.progressbar(
|
|
492
|
-
init_nets(
|
|
498
|
+
init_nets(model_dir, eager_mode=True, phase='test').items(),
|
|
493
499
|
label='Tracing nets',
|
|
494
500
|
item_show_func=lambda n: n[0] if n else n
|
|
495
501
|
) as bar:
|
|
@@ -508,7 +514,7 @@ def serialize(models_dir, output_dir, tile_size, device, verbose):
|
|
|
508
514
|
|
|
509
515
|
# test: whether the original and the serialized model produces highly similar predictions
|
|
510
516
|
print('testing similarity between prediction from original vs serialized models...')
|
|
511
|
-
models_original = init_nets(
|
|
517
|
+
models_original = init_nets(model_dir,eager_mode=True,phase='test')
|
|
512
518
|
models_serialized = init_nets(output_dir,eager_mode=False,phase='test')
|
|
513
519
|
if device == 'gpu':
|
|
514
520
|
sample = sample.cuda()
|
|
@@ -590,11 +596,12 @@ def test(input_dir, output_dir, tile_size, model_dir, gpu_ids, region_size, eage
|
|
|
590
596
|
filename.replace('.' + filename.split('.')[-1], f'_{name}.png')
|
|
591
597
|
))
|
|
592
598
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
599
|
+
if scoring is not None:
|
|
600
|
+
with open(os.path.join(
|
|
601
|
+
output_dir,
|
|
602
|
+
filename.replace('.' + filename.split('.')[-1], f'.json')
|
|
603
|
+
), 'w') as f:
|
|
604
|
+
json.dump(scoring, f, indent=2)
|
|
598
605
|
|
|
599
606
|
@cli.command()
|
|
600
607
|
@click.option('--input-dir', type=str, required=True, help='Path to input images')
|
deepliif/data/aligned_dataset.py
CHANGED
|
@@ -26,6 +26,8 @@ class AlignedDataset(BaseDataset):
|
|
|
26
26
|
self.output_nc = opt.input_nc if opt.direction == 'BtoA' else opt.output_nc
|
|
27
27
|
self.no_flip = opt.no_flip
|
|
28
28
|
self.modalities_no = opt.modalities_no
|
|
29
|
+
self.seg_no = opt.seg_no
|
|
30
|
+
self.input_no = opt.input_no
|
|
29
31
|
self.seg_gen = opt.seg_gen
|
|
30
32
|
self.load_size = opt.load_size
|
|
31
33
|
self.crop_size = opt.crop_size
|
|
@@ -52,6 +54,8 @@ class AlignedDataset(BaseDataset):
|
|
|
52
54
|
num_img = self.modalities_no + 1 + 1 # +1 for segmentation channel, +1 for input image
|
|
53
55
|
elif self.model == 'DeepLIIFExt':
|
|
54
56
|
num_img = self.modalities_no * 2 + 1 if self.seg_gen else self.modalities_no + 1 # +1 for segmentation channel
|
|
57
|
+
elif self.model == 'SDG':
|
|
58
|
+
num_img = self.modalities_no + self.seg_no + self.input_no
|
|
55
59
|
else:
|
|
56
60
|
raise Exception(f'model class {self.model} does not have corresponding implementation in deepliif/data/aligned_dataset.py')
|
|
57
61
|
w2 = int(w / num_img)
|
|
@@ -85,6 +89,19 @@ class AlignedDataset(BaseDataset):
|
|
|
85
89
|
BS_Array.append(BS)
|
|
86
90
|
|
|
87
91
|
return {'A': A, 'B': B_Array, 'BS': BS_Array,'A_paths': AB_path, 'B_paths': AB_path}
|
|
92
|
+
elif self.model == 'SDG':
|
|
93
|
+
A_Array = []
|
|
94
|
+
for i in range(self.input_no):
|
|
95
|
+
A = AB.crop((w2 * i, 0, w2 * (i+1), h))
|
|
96
|
+
A = A_transform(A)
|
|
97
|
+
A_Array.append(A)
|
|
98
|
+
|
|
99
|
+
for i in range(self.input_no, self.input_no + self.modalities_no + 1):
|
|
100
|
+
B = AB.crop((w2 * i, 0, w2 * (i + 1), h))
|
|
101
|
+
B = B_transform(B)
|
|
102
|
+
B_Array.append(B)
|
|
103
|
+
|
|
104
|
+
return {'A': A_Array, 'B': B_Array, 'A_paths': AB_path, 'B_paths': AB_path}
|
|
88
105
|
else:
|
|
89
106
|
raise Exception(f'model class {self.model} does not have corresponding implementation in deepliif/data/aligned_dataset.py')
|
|
90
107
|
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
from .base_model import BaseModel
|
|
3
|
+
from . import networks
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SDGModel(BaseModel):
|
|
7
|
+
""" This class implements the Synthetic Data Generation model (based on DeepLIIFExt), for learning a mapping from input images to modalities given paired data."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, opt):
|
|
10
|
+
"""Initialize the DeepLIIF class.
|
|
11
|
+
|
|
12
|
+
Parameters:
|
|
13
|
+
opt (Option class)-- stores all the experiment flags; needs to be a subclass of BaseOptions
|
|
14
|
+
"""
|
|
15
|
+
BaseModel.__init__(self, opt)
|
|
16
|
+
|
|
17
|
+
self.mod_gen_no = self.opt.modalities_no
|
|
18
|
+
|
|
19
|
+
# weights of the modalities in generating segmentation mask
|
|
20
|
+
self.seg_weights = [0, 0, 0]
|
|
21
|
+
if opt.seg_gen:
|
|
22
|
+
self.seg_weights = [0.3] * self.mod_gen_no
|
|
23
|
+
self.seg_weights[1] = 0.4
|
|
24
|
+
|
|
25
|
+
# self.seg_weights = opt.seg_weights
|
|
26
|
+
# assert len(self.seg_weights) == self.seg_gen_no, 'The number of the segmentation weights (seg_weights) is not equal to the number of target images (modalities_no)!'
|
|
27
|
+
# print(self.seg_weights)
|
|
28
|
+
# loss weights in calculating the final loss
|
|
29
|
+
self.loss_G_weights = [1 / self.mod_gen_no] * self.mod_gen_no
|
|
30
|
+
self.loss_GS_weights = [1 / self.mod_gen_no] * self.mod_gen_no
|
|
31
|
+
|
|
32
|
+
self.loss_D_weights = [1 / self.mod_gen_no] * self.mod_gen_no
|
|
33
|
+
self.loss_DS_weights = [1 / self.mod_gen_no] * self.mod_gen_no
|
|
34
|
+
|
|
35
|
+
self.loss_names = []
|
|
36
|
+
self.visual_names = ['real_A']
|
|
37
|
+
# specify the training losses you want to print out. The training/test scripts will call <BaseModel.get_current_losses>
|
|
38
|
+
for i in range(1, self.mod_gen_no + 1):
|
|
39
|
+
self.loss_names.extend(['G_GAN_' + str(i), 'G_L1_' + str(i), 'D_real_' + str(i), 'D_fake_' + str(i)])
|
|
40
|
+
self.visual_names.extend(['fake_B_' + str(i), 'real_B_' + str(i)])
|
|
41
|
+
|
|
42
|
+
# specify the images you want to save/display. The training/test scripts will call <BaseModel.get_current_visuals>
|
|
43
|
+
# specify the models you want to save to the disk. The training/test scripts will call <BaseModel.save_networks> and <BaseModel.load_networks>
|
|
44
|
+
if self.is_train:
|
|
45
|
+
self.model_names = []
|
|
46
|
+
for i in range(1, self.mod_gen_no + 1):
|
|
47
|
+
self.model_names.extend(['G_' + str(i), 'D_' + str(i)])
|
|
48
|
+
|
|
49
|
+
else: # during test time, only load G
|
|
50
|
+
self.model_names = []
|
|
51
|
+
for i in range(1, self.mod_gen_no + 1):
|
|
52
|
+
self.model_names.extend(['G_' + str(i)])
|
|
53
|
+
|
|
54
|
+
# define networks (both generator and discriminator)
|
|
55
|
+
self.netG = [None for _ in range(self.mod_gen_no)]
|
|
56
|
+
for i in range(self.mod_gen_no):
|
|
57
|
+
self.netG[i] = networks.define_G(self.opt.input_nc * self.opt.input_no, self.opt.output_nc, self.opt.ngf, self.opt.net_g, self.opt.norm,
|
|
58
|
+
not self.opt.no_dropout, self.opt.init_type, self.opt.init_gain, self.opt.gpu_ids, self.opt.padding)
|
|
59
|
+
print('***************************************')
|
|
60
|
+
print(self.opt.input_nc, self.opt.output_nc, self.opt.ngf, self.opt.net_g, self.opt.norm,
|
|
61
|
+
not self.opt.no_dropout, self.opt.init_type, self.opt.init_gain, self.opt.gpu_ids, self.opt.padding)
|
|
62
|
+
print('***************************************')
|
|
63
|
+
|
|
64
|
+
if self.is_train: # define a discriminator; conditional GANs need to take both input and output images; Therefore, #channels for D is input_nc + output_nc
|
|
65
|
+
self.netD = [None for _ in range(self.mod_gen_no)]
|
|
66
|
+
for i in range(self.mod_gen_no):
|
|
67
|
+
self.netD[i] = networks.define_D(self.opt.input_nc * self.opt.input_no + self.opt.output_nc, self.opt.ndf, self.opt.net_d,
|
|
68
|
+
self.opt.n_layers_D, self.opt.norm, self.opt.init_type, self.opt.init_gain,
|
|
69
|
+
self.opt.gpu_ids)
|
|
70
|
+
|
|
71
|
+
if self.is_train:
|
|
72
|
+
# define loss functions
|
|
73
|
+
self.criterionGAN_mod = networks.GANLoss(self.opt.gan_mode).to(self.device)
|
|
74
|
+
self.criterionGAN_seg = networks.GANLoss(self.opt.gan_mode_s).to(self.device)
|
|
75
|
+
|
|
76
|
+
self.criterionSmoothL1 = torch.nn.SmoothL1Loss()
|
|
77
|
+
|
|
78
|
+
self.criterionVGG = networks.VGGLoss().to(self.device)
|
|
79
|
+
|
|
80
|
+
# initialize optimizers; schedulers will be automatically created by function <BaseModel.setup>.
|
|
81
|
+
params = []
|
|
82
|
+
for i in range(len(self.netG)):
|
|
83
|
+
params += list(self.netG[i].parameters())
|
|
84
|
+
self.optimizer_G = torch.optim.Adam(params, lr=opt.lr, betas=(opt.beta1, 0.999))
|
|
85
|
+
|
|
86
|
+
params = []
|
|
87
|
+
for i in range(len(self.netD)):
|
|
88
|
+
params += list(self.netD[i].parameters())
|
|
89
|
+
self.optimizer_D = torch.optim.Adam(params, lr=opt.lr, betas=(opt.beta1, 0.999))
|
|
90
|
+
|
|
91
|
+
self.optimizers.append(self.optimizer_G)
|
|
92
|
+
self.optimizers.append(self.optimizer_D)
|
|
93
|
+
|
|
94
|
+
def set_input(self, input):
|
|
95
|
+
"""
|
|
96
|
+
Unpack input data from the dataloader and perform necessary pre-processing steps.
|
|
97
|
+
|
|
98
|
+
:param input (dict): include the input image and the output modalities
|
|
99
|
+
"""
|
|
100
|
+
self.real_A_array = input['A']
|
|
101
|
+
As = [A.to(self.device) for A in self.real_A_array]
|
|
102
|
+
self.real_A = torch.cat(As, dim=1) # shape: 1, (3 x input_no), 512, 512
|
|
103
|
+
|
|
104
|
+
self.real_B_array = input['B']
|
|
105
|
+
self.real_B = []
|
|
106
|
+
for i in range(len(self.real_B_array)):
|
|
107
|
+
self.real_B.append(self.real_B_array[i].to(self.device))
|
|
108
|
+
|
|
109
|
+
self.real_concatenated = []
|
|
110
|
+
self.image_paths = input['A_paths']
|
|
111
|
+
|
|
112
|
+
def forward(self):
|
|
113
|
+
"""Run forward pass; called by both functions <optimize_parameters> and <test>."""
|
|
114
|
+
self.fake_B = []
|
|
115
|
+
for i in range(self.mod_gen_no):
|
|
116
|
+
self.fake_B.append(self.netG[i](self.real_A))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def backward_D(self):
|
|
120
|
+
"""Calculate GAN loss for the discriminators"""
|
|
121
|
+
|
|
122
|
+
pred_fake = []
|
|
123
|
+
for i in range(self.mod_gen_no):
|
|
124
|
+
pred_fake.append(self.netD[i](torch.cat((self.real_A, self.fake_B[i]), 1).detach()))
|
|
125
|
+
|
|
126
|
+
self.loss_D_fake = []
|
|
127
|
+
for i in range(self.mod_gen_no):
|
|
128
|
+
self.loss_D_fake.append(self.criterionGAN_mod(pred_fake[i], False))
|
|
129
|
+
|
|
130
|
+
pred_real = []
|
|
131
|
+
for i in range(self.mod_gen_no):
|
|
132
|
+
pred_real.append(self.netD[i](torch.cat((self.real_A, self.real_B[i]), 1)))
|
|
133
|
+
|
|
134
|
+
self.loss_D_real = []
|
|
135
|
+
for i in range(self.mod_gen_no):
|
|
136
|
+
self.loss_D_real.append(self.criterionGAN_mod(pred_real[i], True))
|
|
137
|
+
|
|
138
|
+
# combine losses and calculate gradients
|
|
139
|
+
# self.loss_D = (self.loss_D_fake[0] + self.loss_D_real[0]) * 0.5 * self.loss_D_weights[0]
|
|
140
|
+
self.loss_D = torch.tensor(0., device=self.device)
|
|
141
|
+
for i in range(0, self.mod_gen_no):
|
|
142
|
+
self.loss_D += (self.loss_D_fake[i] + self.loss_D_real[i]) * 0.5 * self.loss_D_weights[i]
|
|
143
|
+
self.loss_D.backward()
|
|
144
|
+
|
|
145
|
+
def backward_G(self):
|
|
146
|
+
"""Calculate GAN and L1 loss for the generator"""
|
|
147
|
+
pred_fake = []
|
|
148
|
+
for i in range(self.mod_gen_no):
|
|
149
|
+
pred_fake.append(self.netD[i](torch.cat((self.real_A, self.fake_B[i]), 1)))
|
|
150
|
+
|
|
151
|
+
self.loss_G_GAN = []
|
|
152
|
+
self.loss_GS_GAN = []
|
|
153
|
+
for i in range(self.mod_gen_no):
|
|
154
|
+
self.loss_G_GAN.append(self.criterionGAN_mod(pred_fake[i], True))
|
|
155
|
+
|
|
156
|
+
# Second, G(A) = B
|
|
157
|
+
self.loss_G_L1 = []
|
|
158
|
+
self.loss_GS_L1 = []
|
|
159
|
+
for i in range(self.mod_gen_no):
|
|
160
|
+
self.loss_G_L1.append(self.criterionSmoothL1(self.fake_B[i], self.real_B[i]) * self.opt.lambda_L1)
|
|
161
|
+
|
|
162
|
+
#self.loss_G_VGG = []
|
|
163
|
+
#for i in range(self.mod_gen_no):
|
|
164
|
+
# self.loss_G_VGG.append(self.criterionVGG(self.fake_B[i], self.real_B[i]) * self.opt.lambda_feat)
|
|
165
|
+
|
|
166
|
+
# self.loss_G = (self.loss_G_GAN[0] + self.loss_G_L1[0]) * self.loss_G_weights[0]
|
|
167
|
+
self.loss_G = torch.tensor(0., device=self.device)
|
|
168
|
+
for i in range(0, self.mod_gen_no):
|
|
169
|
+
self.loss_G += (self.loss_G_GAN[i] + self.loss_G_L1[i]) * self.loss_G_weights[i]
|
|
170
|
+
# self.loss_G += (self.loss_G_GAN[i] + self.loss_G_L1[i] + self.loss_G_VGG[i]) * self.loss_G_weights[i]
|
|
171
|
+
self.loss_G.backward()
|
|
172
|
+
|
|
173
|
+
def optimize_parameters(self):
|
|
174
|
+
self.forward() # compute fake images: G(A)
|
|
175
|
+
# update D
|
|
176
|
+
for i in range(self.mod_gen_no):
|
|
177
|
+
self.set_requires_grad(self.netD[i], True) # enable backprop for D1
|
|
178
|
+
|
|
179
|
+
self.optimizer_D.zero_grad() # set D's gradients to zero
|
|
180
|
+
self.backward_D() # calculate gradients for D
|
|
181
|
+
self.optimizer_D.step() # update D's weights
|
|
182
|
+
|
|
183
|
+
# update G
|
|
184
|
+
for i in range(self.mod_gen_no):
|
|
185
|
+
self.set_requires_grad(self.netD[i], False)
|
|
186
|
+
|
|
187
|
+
self.optimizer_G.zero_grad() # set G's gradients to zero
|
|
188
|
+
self.backward_G() # calculate graidents for G
|
|
189
|
+
self.optimizer_G.step() # udpate G's weights
|
deepliif/models/__init__.py
CHANGED
|
@@ -56,8 +56,11 @@ def get_opt(model_dir, mode='test'):
|
|
|
56
56
|
opt = Options(path_file=os.path.join(model_dir,'test_opt.txt'), mode=mode)
|
|
57
57
|
except:
|
|
58
58
|
opt = Options(path_file=os.path.join(model_dir,'train_opt.txt'), mode=mode)
|
|
59
|
+
opt.use_dp = False
|
|
60
|
+
opt.gpu_ids = list(range(torch.cuda.device_count()))
|
|
59
61
|
return opt
|
|
60
62
|
|
|
63
|
+
|
|
61
64
|
def find_model_using_name(model_name):
|
|
62
65
|
"""Import the module "models/[model_name]_model.py".
|
|
63
66
|
|
|
@@ -130,6 +133,10 @@ def load_eager_models(opt, devices):
|
|
|
130
133
|
net.eval()
|
|
131
134
|
net = disable_batchnorm_tracking_stats(net)
|
|
132
135
|
|
|
136
|
+
# SDG models when loaded are still DP.. not sure why
|
|
137
|
+
if isinstance(net, torch.nn.DataParallel):
|
|
138
|
+
net = net.module
|
|
139
|
+
|
|
133
140
|
nets[name] = net
|
|
134
141
|
nets[name].to(devices[name])
|
|
135
142
|
|
|
@@ -148,7 +155,7 @@ def init_nets(model_dir, eager_mode=False, opt=None, phase='test'):
|
|
|
148
155
|
if opt is None:
|
|
149
156
|
opt = get_opt(model_dir, mode=phase)
|
|
150
157
|
opt.use_dp = False
|
|
151
|
-
print_options(opt)
|
|
158
|
+
#print_options(opt)
|
|
152
159
|
|
|
153
160
|
if opt.model == 'DeepLIIF':
|
|
154
161
|
net_groups = [
|
|
@@ -158,7 +165,7 @@ def init_nets(model_dir, eager_mode=False, opt=None, phase='test'):
|
|
|
158
165
|
('G4', 'G55'),
|
|
159
166
|
('G51',)
|
|
160
167
|
]
|
|
161
|
-
elif opt.model
|
|
168
|
+
elif opt.model in ['DeepLIIFExt','SDG']:
|
|
162
169
|
if opt.seg_gen:
|
|
163
170
|
net_groups = [(f'G_{i+1}',f'GS_{i+1}') for i in range(opt.modalities_no)]
|
|
164
171
|
else:
|
|
@@ -168,7 +175,8 @@ def init_nets(model_dir, eager_mode=False, opt=None, phase='test'):
|
|
|
168
175
|
|
|
169
176
|
number_of_gpus_all = torch.cuda.device_count()
|
|
170
177
|
number_of_gpus = len(opt.gpu_ids)
|
|
171
|
-
print(number_of_gpus)
|
|
178
|
+
#print(number_of_gpus)
|
|
179
|
+
|
|
172
180
|
if number_of_gpus > 0:
|
|
173
181
|
mapping_gpu_ids = {i:idx for i,idx in enumerate(opt.gpu_ids)}
|
|
174
182
|
chunks = [itertools.chain.from_iterable(c) for c in chunker(net_groups, number_of_gpus)]
|
|
@@ -203,10 +211,7 @@ def run_torchserve(img, model_path=None, eager_mode=False, opt=None):
|
|
|
203
211
|
opt: same as eager_mode
|
|
204
212
|
"""
|
|
205
213
|
buffer = BytesIO()
|
|
206
|
-
|
|
207
|
-
torch.save(transform(img.resize((1024, 1024))), buffer)
|
|
208
|
-
else:
|
|
209
|
-
torch.save(transform(img.resize((512, 512))), buffer)
|
|
214
|
+
torch.save(transform(img.resize((opt.scale_size, opt.scale_size))), buffer)
|
|
210
215
|
|
|
211
216
|
torchserve_host = os.getenv('TORCHSERVE_HOST', 'http://localhost')
|
|
212
217
|
res = requests.post(
|
|
@@ -226,10 +231,12 @@ def run_dask(img, model_path, eager_mode=False, opt=None):
|
|
|
226
231
|
model_dir = os.getenv('DEEPLIIF_MODEL_DIR', model_path)
|
|
227
232
|
nets = init_nets(model_dir, eager_mode, opt)
|
|
228
233
|
|
|
229
|
-
if opt.model == '
|
|
230
|
-
|
|
234
|
+
if opt.input_no > 1 or opt.model == 'SDG':
|
|
235
|
+
l_ts = [transform(img_i.resize((opt.scale_size,opt.scale_size))) for img_i in img]
|
|
236
|
+
ts = torch.cat(l_ts, dim=1)
|
|
231
237
|
else:
|
|
232
|
-
ts = transform(img.resize((
|
|
238
|
+
ts = transform(img.resize((opt.scale_size, opt.scale_size)))
|
|
239
|
+
|
|
233
240
|
|
|
234
241
|
@delayed
|
|
235
242
|
def forward(input, model):
|
|
@@ -246,14 +253,20 @@ def run_dask(img, model_path, eager_mode=False, opt=None):
|
|
|
246
253
|
lazy_segs['G51'] = forward(ts, nets['G51']).to(torch.device('cpu'))
|
|
247
254
|
segs = compute(lazy_segs)[0]
|
|
248
255
|
|
|
249
|
-
|
|
250
|
-
|
|
256
|
+
weights = {
|
|
257
|
+
'G51': 0.25, # IHC
|
|
258
|
+
'G52': 0.25, # Hema
|
|
259
|
+
'G53': 0.25, # DAPI
|
|
260
|
+
'G54': 0.00, # Lap2
|
|
261
|
+
'G55': 0.25, # Marker
|
|
262
|
+
}
|
|
263
|
+
seg = torch.stack([torch.mul(segs[k], weights[k]) for k in segs.keys()]).sum(dim=0)
|
|
251
264
|
|
|
252
265
|
res = {k: tensor_to_pil(v) for k, v in gens.items()}
|
|
253
266
|
res['G5'] = tensor_to_pil(seg)
|
|
254
267
|
|
|
255
268
|
return res
|
|
256
|
-
elif opt.model
|
|
269
|
+
elif opt.model in ['DeepLIIFExt','SDG']:
|
|
257
270
|
seg_map = {'G_' + str(i): 'GS_' + str(i) for i in range(1, opt.modalities_no + 1)}
|
|
258
271
|
|
|
259
272
|
lazy_gens = {k: forward(ts, nets[k]) for k in seg_map}
|
|
@@ -268,14 +281,22 @@ def run_dask(img, model_path, eager_mode=False, opt=None):
|
|
|
268
281
|
|
|
269
282
|
return res
|
|
270
283
|
else:
|
|
271
|
-
raise Exception(f'run_dask() not implemented for {opt.model}')
|
|
284
|
+
raise Exception(f'run_dask() not fully implemented for {opt.model}')
|
|
272
285
|
|
|
273
|
-
|
|
286
|
+
|
|
287
|
+
def is_empty_old(tile):
|
|
288
|
+
# return True if np.mean(np.array(tile) - np.array(mean_background_val)) < 40 else False
|
|
289
|
+
if isinstance(tile, list): # for pair of tiles, only mark it as empty / no need for prediction if ALL tiles are empty
|
|
290
|
+
return all([True if calculate_background_area(t) > 98 else False for t in tile])
|
|
291
|
+
else:
|
|
292
|
+
return True if calculate_background_area(tile) > 98 else False
|
|
274
293
|
|
|
275
294
|
|
|
276
295
|
def is_empty(tile):
|
|
277
|
-
|
|
278
|
-
|
|
296
|
+
if isinstance(tile, list): # for pair of tiles, only mark it as empty / no need for prediction if ALL tiles are empty
|
|
297
|
+
return all([True if np.max(image_variance_rgb(tile)) < 15 else False for t in tile])
|
|
298
|
+
else:
|
|
299
|
+
return True if np.max(image_variance_rgb(tile)) < 15 else False
|
|
279
300
|
|
|
280
301
|
|
|
281
302
|
def run_wrapper(tile, run_fn, model_path, eager_mode=False, opt=None):
|
|
@@ -290,7 +311,7 @@ def run_wrapper(tile, run_fn, model_path, eager_mode=False, opt=None):
|
|
|
290
311
|
}
|
|
291
312
|
else:
|
|
292
313
|
return run_fn(tile, model_path, eager_mode, opt)
|
|
293
|
-
elif opt.model
|
|
314
|
+
elif opt.model in ['DeepLIIFExt', 'SDG']:
|
|
294
315
|
if is_empty(tile):
|
|
295
316
|
res = {'G_' + str(i): Image.new(mode='RGB', size=(512, 512)) for i in range(1, opt.modalities_no + 1)}
|
|
296
317
|
res.update({'GS_' + str(i): Image.new(mode='RGB', size=(512, 512)) for i in range(1, opt.modalities_no + 1)})
|
|
@@ -351,7 +372,7 @@ def inference(img, tile_size, overlap_size, model_path, use_torchserve=False, ea
|
|
|
351
372
|
color_dapi=False, color_marker=False, opt=None):
|
|
352
373
|
if not opt:
|
|
353
374
|
opt = get_opt(model_path)
|
|
354
|
-
print_options(opt)
|
|
375
|
+
#print_options(opt)
|
|
355
376
|
|
|
356
377
|
if opt.model == 'DeepLIIF':
|
|
357
378
|
rescaled, rows, cols = format_image_for_tiling(img, tile_size, overlap_size)
|
|
@@ -359,28 +380,25 @@ def inference(img, tile_size, overlap_size, model_path, use_torchserve=False, ea
|
|
|
359
380
|
run_fn = run_torchserve if use_torchserve else run_dask
|
|
360
381
|
|
|
361
382
|
images = {}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
383
|
+
d_modality2net = {'Hema':'G1',
|
|
384
|
+
'DAPI':'G2',
|
|
385
|
+
'Lap2':'G3',
|
|
386
|
+
'Marker':'G4',
|
|
387
|
+
'Seg':'G5'}
|
|
388
|
+
|
|
389
|
+
for k in d_modality2net.keys():
|
|
390
|
+
images[k] = create_image_for_stitching(tile_size, rows, cols)
|
|
367
391
|
|
|
368
392
|
for i in range(cols):
|
|
369
393
|
for j in range(rows):
|
|
370
394
|
tile = extract_tile(rescaled, tile_size, overlap_size, i, j)
|
|
371
395
|
res = run_wrapper(tile, run_fn, model_path, eager_mode, opt)
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
images['Hema'] = images['Hema'].resize(img.size)
|
|
380
|
-
images['DAPI'] = images['DAPI'].resize(img.size)
|
|
381
|
-
images['Lap2'] = images['Lap2'].resize(img.size)
|
|
382
|
-
images['Marker'] = images['Marker'].resize(img.size)
|
|
383
|
-
images['Seg'] = images['Seg'].resize(img.size)
|
|
396
|
+
|
|
397
|
+
for modality_name, net_name in d_modality2net.items():
|
|
398
|
+
stitch_tile(images[modality_name], res[net_name], tile_size, overlap_size, i, j)
|
|
399
|
+
|
|
400
|
+
for modality_name, output_img in images.items():
|
|
401
|
+
images[modality_name] = output_img.resize(img.size)
|
|
384
402
|
|
|
385
403
|
if color_dapi:
|
|
386
404
|
matrix = ( 0, 0, 0, 0,
|
|
@@ -400,42 +418,88 @@ def inference(img, tile_size, overlap_size, model_path, use_torchserve=False, ea
|
|
|
400
418
|
#param_dict = read_train_options(model_path)
|
|
401
419
|
#modalities_no = int(param_dict['modalities_no']) if param_dict else 4
|
|
402
420
|
#seg_gen = (param_dict['seg_gen'] == 'True') if param_dict else True
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
rescaled, rows, cols = format_image_for_tiling(img, tile_size, overlap_size)
|
|
406
424
|
run_fn = run_torchserve if use_torchserve else run_dask
|
|
407
|
-
res = [Tile(t.i, t.j, run_wrapper(t.img, run_fn, model_path, eager_mode, opt)) for t in tiles]
|
|
408
425
|
|
|
409
426
|
def get_net_tiles(n):
|
|
410
427
|
return [Tile(t.i, t.j, t.img[n]) for t in res]
|
|
411
428
|
|
|
412
429
|
images = {}
|
|
413
|
-
|
|
414
|
-
for i in range(1, opt.modalities_no + 1):
|
|
415
|
-
images['mod' + str(i)] = stitch(get_net_tiles('G_' + str(i)), tile_size, overlap_size).resize(img.size)
|
|
416
|
-
|
|
430
|
+
d_modality2net = {f'mod{i}':f'G_{i}' for i in range(1, opt.modalities_no + 1)}
|
|
417
431
|
if opt.seg_gen:
|
|
418
|
-
for i in range(1, opt.modalities_no + 1)
|
|
419
|
-
|
|
432
|
+
d_modality2net.update({f'Seg{i}':f'GS_{i}' for i in range(1, opt.modalities_no + 1)})
|
|
433
|
+
|
|
434
|
+
for k in d_modality2net.keys():
|
|
435
|
+
images[k] = create_image_for_stitching(tile_size, rows, cols)
|
|
420
436
|
|
|
437
|
+
for i in range(cols):
|
|
438
|
+
for j in range(rows):
|
|
439
|
+
tile = extract_tile(rescaled, tile_size, overlap_size, i, j)
|
|
440
|
+
res = run_wrapper(tile, run_fn, model_path, eager_mode, opt)
|
|
441
|
+
|
|
442
|
+
for modality_name, net_name in d_modality2net.items():
|
|
443
|
+
stitch_tile(images[modality_name], res[net_name], tile_size, overlap_size, i, j)
|
|
444
|
+
|
|
445
|
+
for modality_name, output_img in images.items():
|
|
446
|
+
images[modality_name] = output_img.resize(img.size)
|
|
447
|
+
|
|
448
|
+
return images
|
|
449
|
+
|
|
450
|
+
elif opt.model == 'SDG':
|
|
451
|
+
# SDG could have multiple input images / modalities
|
|
452
|
+
# the input hence could be a rectangle
|
|
453
|
+
# we split the input to get each modality image one by one
|
|
454
|
+
# then create tiles for each of the modality images
|
|
455
|
+
# tile_pair is a list that contains the tiles at the given location for each modality image
|
|
456
|
+
# l_tile_pair is a list of tile_pair that covers all locations
|
|
457
|
+
# for inference, each tile_pair is used to get the output at the given location
|
|
458
|
+
w, h = img.size
|
|
459
|
+
w2 = int(w / opt.input_no)
|
|
460
|
+
|
|
461
|
+
l_img = []
|
|
462
|
+
for i in range(opt.input_no):
|
|
463
|
+
img_i = img.crop((w2 * i, 0, w2 * (i+1), h))
|
|
464
|
+
rescaled_img_i, rows, cols = format_image_for_tiling(img_i, tile_size, overlap_size)
|
|
465
|
+
l_img.append(rescaled_img_i)
|
|
466
|
+
|
|
467
|
+
run_fn = run_torchserve if use_torchserve else run_dask
|
|
468
|
+
|
|
469
|
+
images = {}
|
|
470
|
+
d_modality2net = {f'mod{i}':f'G_{i}' for i in range(1, opt.modalities_no + 1)}
|
|
471
|
+
for k in d_modality2net.keys():
|
|
472
|
+
images[k] = create_image_for_stitching(tile_size, rows, cols)
|
|
473
|
+
|
|
474
|
+
for i in range(cols):
|
|
475
|
+
for j in range(rows):
|
|
476
|
+
tile_pair = [extract_tile(rescaled, tile_size, overlap_size, i, j) for rescaled in l_img]
|
|
477
|
+
res = run_wrapper(tile_pair, run_fn, model_path, eager_mode, opt)
|
|
478
|
+
|
|
479
|
+
for modality_name, net_name in d_modality2net.items():
|
|
480
|
+
stitch_tile(images[modality_name], res[net_name], tile_size, overlap_size, i, j)
|
|
481
|
+
|
|
482
|
+
for modality_name, output_img in images.items():
|
|
483
|
+
images[modality_name] = output_img.resize((w2,w2))
|
|
484
|
+
|
|
421
485
|
return images
|
|
422
486
|
|
|
423
487
|
else:
|
|
424
488
|
raise Exception(f'inference() not implemented for model {opt.model}')
|
|
425
489
|
|
|
426
490
|
|
|
427
|
-
def postprocess(orig, images, tile_size, seg_thresh=150, size_thresh='
|
|
428
|
-
if
|
|
491
|
+
def postprocess(orig, images, tile_size, model, seg_thresh=150, size_thresh='auto', marker_thresh='auto', size_thresh_upper=None):
|
|
492
|
+
if model == 'DeepLIIF':
|
|
429
493
|
resolution = '40x' if tile_size > 384 else ('20x' if tile_size > 192 else '10x')
|
|
430
494
|
overlay, refined, scoring = compute_results(np.array(orig), np.array(images['Seg']),
|
|
431
|
-
np.array(images['Marker'].convert('L'))
|
|
432
|
-
seg_thresh, size_thresh, marker_thresh, size_thresh_upper)
|
|
495
|
+
np.array(images['Marker'].convert('L')) if 'Marker' in images else None,
|
|
496
|
+
resolution, seg_thresh, size_thresh, marker_thresh, size_thresh_upper)
|
|
433
497
|
processed_images = {}
|
|
434
498
|
processed_images['SegOverlaid'] = Image.fromarray(overlay)
|
|
435
499
|
processed_images['SegRefined'] = Image.fromarray(refined)
|
|
436
500
|
return processed_images, scoring
|
|
437
501
|
|
|
438
|
-
elif
|
|
502
|
+
elif model == 'DeepLIIFExt':
|
|
439
503
|
resolution = '40x' if tile_size > 768 else ('20x' if tile_size > 384 else '10x')
|
|
440
504
|
processed_images = {}
|
|
441
505
|
scoring = {}
|
|
@@ -452,7 +516,7 @@ def postprocess(orig, images, tile_size, seg_thresh=150, size_thresh='default',
|
|
|
452
516
|
return processed_images, scoring
|
|
453
517
|
|
|
454
518
|
else:
|
|
455
|
-
raise Exception(f'postprocess() not implemented for model {
|
|
519
|
+
raise Exception(f'postprocess() not implemented for model {model}')
|
|
456
520
|
|
|
457
521
|
|
|
458
522
|
def infer_modalities(img, tile_size, model_dir, eager_mode=False,
|
|
@@ -467,17 +531,21 @@ def infer_modalities(img, tile_size, model_dir, eager_mode=False,
|
|
|
467
531
|
if opt is None:
|
|
468
532
|
opt = get_opt(model_dir)
|
|
469
533
|
opt.use_dp = False
|
|
470
|
-
print_options(opt)
|
|
534
|
+
#print_options(opt)
|
|
471
535
|
|
|
472
536
|
if not tile_size:
|
|
473
537
|
tile_size = check_multi_scale(Image.open('./images/target.png').convert('L'),
|
|
474
538
|
img.convert('L'))
|
|
475
539
|
tile_size = int(tile_size)
|
|
540
|
+
|
|
541
|
+
# for those with multiple input modalities, find the correct size to calculate overlap_size
|
|
542
|
+
input_no = opt.input_no if hasattr(opt, 'input_no') else 1
|
|
543
|
+
img_size = (img.size[0] / input_no, img.size[1]) # (width, height)
|
|
476
544
|
|
|
477
545
|
images = inference(
|
|
478
546
|
img,
|
|
479
547
|
tile_size=tile_size,
|
|
480
|
-
overlap_size=compute_overlap(
|
|
548
|
+
overlap_size=compute_overlap(img_size, tile_size),
|
|
481
549
|
model_path=model_dir,
|
|
482
550
|
eager_mode=eager_mode,
|
|
483
551
|
color_dapi=color_dapi,
|
|
@@ -486,7 +554,7 @@ def infer_modalities(img, tile_size, model_dir, eager_mode=False,
|
|
|
486
554
|
)
|
|
487
555
|
|
|
488
556
|
if not hasattr(opt,'seg_gen') or (hasattr(opt,'seg_gen') and opt.seg_gen): # the first condition accounts for old settings of deepliif; the second refers to deepliifext models
|
|
489
|
-
post_images, scoring = postprocess(img, images, tile_size, opt
|
|
557
|
+
post_images, scoring = postprocess(img, images, tile_size, opt.model)
|
|
490
558
|
images = {**images, **post_images}
|
|
491
559
|
return images, scoring
|
|
492
560
|
else:
|
deepliif/options/__init__.py
CHANGED
|
@@ -13,7 +13,6 @@ def read_model_params(file_addr):
|
|
|
13
13
|
key = line.split(':')[0].strip()
|
|
14
14
|
val = line.split(':')[1].split('[')[0].strip()
|
|
15
15
|
param_dict[key] = val
|
|
16
|
-
print(param_dict)
|
|
17
16
|
return param_dict
|
|
18
17
|
|
|
19
18
|
class Options:
|
|
@@ -32,19 +31,7 @@ class Options:
|
|
|
32
31
|
else:
|
|
33
32
|
setattr(self,k,v)
|
|
34
33
|
except:
|
|
35
|
-
setattr(self,k,v)
|
|
36
|
-
|
|
37
|
-
if mode != 'train':
|
|
38
|
-
# to account for old settings where gpu_ids value is an integer, not a tuple
|
|
39
|
-
if isinstance(self.gpu_ids,int):
|
|
40
|
-
self.gpu_ids = (self.gpu_ids,)
|
|
41
|
-
|
|
42
|
-
# to account for old settings before modalities_no was introduced
|
|
43
|
-
if not hasattr(self,'modalities_no') and hasattr(self,'targets_no'):
|
|
44
|
-
self.modalities_no = self.targets_no - 1
|
|
45
|
-
del self.targets_no
|
|
46
|
-
|
|
47
|
-
|
|
34
|
+
setattr(self,k,v)
|
|
48
35
|
|
|
49
36
|
if mode == 'train':
|
|
50
37
|
self.is_train = True
|
|
@@ -71,32 +58,78 @@ class Options:
|
|
|
71
58
|
self.checkpoints_dir = str(model_dir.parent)
|
|
72
59
|
self.name = str(model_dir.name)
|
|
73
60
|
|
|
74
|
-
self.gpu_ids = [] # gpu_ids is only used by eager mode, set to empty / cpu to be the same as the old settings; non-eager mode will use all gpus
|
|
61
|
+
#self.gpu_ids = [] # gpu_ids is only used by eager mode, set to empty / cpu to be the same as the old settings; non-eager mode will use all gpus
|
|
62
|
+
|
|
63
|
+
# to account for old settings where gpu_ids value is an integer, not a tuple
|
|
64
|
+
if isinstance(self.gpu_ids,int):
|
|
65
|
+
self.gpu_ids = (self.gpu_ids,)
|
|
66
|
+
|
|
67
|
+
# to account for old settings before modalities_no was introduced
|
|
68
|
+
if not hasattr(self,'modalities_no') and hasattr(self,'targets_no'):
|
|
69
|
+
self.modalities_no = self.targets_no - 1
|
|
70
|
+
del self.targets_no
|
|
71
|
+
|
|
72
|
+
# to account for old settings: same as in cli.py train
|
|
73
|
+
if not hasattr(self,'seg_no'):
|
|
74
|
+
if self.model == 'DeepLIIF':
|
|
75
|
+
self.seg_no = 1
|
|
76
|
+
elif self.model == 'DeepLIIFExt':
|
|
77
|
+
if self.seg_gen:
|
|
78
|
+
self.seg_no = self.modalities_no
|
|
79
|
+
else:
|
|
80
|
+
self.seg_no = 0
|
|
81
|
+
elif self.model == 'SDG':
|
|
82
|
+
self.seg_no = 0
|
|
83
|
+
self.seg_gen = False
|
|
84
|
+
else:
|
|
85
|
+
raise Exception(f'seg_gen cannot be automatically determined for {opt.model}')
|
|
86
|
+
|
|
87
|
+
# to account for old settings: prior to SDG, our models only have 1 input image
|
|
88
|
+
if not hasattr(self,'input_no'):
|
|
89
|
+
self.input_no = 1
|
|
90
|
+
|
|
91
|
+
# to account for old settings: before adding scale_size
|
|
92
|
+
if not hasattr(self, 'scale_size'):
|
|
93
|
+
if self.model in ['DeepLIIF','SDG']:
|
|
94
|
+
self.scale_size = 512
|
|
95
|
+
elif self.model == 'DeepLIIFExt':
|
|
96
|
+
self.scale_size = 1024
|
|
97
|
+
else:
|
|
98
|
+
raise Exception(f'scale_size cannot be automatically determined for {opt.model}')
|
|
99
|
+
|
|
100
|
+
|
|
75
101
|
|
|
76
102
|
def _get_kwargs(self):
|
|
77
103
|
common_attr = ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
|
|
78
104
|
l_args = [x for x in dir(self) if x not in common_attr]
|
|
79
105
|
return {k:getattr(self,k) for k in l_args}
|
|
80
|
-
|
|
81
|
-
def print_options(opt):
|
|
82
|
-
"""Print and save options
|
|
83
106
|
|
|
84
|
-
|
|
85
|
-
It will save options into a text file / [checkpoints_dir] / opt.txt
|
|
86
|
-
"""
|
|
107
|
+
def format_options(opt):
|
|
87
108
|
message = ''
|
|
88
109
|
message += '----------------- Options ---------------\n'
|
|
89
110
|
for k, v in sorted(vars(opt).items()):
|
|
90
111
|
comment = ''
|
|
91
112
|
message += '{:>25}: {:<30}{}\n'.format(str(k), str(v), comment)
|
|
92
113
|
message += '----------------- End -------------------'
|
|
93
|
-
|
|
114
|
+
return message
|
|
115
|
+
|
|
116
|
+
def print_options(opt, save=False):
|
|
117
|
+
"""Print (and save) options
|
|
94
118
|
|
|
119
|
+
It will print both current options and default values(if different).
|
|
120
|
+
If save=True, it will save options into a text file / [checkpoints_dir] / opt.txt
|
|
121
|
+
"""
|
|
122
|
+
message = format_options(opt)
|
|
123
|
+
print(message)
|
|
124
|
+
|
|
95
125
|
# save to the disk
|
|
96
|
-
if
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
126
|
+
if save:
|
|
127
|
+
save_options(opt)
|
|
128
|
+
|
|
129
|
+
def save_options(opt):
|
|
130
|
+
message = format_options(opt)
|
|
131
|
+
expr_dir = os.path.join(opt.checkpoints_dir, opt.name)
|
|
132
|
+
mkdirs(expr_dir)
|
|
133
|
+
file_name = os.path.join(expr_dir, '{}_opt.txt'.format(opt.phase))
|
|
134
|
+
with open(file_name, 'wt') as opt_file:
|
|
135
|
+
opt_file.write(message+'\n')
|
deepliif/postprocessing.py
CHANGED
|
@@ -401,23 +401,30 @@ def calc_default_marker_thresh(marker):
|
|
|
401
401
|
return 0
|
|
402
402
|
|
|
403
403
|
|
|
404
|
-
def compute_results(orig, seg, marker, resolution=None, seg_thresh=150, size_thresh='
|
|
404
|
+
def compute_results(orig, seg, marker, resolution=None, seg_thresh=150, size_thresh='auto', marker_thresh='auto', size_thresh_upper=None):
|
|
405
405
|
mask = create_posneg_mask(seg, seg_thresh)
|
|
406
406
|
mark_background(mask)
|
|
407
407
|
|
|
408
|
-
if size_thresh == '
|
|
408
|
+
if size_thresh == 'auto':
|
|
409
409
|
size_thresh = calc_default_size_thresh(mask, resolution)
|
|
410
|
-
if marker_thresh
|
|
410
|
+
if marker_thresh is None:
|
|
411
|
+
marker_thresh = 0
|
|
412
|
+
marker = None
|
|
413
|
+
elif marker_thresh == 'auto':
|
|
411
414
|
marker_thresh = calc_default_marker_thresh(marker)
|
|
412
415
|
|
|
413
|
-
counts = compute_cell_classification(mask, marker, size_thresh, marker_thresh, size_thresh_upper
|
|
416
|
+
counts = compute_cell_classification(mask, marker, size_thresh, marker_thresh, size_thresh_upper)
|
|
414
417
|
enlarge_cell_boundaries(mask)
|
|
415
418
|
|
|
416
419
|
scoring = {
|
|
417
420
|
'num_total': counts['num_total'],
|
|
418
421
|
'num_pos': counts['num_pos'],
|
|
419
422
|
'num_neg': counts['num_neg'],
|
|
420
|
-
'percent_pos': round(counts['num_pos'] / counts['num_total'] * 100, 1) if counts['num_pos'] > 0 else 0
|
|
423
|
+
'percent_pos': round(counts['num_pos'] / counts['num_total'] * 100, 1) if counts['num_pos'] > 0 else 0,
|
|
424
|
+
'prob_thresh': seg_thresh,
|
|
425
|
+
'size_thresh': size_thresh,
|
|
426
|
+
'size_thresh_upper': size_thresh_upper,
|
|
427
|
+
'marker_thresh': marker_thresh if marker is not None else None,
|
|
421
428
|
}
|
|
422
429
|
|
|
423
430
|
overlay = np.copy(orig)
|
deepliif/util/__init__.py
CHANGED
|
@@ -164,6 +164,28 @@ def adjust_background_tile(img):
|
|
|
164
164
|
return image
|
|
165
165
|
|
|
166
166
|
|
|
167
|
+
def image_variance_gray(img):
|
|
168
|
+
px = np.asarray(img) if img.mode == 'L' else np.asarray(img.convert('L'))
|
|
169
|
+
idx = np.logical_and(px != 255, px != 0)
|
|
170
|
+
val = px[idx]
|
|
171
|
+
if val.shape[0] == 0:
|
|
172
|
+
return 0
|
|
173
|
+
var = np.var(val)
|
|
174
|
+
return var
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def image_variance_rgb(img):
|
|
178
|
+
px = np.asarray(img) if img.mode == 'RGB' else np.asarray(img.convert('RGB'))
|
|
179
|
+
nonwhite = np.any(px != [255, 255, 255], axis=-1)
|
|
180
|
+
nonblack = np.any(px != [0, 0, 0], axis=-1)
|
|
181
|
+
idx = np.logical_and(nonwhite, nonblack)
|
|
182
|
+
val = px[idx]
|
|
183
|
+
if val.shape[0] == 0:
|
|
184
|
+
return [0, 0, 0]
|
|
185
|
+
var = np.var(val, axis=0)
|
|
186
|
+
return var
|
|
187
|
+
|
|
188
|
+
|
|
167
189
|
def read_bioformats_image_with_reader(path, channel=0, region=(0, 0, 0, 0)):
|
|
168
190
|
"""
|
|
169
191
|
Using this function, you can read a specific region of a large image by giving the region bounding box (XYWH format)
|
deepliif/util/util.py
CHANGED
|
@@ -48,7 +48,23 @@ def save_image(image_numpy, image_path, aspect_ratio=1.0):
|
|
|
48
48
|
image_numpy (numpy array) -- input numpy array
|
|
49
49
|
image_path (str) -- the path of the image
|
|
50
50
|
"""
|
|
51
|
-
|
|
51
|
+
x, y, nc = image_numpy.shape
|
|
52
|
+
|
|
53
|
+
if nc > 3:
|
|
54
|
+
if nc % 3 == 0:
|
|
55
|
+
nc_img = 3
|
|
56
|
+
no_img = nc // nc_img
|
|
57
|
+
|
|
58
|
+
elif nc % 2 == 0:
|
|
59
|
+
nc_img = 2
|
|
60
|
+
no_img = nc // nc_img
|
|
61
|
+
else:
|
|
62
|
+
nc_img = 1
|
|
63
|
+
no_img = nc // nc_img
|
|
64
|
+
print(f'image (numpy) has {nc}>3 channels, inferred to have {no_img} images each with {nc_img} channel(s)')
|
|
65
|
+
l_image_numpy = np.dsplit(image_numpy,[nc_img*i for i in range(1,no_img)])
|
|
66
|
+
image_numpy = np.concatenate(l_image_numpy, axis=1) # stack horizontally
|
|
67
|
+
|
|
52
68
|
image_pil = Image.fromarray(image_numpy)
|
|
53
69
|
h, w, _ = image_numpy.shape
|
|
54
70
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: deepliif
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.10
|
|
4
4
|
Summary: DeepLIIF: Deep-Learning Inferred Multiplex Immunofluorescence for Immunohistochemical Image Quantification
|
|
5
5
|
Home-page: https://github.com/nadeemlab/DeepLIIF
|
|
6
6
|
Author: Parmida93
|
|
@@ -61,6 +61,9 @@ segmentation.*
|
|
|
61
61
|
|
|
62
62
|
© This code is made available for non-commercial academic purposes.
|
|
63
63
|
|
|
64
|
+

|
|
65
|
+
[](https://pepy.tech/project/deepliif?&left_text=totalusers)
|
|
66
|
+
|
|
64
67
|
*Overview of DeepLIIF pipeline and sample input IHCs (different
|
|
65
68
|
brown/DAB markers -- BCL2, BCL6, CD10, CD3/CD8, Ki67) with corresponding DeepLIIF-generated hematoxylin/mpIF modalities
|
|
66
69
|
and classified (positive (red) and negative (blue) cell) segmentation masks. (a) Overview of DeepLIIF. Given an IHC
|
|
@@ -132,7 +135,7 @@ deepliif prepare-training-data --input-dir /path/to/input/images
|
|
|
132
135
|
To train a model:
|
|
133
136
|
```
|
|
134
137
|
deepliif train --dataroot /path/to/input/images
|
|
135
|
-
|
|
138
|
+
--name Model_Name
|
|
136
139
|
```
|
|
137
140
|
or
|
|
138
141
|
```
|
|
@@ -178,7 +181,7 @@ The installed `deepliif` uses Dask to perform inference on the input IHC images.
|
|
|
178
181
|
Before running the `test` command, the model files must be serialized using Torchscript.
|
|
179
182
|
To serialize the model files:
|
|
180
183
|
```
|
|
181
|
-
deepliif serialize --
|
|
184
|
+
deepliif serialize --model-dir /path/to/input/model/files
|
|
182
185
|
--output-dir /path/to/output/model/files
|
|
183
186
|
```
|
|
184
187
|
* By default, the model files are expected to be located in `DeepLIIF/model-server/DeepLIIF_Latest_Model`.
|
|
@@ -187,15 +190,17 @@ deepliif serialize --models-dir /path/to/input/model/files
|
|
|
187
190
|
## Testing
|
|
188
191
|
To test the model:
|
|
189
192
|
```
|
|
190
|
-
deepliif test --input-dir /path/to/input/images
|
|
191
|
-
--output-dir /path/to/output/images
|
|
192
|
-
--model-dir path/to/the/serialized/model
|
|
193
|
+
deepliif test --input-dir /path/to/input/images
|
|
194
|
+
--output-dir /path/to/output/images
|
|
195
|
+
--model-dir /path/to/the/serialized/model
|
|
193
196
|
--tile-size 512
|
|
194
197
|
```
|
|
195
198
|
or
|
|
196
199
|
```
|
|
197
|
-
python test.py --dataroot /path/to/input/images
|
|
198
|
-
--
|
|
200
|
+
python test.py --dataroot /path/to/input/images
|
|
201
|
+
--results_dir /path/to/output/images
|
|
202
|
+
--checkpoints_dir /path/to/model/files
|
|
203
|
+
--name Model_Name
|
|
199
204
|
```
|
|
200
205
|
* The latest version of the pretrained models can be downloaded [here](https://zenodo.org/record/4751737#.YKRTS0NKhH4).
|
|
201
206
|
* Before running test on images, the model files must be serialized as described above.
|
|
@@ -216,7 +221,7 @@ Based on the available GPU resources, the region-size can be changed.
|
|
|
216
221
|
```
|
|
217
222
|
deepliif test --input-dir /path/to/input/images
|
|
218
223
|
--output-dir /path/to/output/images
|
|
219
|
-
--model-dir path/to/the/serialized/model
|
|
224
|
+
--model-dir /path/to/the/serialized/model
|
|
220
225
|
--tile-size 512
|
|
221
226
|
--region-size 20000
|
|
222
227
|
```
|
|
@@ -257,25 +262,61 @@ If you don't have access to GPU or appropriate hardware and don't want to instal
|
|
|
257
262
|
|
|
258
263
|

|
|
259
264
|
|
|
260
|
-
|
|
261
|
-
|
|
265
|
+
## Cloud API Endpoints
|
|
266
|
+
|
|
267
|
+
DeepLIIF can also be accessed programmatically through an endpoint by posting a multipart-encoded request containing the original image file, along with optional parameters including postprocessing thresholds:
|
|
262
268
|
|
|
263
269
|
```
|
|
264
270
|
POST /api/infer
|
|
265
271
|
|
|
266
|
-
|
|
272
|
+
File Parameter:
|
|
273
|
+
|
|
274
|
+
img (required)
|
|
275
|
+
Image on which to run DeepLIIF.
|
|
276
|
+
|
|
277
|
+
Query String Parameters:
|
|
278
|
+
|
|
279
|
+
resolution
|
|
280
|
+
Resolution used to scan the slide (10x, 20x, 40x). Default is 40x.
|
|
267
281
|
|
|
268
|
-
|
|
269
|
-
|
|
282
|
+
pil
|
|
283
|
+
If present, use Pillow to load the image instead of Bio-Formats. Pillow is
|
|
284
|
+
faster, but works only on common image types (png, jpeg, etc.).
|
|
270
285
|
|
|
271
|
-
|
|
272
|
-
|
|
286
|
+
slim
|
|
287
|
+
If present, return only the refined segmentation result image.
|
|
273
288
|
|
|
274
|
-
|
|
275
|
-
|
|
289
|
+
nopost
|
|
290
|
+
If present, do not perform postprocessing (returns only inferred images).
|
|
276
291
|
|
|
277
|
-
|
|
278
|
-
|
|
292
|
+
prob_thresh
|
|
293
|
+
Probability threshold used in postprocessing the inferred segmentation map
|
|
294
|
+
image. The segmentation map value must be above this value in order for a
|
|
295
|
+
pixel to be included in the final cell segmentation. Valid values are an
|
|
296
|
+
integer in the range 0-254. Default is 150.
|
|
297
|
+
|
|
298
|
+
size_thresh
|
|
299
|
+
Lower threshold for size gating the cells in postprocessing. Segmented
|
|
300
|
+
cells must have more pixels than this value in order to be included in the
|
|
301
|
+
final cell segmentation. Valid values are 0, a positive integer, or 'auto'.
|
|
302
|
+
'Auto' will try to automatically determine this lower bound for size gating
|
|
303
|
+
based on the distribution of detected cell sizes. Default is 'auto'.
|
|
304
|
+
|
|
305
|
+
size_thresh_upper
|
|
306
|
+
Upper threshold for size gating the cells in postprocessing. Segmented
|
|
307
|
+
cells must have less pixels that this value in order to be included in the
|
|
308
|
+
final cell segmentation. Valid values are a positive integer or 'none'.
|
|
309
|
+
'None' will use no upper threshold in size gating. Default is 'none'.
|
|
310
|
+
|
|
311
|
+
marker_thresh
|
|
312
|
+
Threshold for the effect that the inferred marker image will have on the
|
|
313
|
+
postprocessing classification of cells as positive. If any corresponding
|
|
314
|
+
pixel in the marker image for a cell is above this threshold, the cell will
|
|
315
|
+
be classified as being positive regardless of the values from the inferred
|
|
316
|
+
segmentation image. Valid values are an integer in the range 0-255, 'none',
|
|
317
|
+
or 'auto'. 'None' will not use the marker image during classification.
|
|
318
|
+
'Auto' will automatically determine a threshold from the marker image.
|
|
319
|
+
Default is 'auto'.
|
|
279
320
|
```
|
|
280
321
|
|
|
281
322
|
For example, in Python:
|
|
@@ -293,15 +334,118 @@ from PIL import Image
|
|
|
293
334
|
images_dir = './Sample_Large_Tissues'
|
|
294
335
|
filename = 'ROI_1.png'
|
|
295
336
|
|
|
337
|
+
root = os.path.splitext(filename)[0]
|
|
338
|
+
|
|
296
339
|
res = requests.post(
|
|
297
340
|
url='https://deepliif.org/api/infer',
|
|
298
341
|
files={
|
|
299
|
-
'img': open(f'{images_dir}/{filename}', 'rb')
|
|
342
|
+
'img': open(f'{images_dir}/{filename}', 'rb'),
|
|
300
343
|
},
|
|
301
|
-
# optional param that can be 10x, 20x (default) or 40x
|
|
302
344
|
params={
|
|
303
|
-
'resolution': '
|
|
304
|
-
}
|
|
345
|
+
'resolution': '40x',
|
|
346
|
+
},
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
data = res.json()
|
|
350
|
+
|
|
351
|
+
def b64_to_pil(b):
|
|
352
|
+
return Image.open(BytesIO(base64.b64decode(b.encode())))
|
|
353
|
+
|
|
354
|
+
for name, img in data['images'].items():
|
|
355
|
+
with open(f'{images_dir}/{root}_{name}.png', 'wb') as f:
|
|
356
|
+
b64_to_pil(img).save(f, format='PNG')
|
|
357
|
+
|
|
358
|
+
with open(f'{images_dir}/{root}_scoring.json', 'w') as f:
|
|
359
|
+
json.dump(data['scoring'], f, indent=2)
|
|
360
|
+
print(json.dumps(data['scoring'], indent=2))
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
If you have previously run DeepLIIF on an image and want to postprocess it with different thresholds, the postprocessing routine can be called directly using the previously inferred results:
|
|
364
|
+
|
|
365
|
+
```
|
|
366
|
+
POST /api/postprocess
|
|
367
|
+
|
|
368
|
+
File Parameters:
|
|
369
|
+
|
|
370
|
+
img (required)
|
|
371
|
+
Image on which DeepLIIF was run.
|
|
372
|
+
|
|
373
|
+
seg_img (required)
|
|
374
|
+
Inferred segmentation image previously generated by DeepLIIF.
|
|
375
|
+
|
|
376
|
+
marker_img (optional)
|
|
377
|
+
Inferred marker image previously generated by DeepLIIF. If this is
|
|
378
|
+
omitted, then the marker image will not be used in classification.
|
|
379
|
+
|
|
380
|
+
Query String Parameters:
|
|
381
|
+
|
|
382
|
+
resolution
|
|
383
|
+
Resolution used to scan the slide (10x, 20x, 40x). Default is 40x.
|
|
384
|
+
|
|
385
|
+
pil
|
|
386
|
+
If present, use Pillow to load the original image instead of Bio-Formats.
|
|
387
|
+
Pillow is faster, but works only on common image types (png, jpeg, etc.).
|
|
388
|
+
Pillow is always used to open the seg_img and marker_img files.
|
|
389
|
+
|
|
390
|
+
prob_thresh
|
|
391
|
+
Probability threshold used in postprocessing the inferred segmentation map
|
|
392
|
+
image. The segmentation map value must be above this value in order for a
|
|
393
|
+
pixel to be included in the final cell segmentation. Valid values are an
|
|
394
|
+
integer in the range 0-254. Default is 150.
|
|
395
|
+
|
|
396
|
+
size_thresh
|
|
397
|
+
Lower threshold for size gating the cells in postprocessing. Segmented
|
|
398
|
+
cells must have more pixels than this value in order to be included in the
|
|
399
|
+
final cell segmentation. Valid values are 0, a positive integer, or 'auto'.
|
|
400
|
+
'Auto' will try to automatically determine this lower bound for size gating
|
|
401
|
+
based on the distribution of detected cell sizes. Default is 'auto'.
|
|
402
|
+
|
|
403
|
+
size_thresh_upper
|
|
404
|
+
Upper threshold for size gating the cells in postprocessing. Segmented
|
|
405
|
+
cells must have less pixels that this value in order to be included in the
|
|
406
|
+
final cell segmentation. Valid values are a positive integer or 'none'.
|
|
407
|
+
'None' will use no upper threshold in size gating. Default is 'none'.
|
|
408
|
+
|
|
409
|
+
marker_thresh
|
|
410
|
+
Threshold for the effect that the inferred marker image will have on the
|
|
411
|
+
postprocessing classification of cells as positive. If any corresponding
|
|
412
|
+
pixel in the marker image for a cell is above this threshold, the cell will
|
|
413
|
+
be classified as being positive regardless of the values from the inferred
|
|
414
|
+
segmentation image. Valid values are an integer in the range 0-255, 'none',
|
|
415
|
+
or 'auto'. 'None' will not use the marker image during classification.
|
|
416
|
+
'Auto' will automatically determine a threshold from the marker image.
|
|
417
|
+
Default is 'auto'. (If marker_img is not supplied, this has no effect.)
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
For example, in Python:
|
|
421
|
+
|
|
422
|
+
```python
|
|
423
|
+
import os
|
|
424
|
+
import json
|
|
425
|
+
import base64
|
|
426
|
+
from io import BytesIO
|
|
427
|
+
|
|
428
|
+
import requests
|
|
429
|
+
from PIL import Image
|
|
430
|
+
|
|
431
|
+
# Use the sample images from the main DeepLIIF repo
|
|
432
|
+
images_dir = './Sample_Large_Tissues'
|
|
433
|
+
filename = 'ROI_1.png'
|
|
434
|
+
|
|
435
|
+
root = os.path.splitext(filename)[0]
|
|
436
|
+
|
|
437
|
+
res = requests.post(
|
|
438
|
+
url='https://deepliif.org/api/infer',
|
|
439
|
+
files={
|
|
440
|
+
'img': open(f'{images_dir}/{filename}', 'rb'),
|
|
441
|
+
'seg_img': open(f'{images_dir}/{root}_Seg.png', 'rb'),
|
|
442
|
+
'marker_img': open(f'{images_dir}/{root}_Marker.png', 'rb'),
|
|
443
|
+
},
|
|
444
|
+
params={
|
|
445
|
+
'resolution': '40x',
|
|
446
|
+
'pil': True,
|
|
447
|
+
'size_thresh': 250,
|
|
448
|
+
},
|
|
305
449
|
)
|
|
306
450
|
|
|
307
451
|
data = res.json()
|
|
@@ -310,10 +454,11 @@ def b64_to_pil(b):
|
|
|
310
454
|
return Image.open(BytesIO(base64.b64decode(b.encode())))
|
|
311
455
|
|
|
312
456
|
for name, img in data['images'].items():
|
|
313
|
-
|
|
314
|
-
with open(output_filepath, 'wb') as f:
|
|
457
|
+
with open(f'{images_dir}/{root}_{name}.png', 'wb') as f:
|
|
315
458
|
b64_to_pil(img).save(f, format='PNG')
|
|
316
459
|
|
|
460
|
+
with open(f'{images_dir}/{root}_scoring.json', 'w') as f:
|
|
461
|
+
json.dump(data['scoring'], f, indent=2)
|
|
317
462
|
print(json.dumps(data['scoring'], indent=2))
|
|
318
463
|
```
|
|
319
464
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
cli.py,sha256=
|
|
1
|
+
cli.py,sha256=iU9YxO65T1rxX2Mx9f9LsEPC4o_ZXO-wH_-FUjIA1so,40088
|
|
2
2
|
deepliif/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
deepliif/postprocessing.py,sha256=
|
|
3
|
+
deepliif/postprocessing.py,sha256=cM-cYVidY691Sjb1-B8a1jkLq5UR_hTCbuKzuF4765o,17589
|
|
4
4
|
deepliif/train.py,sha256=-ZORL5vQrD0_Jq2Adgr3w8vJ7L1QcAgNTqMnBgtixgk,15757
|
|
5
5
|
deepliif/data/__init__.py,sha256=euf9eUboK4RYR0jvdiyZDgPGozC1Nv7WRqRbTxSZD6A,5281
|
|
6
|
-
deepliif/data/aligned_dataset.py,sha256=
|
|
6
|
+
deepliif/data/aligned_dataset.py,sha256=6qNFLXXW1phuIfNhCJSaLfOc-KN2kl7EuUdmyAPPU4I,5148
|
|
7
7
|
deepliif/data/base_dataset.py,sha256=bQlxfY7bGSE9WPj31ZHkCxv5CAEJovjakGDCcK-aYdc,5564
|
|
8
8
|
deepliif/data/colorization_dataset.py,sha256=uDYWciSxwqZkStQ_Vte27D9x5FNhv6eR9wSPn39K3RY,2808
|
|
9
9
|
deepliif/data/image_folder.py,sha256=eesP9vn__YQ-dw1KJG9J-yVUHMmJjLcIEQI552Iv2vE,2006
|
|
@@ -12,23 +12,24 @@ deepliif/data/template_dataset.py,sha256=PCDBnFRzRKReaeWgKUZmW0LrzRByI9adrKDJ6SN
|
|
|
12
12
|
deepliif/data/unaligned_dataset.py,sha256=m7j-CX-hkXbhg96NSEcaCagNVhTuXKkMsBADdMEJDBA,3393
|
|
13
13
|
deepliif/models/DeepLIIFExt_model.py,sha256=Sc60rOfDJuoGrJ1CYe4beAg6as6F0o864AO6ZB7paBY,14527
|
|
14
14
|
deepliif/models/DeepLIIF_model.py,sha256=ECZyM9jzoJAWSgB1ProBoarVuGcbScQMaSkRjSMgt0k,20872
|
|
15
|
-
deepliif/models/
|
|
15
|
+
deepliif/models/SDG_model.py,sha256=xcZCTMNyJbcB78I1c8KtYVIB6OWL7WSMKdCxNemIzxs,9074
|
|
16
|
+
deepliif/models/__init__.py,sha256=Bjya0xOHjoJa0Wnfiwby-gzJaUzfVsVDS4S_37Uid-g,25597
|
|
16
17
|
deepliif/models/base_model.py,sha256=HKcUOBHtL-zLs5ZcmeXT-ZV_ubqsSUo4wMCQ0W27YHU,15583
|
|
17
18
|
deepliif/models/networks.py,sha256=bN4yjRdE413efUESq8pvhzPDgFCTwFKXyQOrRqHckWY,32177
|
|
18
|
-
deepliif/options/__init__.py,sha256
|
|
19
|
+
deepliif/options/__init__.py,sha256=-syiyTK_oAeTLCBDm0bz1f_1jI3VK3LCwo2UNwOz6eM,5949
|
|
19
20
|
deepliif/options/base_options.py,sha256=m5UXY8MvjNcDisUWuiP228yoT27SsCh1bXS_Td6SwTc,9852
|
|
20
21
|
deepliif/options/processing_options.py,sha256=OnNT-ytoTQzetFiMEKrWvrsrhZlupRK4smcnIk0MbqY,2947
|
|
21
22
|
deepliif/options/test_options.py,sha256=4ZbQC5U-nTbUz8jvdDIbse5TK_mjw4D5yNjpVevWD5M,1114
|
|
22
23
|
deepliif/options/train_options.py,sha256=5eA_oxpRj2-HiuMMvC5-HLapxNFG_JXOQ3K132JjpR8,3580
|
|
23
|
-
deepliif/util/__init__.py,sha256=
|
|
24
|
+
deepliif/util/__init__.py,sha256=5lkf-6R03VPnLXABKec_nx3BmXM-ZGQd3SZ1ft-koHA,17573
|
|
24
25
|
deepliif/util/get_data.py,sha256=HaRoQYb2u0LUgLT7ES-w35AmJ4BrlBEJWU4Cok29pxI,3749
|
|
25
26
|
deepliif/util/html.py,sha256=RNAONZ4opP-bViahgmpSbHwOc6jXKQRnWRAVIaeIvac,3309
|
|
26
27
|
deepliif/util/image_pool.py,sha256=M89Hc7DblRWroNP71S9mAdRn7h3DrhPFPjqFxxZYSgw,2280
|
|
27
|
-
deepliif/util/util.py,sha256=
|
|
28
|
+
deepliif/util/util.py,sha256=9MNgqthJZYjl5-TJm5-sjWvMfPBz8F4P5K0RHXRQhfY,5241
|
|
28
29
|
deepliif/util/visualizer.py,sha256=5V1lWidHqssJX21jn1P5-bOVgtrEXKVaQgnMWAsMfqg,15636
|
|
29
|
-
deepliif-1.1.
|
|
30
|
-
deepliif-1.1.
|
|
31
|
-
deepliif-1.1.
|
|
32
|
-
deepliif-1.1.
|
|
33
|
-
deepliif-1.1.
|
|
34
|
-
deepliif-1.1.
|
|
30
|
+
deepliif-1.1.10.dist-info/LICENSE.md,sha256=HlZw_UPS6EtJimJ_Ci7xKh-S5Iubs0Z8y8E6EZ3ZNyE,956
|
|
31
|
+
deepliif-1.1.10.dist-info/METADATA,sha256=iApT5xf7jFaGZoqda3fQ68c3WCnE6e_nUxjFcxVInHk,31173
|
|
32
|
+
deepliif-1.1.10.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
|
33
|
+
deepliif-1.1.10.dist-info/entry_points.txt,sha256=f70-10j2q68o_rDlsE3hspnv4ejlDnXwwGZ9JJ-3yF4,37
|
|
34
|
+
deepliif-1.1.10.dist-info/top_level.txt,sha256=vLDK5YKmDz08E7PywuvEjAo7dM5rnIpsjR4c0ubQCnc,13
|
|
35
|
+
deepliif-1.1.10.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|