deepliif 1.1.12__py3-none-any.whl → 1.1.14__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 +15 -22
- deepliif/data/aligned_dataset.py +2 -2
- deepliif/models/DeepLIIFKD_model.py +409 -0
- deepliif/models/__init__ - different weighted.py +762 -0
- deepliif/models/__init__ - time gens.py +792 -0
- deepliif/models/__init__ - weights, empty, zarr, tile count.py +792 -0
- deepliif/models/__init__.py +131 -43
- deepliif/models/base_model.py +1 -1
- deepliif/models/networks.py +7 -5
- deepliif/postprocessing.py +55 -24
- deepliif/util/__init__.py +1 -1
- deepliif/util/checks.py +17 -0
- deepliif/util/util.py +42 -0
- {deepliif-1.1.12.dist-info → deepliif-1.1.14.dist-info}/METADATA +4 -5
- {deepliif-1.1.12.dist-info → deepliif-1.1.14.dist-info}/RECORD +19 -14
- {deepliif-1.1.12.dist-info → deepliif-1.1.14.dist-info}/LICENSE.md +0 -0
- {deepliif-1.1.12.dist-info → deepliif-1.1.14.dist-info}/WHEEL +0 -0
- {deepliif-1.1.12.dist-info → deepliif-1.1.14.dist-info}/entry_points.txt +0 -0
- {deepliif-1.1.12.dist-info → deepliif-1.1.14.dist-info}/top_level.txt +0 -0
deepliif/models/__init__.py
CHANGED
|
@@ -25,6 +25,8 @@ from functools import lru_cache
|
|
|
25
25
|
from io import BytesIO
|
|
26
26
|
import json
|
|
27
27
|
import math
|
|
28
|
+
import importlib.metadata
|
|
29
|
+
import pathlib
|
|
28
30
|
|
|
29
31
|
import requests
|
|
30
32
|
import torch
|
|
@@ -33,12 +35,11 @@ Image.MAX_IMAGE_PIXELS = None
|
|
|
33
35
|
|
|
34
36
|
import numpy as np
|
|
35
37
|
from dask import delayed, compute
|
|
36
|
-
import openslide
|
|
37
38
|
|
|
38
39
|
from deepliif.util import *
|
|
39
40
|
from deepliif.util.util import tensor_to_pil
|
|
40
41
|
from deepliif.data import transform
|
|
41
|
-
from deepliif.postprocessing import compute_final_results, compute_cell_results
|
|
42
|
+
from deepliif.postprocessing import compute_final_results, compute_cell_results, to_array
|
|
42
43
|
from deepliif.postprocessing import encode_cell_data_v4, decode_cell_data_v4
|
|
43
44
|
from deepliif.options import Options, print_options
|
|
44
45
|
|
|
@@ -168,7 +169,7 @@ def init_nets(model_dir, eager_mode=False, opt=None, phase='test'):
|
|
|
168
169
|
opt = get_opt(model_dir, mode=phase)
|
|
169
170
|
opt.use_dp = False
|
|
170
171
|
|
|
171
|
-
if opt.model
|
|
172
|
+
if opt.model in ['DeepLIIF','DeepLIIFKD']:
|
|
172
173
|
net_groups = [
|
|
173
174
|
('G1', 'G52'),
|
|
174
175
|
('G2', 'G53'),
|
|
@@ -218,13 +219,14 @@ def compute_overlap(img_size, tile_size):
|
|
|
218
219
|
return tile_size // 4
|
|
219
220
|
|
|
220
221
|
|
|
221
|
-
def run_torchserve(img, model_path=None, eager_mode=False, opt=None, seg_only=False):
|
|
222
|
+
def run_torchserve(img, model_path=None, nets=None, eager_mode=False, opt=None, seg_only=False, use_dask=True, output_tensor=False):
|
|
222
223
|
"""
|
|
223
224
|
eager_mode: not used in this function; put in place to be consistent with run_dask
|
|
224
225
|
so that run_wrapper() could call either this function or run_dask with
|
|
225
226
|
same syntax
|
|
226
227
|
opt: same as eager_mode
|
|
227
228
|
seg_only: same as eager_mode
|
|
229
|
+
nets: same as eager_mode
|
|
228
230
|
"""
|
|
229
231
|
buffer = BytesIO()
|
|
230
232
|
torch.save(transform(img.resize((opt.scale_size, opt.scale_size))), buffer)
|
|
@@ -243,16 +245,28 @@ def run_torchserve(img, model_path=None, eager_mode=False, opt=None, seg_only=Fa
|
|
|
243
245
|
return {k: tensor_to_pil(deserialize_tensor(v)) for k, v in res.json().items()}
|
|
244
246
|
|
|
245
247
|
|
|
246
|
-
def run_dask(img, model_path, eager_mode=False, opt=None, seg_only=False):
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
248
|
+
def run_dask(img, model_path=None, nets=None, eager_mode=False, opt=None, seg_only=False, use_dask=True, output_tensor=False):
|
|
249
|
+
"""
|
|
250
|
+
Provide either the model path or the networks object.
|
|
251
|
+
|
|
252
|
+
`eager_mode` is only applicable if model_path is provided.
|
|
253
|
+
"""
|
|
254
|
+
assert model_path is not None or nets is not None, 'Provide either the model path or the networks object.'
|
|
255
|
+
if nets is None:
|
|
256
|
+
model_dir = os.getenv('DEEPLIIF_MODEL_DIR', model_path)
|
|
257
|
+
nets = init_nets(model_dir, eager_mode, opt)
|
|
250
258
|
|
|
251
|
-
if
|
|
252
|
-
|
|
253
|
-
|
|
259
|
+
if use_dask: # check if use_dask should be overwritten
|
|
260
|
+
use_dask = True if opt.norm != 'spectral' else False
|
|
261
|
+
|
|
262
|
+
if isinstance(img,torch.Tensor): # if img input is already a tensor, pass
|
|
263
|
+
ts = img
|
|
254
264
|
else:
|
|
255
|
-
|
|
265
|
+
if opt.input_no > 1 or opt.model == 'SDG':
|
|
266
|
+
l_ts = [transform(img_i.resize((opt.scale_size,opt.scale_size))) for img_i in img]
|
|
267
|
+
ts = torch.cat(l_ts, dim=1)
|
|
268
|
+
else:
|
|
269
|
+
ts = transform(img.resize((opt.scale_size, opt.scale_size)))
|
|
256
270
|
|
|
257
271
|
|
|
258
272
|
if use_dask:
|
|
@@ -265,13 +279,20 @@ def run_dask(img, model_path, eager_mode=False, opt=None, seg_only=False):
|
|
|
265
279
|
with torch.no_grad():
|
|
266
280
|
return model(input.to(next(model.parameters()).device))
|
|
267
281
|
|
|
268
|
-
if opt.model
|
|
282
|
+
if opt.model in ['DeepLIIF','DeepLIIFKD']:
|
|
283
|
+
#weights = {
|
|
284
|
+
# 'G51': 0.25, # IHC
|
|
285
|
+
# 'G52': 0.25, # Hema
|
|
286
|
+
# 'G53': 0.25, # DAPI
|
|
287
|
+
# 'G54': 0.00, # Lap2
|
|
288
|
+
# 'G55': 0.25, # Marker
|
|
289
|
+
#}
|
|
269
290
|
weights = {
|
|
270
|
-
'G51': 0.
|
|
271
|
-
'G52': 0.
|
|
272
|
-
'G53': 0.
|
|
273
|
-
'G54': 0.
|
|
274
|
-
'G55': 0.
|
|
291
|
+
'G51': 0.5, # IHC
|
|
292
|
+
'G52': 0.0, # Hema
|
|
293
|
+
'G53': 0.0, # DAPI
|
|
294
|
+
'G54': 0.0, # Lap2
|
|
295
|
+
'G55': 0.5, # Marker
|
|
275
296
|
}
|
|
276
297
|
|
|
277
298
|
seg_map = {'G1': 'G52', 'G2': 'G53', 'G3': 'G54', 'G4': 'G55'}
|
|
@@ -283,19 +304,27 @@ def run_dask(img, model_path, eager_mode=False, opt=None, seg_only=False):
|
|
|
283
304
|
lazy_gens['G4'] = forward(ts, nets['G4'])
|
|
284
305
|
gens = compute(lazy_gens)[0]
|
|
285
306
|
|
|
286
|
-
lazy_segs = {v: forward(gens[k], nets[v])
|
|
307
|
+
lazy_segs = {v: forward(gens[k], nets[v]) for k, v in seg_map.items()}
|
|
287
308
|
if not seg_only or weights['G51'] != 0:
|
|
288
|
-
lazy_segs['G51'] = forward(ts, nets['G51'])
|
|
309
|
+
lazy_segs['G51'] = forward(ts, nets['G51'])
|
|
289
310
|
segs = compute(lazy_segs)[0]
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
311
|
+
|
|
312
|
+
device = next(nets['G1'].parameters()).device # take the device of the first net and move all outputs there for seg aggregation
|
|
313
|
+
seg = torch.stack([torch.mul(segs[k].to(device), weights[k]) for k in segs.keys()]).sum(dim=0)
|
|
314
|
+
|
|
315
|
+
if output_tensor:
|
|
316
|
+
if seg_only:
|
|
317
|
+
res = {'G4': gens['G4']} if 'G4' in gens else {}
|
|
318
|
+
else:
|
|
319
|
+
res = {**gens, **segs}
|
|
320
|
+
res['G5'] = seg
|
|
295
321
|
else:
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
322
|
+
if seg_only:
|
|
323
|
+
res = {'G4': tensor_to_pil(gens['G4'].to(torch.device('cpu')))} if 'G4' in gens else {}
|
|
324
|
+
else:
|
|
325
|
+
res = {k: tensor_to_pil(v.to(torch.device('cpu'))) for k, v in gens.items()}
|
|
326
|
+
res.update({k: tensor_to_pil(v.to(torch.device('cpu'))) for k, v in segs.items()})
|
|
327
|
+
res['G5'] = tensor_to_pil(seg.to(torch.device('cpu')))
|
|
299
328
|
|
|
300
329
|
return res
|
|
301
330
|
elif opt.model in ['DeepLIIFExt','SDG','CycleGAN']:
|
|
@@ -333,8 +362,8 @@ def is_empty(tile):
|
|
|
333
362
|
return True if np.max(image_variance_rgb(tile)) < thresh else False
|
|
334
363
|
|
|
335
364
|
|
|
336
|
-
def run_wrapper(tile, run_fn, model_path, eager_mode=False, opt=None, seg_only=False):
|
|
337
|
-
if opt.model
|
|
365
|
+
def run_wrapper(tile, run_fn, model_path=None, nets=None, eager_mode=False, opt=None, seg_only=False, use_dask=True, output_tensor=False):
|
|
366
|
+
if opt.model in ['DeepLIIF','DeepLIIFKD']:
|
|
338
367
|
if is_empty(tile):
|
|
339
368
|
if seg_only:
|
|
340
369
|
return {
|
|
@@ -355,31 +384,38 @@ def run_wrapper(tile, run_fn, model_path, eager_mode=False, opt=None, seg_only=F
|
|
|
355
384
|
'G55': Image.new(mode='RGB', size=(512, 512), color=(0, 0, 0)),
|
|
356
385
|
}
|
|
357
386
|
else:
|
|
358
|
-
return run_fn(tile, model_path, eager_mode, opt, seg_only)
|
|
387
|
+
return run_fn(tile, model_path, None, eager_mode, opt, seg_only)
|
|
359
388
|
elif opt.model in ['DeepLIIFExt', 'SDG']:
|
|
360
389
|
if is_empty(tile):
|
|
361
390
|
res = {'G_' + str(i): Image.new(mode='RGB', size=(512, 512)) for i in range(1, opt.modalities_no + 1)}
|
|
362
391
|
res.update({'GS_' + str(i): Image.new(mode='RGB', size=(512, 512)) for i in range(1, opt.modalities_no + 1)})
|
|
363
392
|
return res
|
|
364
393
|
else:
|
|
365
|
-
return run_fn(tile, model_path, eager_mode, opt)
|
|
394
|
+
return run_fn(tile, model_path, None, eager_mode, opt)
|
|
366
395
|
elif opt.model in ['CycleGAN']:
|
|
367
396
|
if is_empty(tile):
|
|
368
397
|
net_names = ['GB_{i+1}' for i in range(opt.modalities_no)] if opt.BtoA else [f'GA_{i+1}' for i in range(opt.modalities_no)]
|
|
369
398
|
res = {net_name: Image.new(mode='RGB', size=(512, 512)) for net_name in net_names}
|
|
370
399
|
return res
|
|
371
400
|
else:
|
|
372
|
-
return run_fn(tile, model_path, eager_mode, opt)
|
|
401
|
+
return run_fn(tile, model_path, None, eager_mode, opt)
|
|
373
402
|
else:
|
|
374
403
|
raise Exception(f'run_wrapper() not implemented for model {opt.model}')
|
|
375
404
|
|
|
376
405
|
|
|
377
406
|
def inference(img, tile_size, overlap_size, model_path, use_torchserve=False,
|
|
378
407
|
eager_mode=False, color_dapi=False, color_marker=False, opt=None,
|
|
379
|
-
return_seg_intermediate=False, seg_only=False):
|
|
408
|
+
return_seg_intermediate=False, seg_only=False, opt_args={}):
|
|
409
|
+
"""
|
|
410
|
+
opt_args: a dictionary of key and values to add/overwrite to opt
|
|
411
|
+
"""
|
|
380
412
|
if not opt:
|
|
381
413
|
opt = get_opt(model_path)
|
|
382
414
|
#print_options(opt)
|
|
415
|
+
|
|
416
|
+
for k,v in opt_args.items():
|
|
417
|
+
setattr(opt,k,v)
|
|
418
|
+
#print_options(opt)
|
|
383
419
|
|
|
384
420
|
run_fn = run_torchserve if use_torchserve else run_dask
|
|
385
421
|
|
|
@@ -394,10 +430,11 @@ def inference(img, tile_size, overlap_size, model_path, use_torchserve=False,
|
|
|
394
430
|
|
|
395
431
|
tiler = InferenceTiler(orig, tile_size, overlap_size)
|
|
396
432
|
for tile in tiler:
|
|
397
|
-
tiler.stitch(run_wrapper(tile, run_fn, model_path, eager_mode, opt, seg_only))
|
|
433
|
+
tiler.stitch(run_wrapper(tile, run_fn, model_path, None, eager_mode, opt, seg_only))
|
|
434
|
+
|
|
398
435
|
results = tiler.results()
|
|
399
436
|
|
|
400
|
-
if opt.model
|
|
437
|
+
if opt.model in ['DeepLIIF','DeepLIIFKD']:
|
|
401
438
|
if seg_only:
|
|
402
439
|
images = {'Seg': results['G5']}
|
|
403
440
|
if 'G4' in results:
|
|
@@ -446,7 +483,7 @@ def inference(img, tile_size, overlap_size, model_path, use_torchserve=False,
|
|
|
446
483
|
|
|
447
484
|
|
|
448
485
|
def postprocess(orig, images, tile_size, model, seg_thresh=150, size_thresh='default', marker_thresh=None, size_thresh_upper=None):
|
|
449
|
-
if model
|
|
486
|
+
if model in ['DeepLIIF','DeepLIIFKD']:
|
|
450
487
|
resolution = '40x' if tile_size > 384 else ('20x' if tile_size > 192 else '10x')
|
|
451
488
|
overlay, refined, scoring = compute_final_results(
|
|
452
489
|
orig, images['Seg'], images.get('Marker'), resolution,
|
|
@@ -592,10 +629,13 @@ def infer_results_for_wsi(input_dir, filename, output_dir, model_dir, tile_size,
|
|
|
592
629
|
|
|
593
630
|
def get_wsi_resolution(filename):
|
|
594
631
|
"""
|
|
595
|
-
|
|
596
|
-
|
|
632
|
+
Try to get the resolution (magnification) of the slide and
|
|
633
|
+
the corresponding tile size to use by default for DeepLIIF.
|
|
597
634
|
If it cannot be found, return (None, None) instead.
|
|
598
635
|
|
|
636
|
+
Note: This will start the javabridge VM, but not kill it.
|
|
637
|
+
It must be killed elsewhere.
|
|
638
|
+
|
|
599
639
|
Parameters
|
|
600
640
|
----------
|
|
601
641
|
filename : str
|
|
@@ -604,13 +644,42 @@ def get_wsi_resolution(filename):
|
|
|
604
644
|
Returns
|
|
605
645
|
-------
|
|
606
646
|
str :
|
|
607
|
-
Magnification (objective power)
|
|
647
|
+
Magnification (objective power) from image metadata.
|
|
608
648
|
int :
|
|
609
649
|
Corresponding tile size for DeepLIIF.
|
|
610
650
|
"""
|
|
651
|
+
|
|
652
|
+
# make sure javabridge is already set up from with call to get_information()
|
|
653
|
+
size_x, size_y, size_z, size_c, size_t, pixel_type = get_information(filename)
|
|
654
|
+
|
|
655
|
+
mag = None
|
|
656
|
+
metadata = bioformats.get_omexml_metadata(filename)
|
|
657
|
+
try:
|
|
658
|
+
omexml = bioformats.OMEXML(metadata)
|
|
659
|
+
mag = omexml.instrument().Objective.NominalMagnification
|
|
660
|
+
except Exception as e:
|
|
661
|
+
fields = ['AppMag', 'NominalMagnification']
|
|
662
|
+
try:
|
|
663
|
+
for field in fields:
|
|
664
|
+
idx = metadata.find(field)
|
|
665
|
+
if idx >= 0:
|
|
666
|
+
for i in range(idx, len(metadata)):
|
|
667
|
+
if metadata[i].isdigit() or metadata[i] == '.':
|
|
668
|
+
break
|
|
669
|
+
for j in range(i, len(metadata)):
|
|
670
|
+
if not metadata[j].isdigit() and metadata[j] != '.':
|
|
671
|
+
break
|
|
672
|
+
if i == j:
|
|
673
|
+
continue
|
|
674
|
+
mag = metadata[i:j]
|
|
675
|
+
break
|
|
676
|
+
except Exception as e:
|
|
677
|
+
pass
|
|
678
|
+
|
|
679
|
+
if mag is None:
|
|
680
|
+
return None, None
|
|
681
|
+
|
|
611
682
|
try:
|
|
612
|
-
image = openslide.OpenSlide(filename)
|
|
613
|
-
mag = image.properties.get(openslide.PROPERTY_NAME_OBJECTIVE_POWER)
|
|
614
683
|
tile_size = round((float(mag) / 40) * 512)
|
|
615
684
|
return mag, tile_size
|
|
616
685
|
except Exception as e:
|
|
@@ -675,6 +744,7 @@ def infer_cells_for_wsi(filename, model_dir, tile_size, region_size=20000, versi
|
|
|
675
744
|
print_info(region.shape, region.dtype)
|
|
676
745
|
img = Image.fromarray((region * 255).astype(np.uint8)) if rescale else Image.fromarray(region)
|
|
677
746
|
print_info(img.size, img.mode)
|
|
747
|
+
del region
|
|
678
748
|
|
|
679
749
|
images = inference(
|
|
680
750
|
img,
|
|
@@ -688,7 +758,15 @@ def infer_cells_for_wsi(filename, model_dir, tile_size, region_size=20000, versi
|
|
|
688
758
|
return_seg_intermediate=False,
|
|
689
759
|
seg_only=True,
|
|
690
760
|
)
|
|
691
|
-
|
|
761
|
+
del img
|
|
762
|
+
|
|
763
|
+
seg = to_array(images['Seg'])
|
|
764
|
+
del images['Seg']
|
|
765
|
+
marker = to_array(images['Marker'], True) if 'Marker' in images else None
|
|
766
|
+
del images
|
|
767
|
+
region_data = compute_cell_results(seg, marker, resolution, version=version)
|
|
768
|
+
del seg
|
|
769
|
+
del marker
|
|
692
770
|
|
|
693
771
|
if start_x != 0 or start_y != 0:
|
|
694
772
|
for i in range(len(region_data['cells'])):
|
|
@@ -726,4 +804,14 @@ def infer_cells_for_wsi(filename, model_dir, tile_size, region_size=20000, versi
|
|
|
726
804
|
data['settings']['default_marker_thresh'] = round(default_marker_thresh / count_marker_thresh)
|
|
727
805
|
data['settings']['default_size_thresh'] = round(default_size_thresh / count_size_thresh)
|
|
728
806
|
|
|
807
|
+
try:
|
|
808
|
+
data['deepliifVersion'] = importlib.metadata.version('deepliif')
|
|
809
|
+
except Exception as e:
|
|
810
|
+
data['deepliifVersion'] = 'unknown'
|
|
811
|
+
|
|
812
|
+
try:
|
|
813
|
+
data['modelVersion'] = pathlib.PurePath(model_dir).name
|
|
814
|
+
except Exception as e:
|
|
815
|
+
data['modelVersion'] = 'unknown'
|
|
816
|
+
|
|
729
817
|
return data
|
deepliif/models/base_model.py
CHANGED
|
@@ -147,7 +147,7 @@ class BaseModel(ABC):
|
|
|
147
147
|
if isinstance(name, str):
|
|
148
148
|
if not hasattr(self, name):
|
|
149
149
|
if len(name.split('_')) != 2:
|
|
150
|
-
if self.opt.model
|
|
150
|
+
if self.opt.model in ['DeepLIIF','DeepLIIFKD']:
|
|
151
151
|
img_name = name[:-1] + '_' + name[-1]
|
|
152
152
|
visual_ret[name] = getattr(self, img_name)
|
|
153
153
|
else:
|
deepliif/models/networks.py
CHANGED
|
@@ -172,12 +172,14 @@ def define_G(
|
|
|
172
172
|
norm_layer = get_norm_layer(norm_type=norm)
|
|
173
173
|
use_spectral_norm = norm == 'spectral'
|
|
174
174
|
|
|
175
|
-
if netG
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
elif netG == 'resnet_6blocks':
|
|
179
|
-
net = ResnetGenerator(input_nc, output_nc, ngf, norm_layer=norm_layer, use_dropout=use_dropout, n_blocks=6,
|
|
175
|
+
if netG.startswith('resnet_'):
|
|
176
|
+
n_blocks = int(netG.split('_')[1].replace('blocks',''))
|
|
177
|
+
net = ResnetGenerator(input_nc, output_nc, ngf, norm_layer=norm_layer, use_dropout=use_dropout, n_blocks=n_blocks,
|
|
180
178
|
padding_type=padding_type, upsample=upsample, use_spectral_norm=use_spectral_norm)
|
|
179
|
+
elif netG == 'unet_32':
|
|
180
|
+
net = UnetGenerator(input_nc, output_nc, 5, ngf, norm_layer=norm_layer, use_dropout=use_dropout)
|
|
181
|
+
elif netG == 'unet_64':
|
|
182
|
+
net = UnetGenerator(input_nc, output_nc, 6, ngf, norm_layer=norm_layer, use_dropout=use_dropout)
|
|
181
183
|
elif netG == 'unet_128':
|
|
182
184
|
net = UnetGenerator(input_nc, output_nc, 7, ngf, norm_layer=norm_layer, use_dropout=use_dropout)
|
|
183
185
|
elif netG == 'unet_256':
|
deepliif/postprocessing.py
CHANGED
|
@@ -184,32 +184,38 @@ def mark_background(mask):
|
|
|
184
184
|
After the function executes, the pixels will be labeled as background, positive, negative, or unknown.
|
|
185
185
|
"""
|
|
186
186
|
|
|
187
|
-
seeds = []
|
|
188
187
|
for i in range(mask.shape[0]):
|
|
189
188
|
if mask[i, 0] == LABEL_UNKNOWN:
|
|
190
|
-
|
|
189
|
+
mask[i, 0] = LABEL_BACKGROUND
|
|
191
190
|
if mask[i, mask.shape[1]-1] == LABEL_UNKNOWN:
|
|
192
|
-
|
|
191
|
+
mask[i, mask.shape[1]-1] = LABEL_BACKGROUND
|
|
193
192
|
for j in range(mask.shape[1]):
|
|
194
193
|
if mask[0, j] == LABEL_UNKNOWN:
|
|
195
|
-
|
|
194
|
+
mask[0, j] = LABEL_BACKGROUND
|
|
196
195
|
if mask[mask.shape[0]-1, j] == LABEL_UNKNOWN:
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
196
|
+
mask[mask.shape[0]-1, j] = LABEL_BACKGROUND
|
|
197
|
+
|
|
198
|
+
count = 1
|
|
199
|
+
while count > 0:
|
|
200
|
+
count = 0
|
|
201
|
+
for i in range(mask.shape[0]):
|
|
202
|
+
for j in range(mask.shape[1]):
|
|
203
|
+
if mask[i, j] == LABEL_UNKNOWN:
|
|
204
|
+
if (mask[i-1, j] == LABEL_BACKGROUND or mask[i+1, j] == LABEL_BACKGROUND or
|
|
205
|
+
mask[i, j-1] == LABEL_BACKGROUND or mask[i, j+1] == LABEL_BACKGROUND):
|
|
206
|
+
mask[i, j] = LABEL_BACKGROUND
|
|
207
|
+
count += 1
|
|
208
|
+
if count > 0:
|
|
209
|
+
for i in range(mask.shape[0]-1, -1, -1):
|
|
210
|
+
for j in range(mask.shape[1]-1, -1, -1):
|
|
211
|
+
if mask[i, j] == LABEL_UNKNOWN:
|
|
212
|
+
if (mask[i-1, j] == LABEL_BACKGROUND or mask[i+1, j] == LABEL_BACKGROUND or
|
|
213
|
+
mask[i, j-1] == LABEL_BACKGROUND or mask[i, j+1] == LABEL_BACKGROUND):
|
|
214
|
+
mask[i, j] = LABEL_BACKGROUND
|
|
209
215
|
|
|
210
216
|
|
|
211
217
|
@jit(nopython=True)
|
|
212
|
-
def compute_cell_mapping(mask, marker, noise_thresh):
|
|
218
|
+
def compute_cell_mapping(mask, marker, noise_thresh, large_noise_thresh):
|
|
213
219
|
"""
|
|
214
220
|
Compute the mapping from mask to positive and negative cells.
|
|
215
221
|
|
|
@@ -264,7 +270,7 @@ def compute_cell_mapping(mask, marker, noise_thresh):
|
|
|
264
270
|
center_x += idx[1]
|
|
265
271
|
count += 1
|
|
266
272
|
|
|
267
|
-
if count > noise_thresh:
|
|
273
|
+
if count > noise_thresh and (large_noise_thresh is None or count < large_noise_thresh):
|
|
268
274
|
center_y = int(round(center_y / count))
|
|
269
275
|
center_x = int(round(center_x / count))
|
|
270
276
|
positive = True if count_positive >= count_negative else False
|
|
@@ -273,7 +279,7 @@ def compute_cell_mapping(mask, marker, noise_thresh):
|
|
|
273
279
|
return cells
|
|
274
280
|
|
|
275
281
|
|
|
276
|
-
def get_cells_info(seg, marker, resolution, noise_thresh, seg_thresh):
|
|
282
|
+
def get_cells_info(seg, marker, resolution, noise_thresh, seg_thresh, large_noise_thresh):
|
|
277
283
|
"""
|
|
278
284
|
Find all cells in the segmentation image that are larger than the noise threshold.
|
|
279
285
|
|
|
@@ -289,6 +295,8 @@ def get_cells_info(seg, marker, resolution, noise_thresh, seg_thresh):
|
|
|
289
295
|
Threshold for tiny noise to ignore (include only cells larger than this value).
|
|
290
296
|
seg_thresh : int
|
|
291
297
|
Threshold to use in determining if a pixel should be labeled as positive/negative.
|
|
298
|
+
large_noise_thresh : int | None
|
|
299
|
+
Threshold for large noise to ignore (include only cells smaller than this value).
|
|
292
300
|
|
|
293
301
|
Returns
|
|
294
302
|
-------
|
|
@@ -303,9 +311,10 @@ def get_cells_info(seg, marker, resolution, noise_thresh, seg_thresh):
|
|
|
303
311
|
seg = to_array(seg)
|
|
304
312
|
if marker is not None:
|
|
305
313
|
marker = to_array(marker, True)
|
|
314
|
+
|
|
306
315
|
mask = create_posneg_mask(seg, seg_thresh)
|
|
307
316
|
mark_background(mask)
|
|
308
|
-
cellsinfo = compute_cell_mapping(mask, marker, noise_thresh)
|
|
317
|
+
cellsinfo = compute_cell_mapping(mask, marker, noise_thresh, large_noise_thresh)
|
|
309
318
|
|
|
310
319
|
defaults = {}
|
|
311
320
|
sizes = np.zeros(len(cellsinfo), dtype=np.int64)
|
|
@@ -1040,9 +1049,21 @@ def fill_cells(mask):
|
|
|
1040
1049
|
mask[y, x] = LABEL_NEGATIVE
|
|
1041
1050
|
|
|
1042
1051
|
|
|
1052
|
+
def calculate_large_noise_thresh(large_noise_thresh, resolution):
|
|
1053
|
+
if large_noise_thresh != 'default':
|
|
1054
|
+
return large_noise_thresh
|
|
1055
|
+
if resolution == '10x':
|
|
1056
|
+
return 250
|
|
1057
|
+
elif resolution == '20x':
|
|
1058
|
+
return 1000
|
|
1059
|
+
else: # 40x
|
|
1060
|
+
return 4000
|
|
1061
|
+
|
|
1062
|
+
|
|
1043
1063
|
def compute_cell_results(seg, marker, resolution, version=3,
|
|
1044
1064
|
seg_thresh=DEFAULT_SEG_THRESH,
|
|
1045
|
-
noise_thresh=DEFAULT_NOISE_THRESH
|
|
1065
|
+
noise_thresh=DEFAULT_NOISE_THRESH,
|
|
1066
|
+
large_noise_thresh='default'):
|
|
1046
1067
|
"""
|
|
1047
1068
|
Perform postprocessing to compute individual cell results.
|
|
1048
1069
|
|
|
@@ -1060,6 +1081,9 @@ def compute_cell_results(seg, marker, resolution, version=3,
|
|
|
1060
1081
|
Threshold to use in determining if a pixel should be labeled as positive/negative.
|
|
1061
1082
|
noise_thresh : int
|
|
1062
1083
|
Threshold for tiny noise to ignore (include only cells larger than this value).
|
|
1084
|
+
large_noise_thresh : int | string | None
|
|
1085
|
+
Threshold for large noise to ignore (include only cells smaller than this value).
|
|
1086
|
+
Valid arguments can be an integer value, the string value 'default', or None.
|
|
1063
1087
|
|
|
1064
1088
|
Returns
|
|
1065
1089
|
-------
|
|
@@ -1071,7 +1095,8 @@ def compute_cell_results(seg, marker, resolution, version=3,
|
|
|
1071
1095
|
warnings.warn('Invalid cell data version provided, defaulting to version 3.')
|
|
1072
1096
|
version = 3
|
|
1073
1097
|
|
|
1074
|
-
|
|
1098
|
+
large_noise_thresh = calculate_large_noise_thresh(large_noise_thresh, resolution)
|
|
1099
|
+
mask, cellsinfo, defaults = get_cells_info(seg, marker, resolution, noise_thresh, seg_thresh, large_noise_thresh)
|
|
1075
1100
|
|
|
1076
1101
|
cells = []
|
|
1077
1102
|
for cell in cellsinfo:
|
|
@@ -1094,6 +1119,7 @@ def compute_cell_results(seg, marker, resolution, version=3,
|
|
|
1094
1119
|
'default_marker_thresh': defaults['marker_thresh'] if 'marker_thresh' in defaults else None,
|
|
1095
1120
|
'default_size_thresh': defaults['size_thresh'],
|
|
1096
1121
|
'noise_thresh': noise_thresh,
|
|
1122
|
+
'large_noise_thresh': large_noise_thresh,
|
|
1097
1123
|
'seg_thresh': seg_thresh,
|
|
1098
1124
|
},
|
|
1099
1125
|
'dataVersion': version,
|
|
@@ -1107,7 +1133,8 @@ def compute_final_results(orig, seg, marker, resolution,
|
|
|
1107
1133
|
marker_thresh=None,
|
|
1108
1134
|
size_thresh_upper=None,
|
|
1109
1135
|
seg_thresh=DEFAULT_SEG_THRESH,
|
|
1110
|
-
noise_thresh=DEFAULT_NOISE_THRESH
|
|
1136
|
+
noise_thresh=DEFAULT_NOISE_THRESH,
|
|
1137
|
+
large_noise_thresh='default'):
|
|
1111
1138
|
"""
|
|
1112
1139
|
Perform postprocessing to compute final count and image results.
|
|
1113
1140
|
|
|
@@ -1131,6 +1158,9 @@ def compute_final_results(orig, seg, marker, resolution,
|
|
|
1131
1158
|
Threshold to use in determining if a pixel should be labeled as positive/negative.
|
|
1132
1159
|
noise_thresh : int
|
|
1133
1160
|
Threshold for tiny noise to ignore (include only cells larger than this value).
|
|
1161
|
+
large_noise_thresh : int | string | None
|
|
1162
|
+
Threshold for large noise to ignore (include only cells smaller than this value).
|
|
1163
|
+
Valid arguments can be an integer value, the string value 'default', or None.
|
|
1134
1164
|
|
|
1135
1165
|
Returns
|
|
1136
1166
|
-------
|
|
@@ -1142,7 +1172,8 @@ def compute_final_results(orig, seg, marker, resolution,
|
|
|
1142
1172
|
Dictionary with scoring and settings information.
|
|
1143
1173
|
"""
|
|
1144
1174
|
|
|
1145
|
-
|
|
1175
|
+
large_noise_thresh = calculate_large_noise_thresh(large_noise_thresh, resolution)
|
|
1176
|
+
mask, cellsinfo, defaults = get_cells_info(seg, marker, resolution, noise_thresh, seg_thresh, large_noise_thresh)
|
|
1146
1177
|
|
|
1147
1178
|
if size_thresh is None:
|
|
1148
1179
|
size_thresh = 0
|
deepliif/util/__init__.py
CHANGED
|
@@ -437,7 +437,7 @@ def get_information(filename):
|
|
|
437
437
|
omexml.image().Pixels.SizeC, \
|
|
438
438
|
omexml.image().Pixels.SizeT, \
|
|
439
439
|
omexml.image().Pixels.PixelType
|
|
440
|
-
print('SizeX:', size_x, ' SizeY:', size_y, ' SizeZ:', size_z, ' SizeC:', size_c, ' SizeT:', size_t, ' PixelType:', pixel_type)
|
|
440
|
+
#print('SizeX:', size_x, ' SizeY:', size_y, ' SizeZ:', size_z, ' SizeC:', size_c, ' SizeT:', size_t, ' PixelType:', pixel_type)
|
|
441
441
|
return size_x, size_y, size_z, size_c, size_t, pixel_type
|
|
442
442
|
|
|
443
443
|
|
deepliif/util/checks.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
def check_weights(model, modalities_no, seg_weights, loss_weights_g, loss_weights_d):
|
|
4
|
+
assert sum(seg_weights) == 1, 'seg weights should add up to 1'
|
|
5
|
+
assert sum(loss_weights_g) == 1, 'loss weights g should add up to 1'
|
|
6
|
+
assert sum(loss_weights_d) == 1, 'loss weights d should add up to 1'
|
|
7
|
+
|
|
8
|
+
if model in ['DeepLIIF','DeepLIIFKD']:
|
|
9
|
+
# +1 because input becomes an additional modality used in generating the final segmentation
|
|
10
|
+
assert len(seg_weights) == modalities_no+1, 'seg weights should have the same number of elements as number of modalities to be generated'
|
|
11
|
+
assert len(loss_weights_g) == modalities_no+1, 'loss weights g should have the same number of elements as number of modalities to be generated'
|
|
12
|
+
assert len(loss_weights_d) == modalities_no+1, 'loss weights d should have the same number of elements as number of modalities to be generated'
|
|
13
|
+
|
|
14
|
+
else:
|
|
15
|
+
assert len(seg_weights) == modalities_no, 'seg weights should have the same number of elements as number of modalities to be generated'
|
|
16
|
+
assert len(loss_weights_g) == modalities_no, 'loss weights g should have the same number of elements as number of modalities to be generated'
|
|
17
|
+
assert len(loss_weights_d) == modalities_no, 'loss weights d should have the same number of elements as number of modalities to be generated'
|
deepliif/util/util.py
CHANGED
|
@@ -163,3 +163,45 @@ def check_multi_scale(img1, img2):
|
|
|
163
163
|
if max_ssim[1] < image_ssim:
|
|
164
164
|
max_ssim = (tile_size, image_ssim)
|
|
165
165
|
return max_ssim[0]
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
import subprocess
|
|
169
|
+
import os
|
|
170
|
+
from threading import Thread , Timer
|
|
171
|
+
import sched, time
|
|
172
|
+
|
|
173
|
+
# modified from https://stackoverflow.com/questions/67707828/how-to-get-every-seconds-gpu-usage-in-python
|
|
174
|
+
def get_gpu_memory(gpu_id=0):
|
|
175
|
+
"""
|
|
176
|
+
Currently collects gpu memory info for a given gpu id.
|
|
177
|
+
"""
|
|
178
|
+
output_to_list = lambda x: x.decode('ascii').split('\n')[:-1]
|
|
179
|
+
ACCEPTABLE_AVAILABLE_MEMORY = 1024
|
|
180
|
+
COMMAND = "nvidia-smi --query-gpu=memory.used --format=csv"
|
|
181
|
+
try:
|
|
182
|
+
memory_use_info = output_to_list(subprocess.check_output(COMMAND.split(),stderr=subprocess.STDOUT))[1:]
|
|
183
|
+
except subprocess.CalledProcessError as e:
|
|
184
|
+
raise RuntimeError("command '{}' return with error (code {}): {}".format(e.cmd, e.returncode, e.output))
|
|
185
|
+
memory_use_values = [int(x.split()[0]) for i, x in enumerate(memory_use_info)]
|
|
186
|
+
|
|
187
|
+
#assert len(memory_use_values)==1, f"get_gpu_memory::memory_use_values should have only 1 value, now has {len(memory_use_values)} (memory_use_values)"
|
|
188
|
+
return memory_use_values[gpu_id]
|
|
189
|
+
|
|
190
|
+
class HardwareStatus():
|
|
191
|
+
def __init__(self):
|
|
192
|
+
self.gpu_mem = []
|
|
193
|
+
self.timer = None
|
|
194
|
+
|
|
195
|
+
def get_status_every_sec(self, gpu_id=0):
|
|
196
|
+
"""
|
|
197
|
+
This function calls itself every 1 sec and appends the gpu_memory.
|
|
198
|
+
"""
|
|
199
|
+
self.timer = Timer(1.0, self.get_status_every_sec)
|
|
200
|
+
self.timer.start()
|
|
201
|
+
self.gpu_mem.append(get_gpu_memory(gpu_id))
|
|
202
|
+
# print('self.gpu_mem',self.gpu_mem)
|
|
203
|
+
|
|
204
|
+
def stop_timer(self):
|
|
205
|
+
self.timer.cancel()
|
|
206
|
+
|
|
207
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: deepliif
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.14
|
|
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
|
|
@@ -9,7 +9,8 @@ Keywords: DeepLIIF,IHC,Segmentation,Classification
|
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE.md
|
|
11
11
|
Requires-Dist: opencv-python (==4.8.1.78)
|
|
12
|
-
Requires-Dist:
|
|
12
|
+
Requires-Dist: torch (==1.13.1)
|
|
13
|
+
Requires-Dist: torchvision (==0.14.1)
|
|
13
14
|
Requires-Dist: scikit-image (==0.18.3)
|
|
14
15
|
Requires-Dist: dominate (==2.6.0)
|
|
15
16
|
Requires-Dist: numba (==0.57.1)
|
|
@@ -18,8 +19,6 @@ Requires-Dist: requests (==2.32.2)
|
|
|
18
19
|
Requires-Dist: dask (==2021.11.2)
|
|
19
20
|
Requires-Dist: visdom (>=0.1.8.3)
|
|
20
21
|
Requires-Dist: python-bioformats (>=4.0.6)
|
|
21
|
-
Requires-Dist: openslide-bin (==4.0.0.6)
|
|
22
|
-
Requires-Dist: openslide-python (==1.4.1)
|
|
23
22
|
|
|
24
23
|
|
|
25
24
|
<!-- PROJECT LOGO -->
|
|
@@ -65,7 +64,7 @@ segmentation.*
|
|
|
65
64
|
|
|
66
65
|
© This code is made available for non-commercial academic purposes.
|
|
67
66
|
|
|
68
|
-

|
|
69
68
|
[](https://pepy.tech/project/deepliif?&left_text=totalusers)
|
|
70
69
|
|
|
71
70
|
*Overview of DeepLIIF pipeline and sample input IHCs (different
|