forked from choosehappy/HistoQC
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathBaseImage.py
138 lines (116 loc) · 6.56 KB
/
BaseImage.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import logging
import os
import numpy as np
import inspect
from distutils.util import strtobool
# os.environ['PATH'] = '.\\openslide\\bin' + ';' + os.environ['PATH'] #can either specify openslide bin path in PATH, or add it dynamically
import openslide
def printMaskHelper(type, prev_mask, curr_mask):
if type == "relative2mask":
return str(1 - len(curr_mask.nonzero()[0]) / len(prev_mask.nonzero()[0]))
elif type == "relative2image":
return str(len(curr_mask.nonzero()[0]) / np.prod(curr_mask.shape))
elif type == "absolute":
return str(len(curr_mask.nonzero()[0]))
else:
return str(-1)
# this function is seperated out because in the future we hope to have automatic detection of
# magnification if not present in open slide, and/or to confirm openslide base magnification
def getMag(s, params):
logging.info(f"{s['filename']} - \tgetMag")
osh = s["os_handle"]
mag = osh.properties.get("openslide.objective-power", "NA")
if (
mag == "NA"): # openslide doesn't set objective-power for all SVS files: https://github.com/openslide/openslide/issues/247
mag = osh.properties.get("aperio.AppMag", "NA")
if (mag == "NA" or strtobool(
params.get("confirm_base_mag", "False"))):
# do analysis work here
logging.warning(f"{s['filename']} - Unknown base magnification for file")
s["warnings"].append(f"{s['filename']} - Unknown base magnification for file")
else:
mag = float(mag)
return mag
class BaseImage(dict):
def __init__(self, fname, fname_outdir, params):
dict.__init__(self)
self["warnings"] = [] # this needs to be first key in case anything else wants to add to it
self["output"] = []
# these 2 need to be first for UI to work
self.addToPrintList("filename", os.path.basename(fname))
self.addToPrintList("comments", " ")
self["outdir"] = fname_outdir
self["dir"] = os.path.dirname(fname)
self["os_handle"] = openslide.OpenSlide(fname)
self["image_work_size"] = params.get("image_work_size", "1.25x")
self["mask_statistics"] = params.get("mask_statistics", "relative2mask")
self.addToPrintList("base_mag", getMag(self, params))
mask_statistics_types = ["relative2mask", "absolute", "relative2image"]
if (self["mask_statistics"] not in mask_statistics_types):
logging.error(
f"mask_statistic type '{self['mask_statistics']}' is not one of the 3 supported options relative2mask, absolute, relative2image!")
exit()
self["img_mask_use"] = np.ones(self.getImgThumb(self["image_work_size"]).shape[0:2], dtype=bool)
self["img_mask_force"] = []
self["completed"] = []
def addToPrintList(self, name, val):
self[name] = val
self["output"].append(name)
def getImgThumb(self, dim):
key = "img_" + str(dim)
if key not in self:
osh = self["os_handle"]
if dim.replace(".", "0", 1).isdigit(): #check to see if dim is a number
dim = float(dim)
if dim < 1 and not dim.is_integer(): # specifying a downscale factor from base
new_dim = np.asarray(osh.dimensions) * dim
self[key] = np.array(osh.get_thumbnail(new_dim))
elif dim < 100: # assume it is a level in the openslide pyramid instead of a direct request
dim = int(dim)
if dim >= osh.level_count:
dim = osh.level_count - 1
calling_class = inspect.stack()[1][3]
logging.error(
f"{self['filename']}: Desired Image Level {dim+1} does not exist! Instead using level {osh.level_count-1}! Downstream output may not be correct")
self["warnings"].append(
f"Desired Image Level {dim+1} does not exist! Instead using level {osh.level_count-1}! Downstream output may not be correct")
logging.info(
f"{self['filename']} - \t\tloading image from level {dim} of size {osh.level_dimensions[dim]}")
img = osh.read_region((0, 0), dim, osh.level_dimensions[dim])
self[key] = np.asarray(img)[:, :, 0:3]
else: # assume its an explicit size, *WARNING* this will likely cause different images to have different
# perceived magnifications!
logging.info(f"{self['filename']} - \t\tcreating image thumb of size {str(dim)}")
self[key] = np.array(osh.get_thumbnail((dim, dim)))
elif "X" in dim.upper(): # specifies a desired operating magnification
base_mag = self["base_mag"]
if base_mag != "NA": # if base magnification is not known, it is set to NA by basic module
base_mag = float(base_mag)
else: # without knowing base mag, can't use this scaling, push error and exit
logging.error(
f"{self['filename']}: Has unknown or uncalculated base magnification, cannot specify magnification scale: {base_mag}! Did you try getMag?")
return -1
target_mag = float(dim.upper().split("X")[0])
down_factor = base_mag / target_mag
level = osh.get_best_level_for_downsample(down_factor)
relative_down = down_factor / osh.level_downsamples[level]
win_size = 2048
win_size_down = int(win_size * 1 / relative_down)
dim_base = osh.level_dimensions[0]
output = []
for x in range(0, dim_base[0], round(win_size * osh.level_downsamples[level])):
row_piece = []
for y in range(0, dim_base[1], round(win_size * osh.level_downsamples[level])):
aa = osh.read_region((x, y), level, (win_size, win_size))
bb = aa.resize((win_size_down, win_size_down))
row_piece.append(bb)
row_piece = np.concatenate(row_piece, axis=0)[:, :, 0:3]
output.append(row_piece)
output = np.concatenate(output, axis=1)
output = output[0:round(dim_base[1] * 1 / down_factor), 0:round(dim_base[0] * 1 / down_factor), :]
self[key] = output
else:
logging.error(
f"{self['filename']}: Unknown image level setting: {dim}!")
return -1
return self[key]