in ez_wsi_dicomweb/patch_generator.py [0:0]
def _normalized_tissue_mask(self) -> np.ndarray:
"""An image mask that is likely to contain tissues.
It is normalized/down-scaled by the specified stride size, i.e.
normalized_tissue_mask size = original size / stride.
Returns:
A 2D ndarray binary (0, 1) image denoting tissue mask.
Raises:
DicomImageMissingRegionError if there is no region with the
required luminance values.
"""
if self._tissue_mask_cache is not None:
return self._tissue_mask_cache
rgb_at_tissue_ps = self.get_tissue_mask()
# luminance values
if np.issubdtype(rgb_at_tissue_ps.dtype, np.floating):
if rgb_at_tissue_ps.max() <= 1.0:
rgb_at_tissue_ps *= 255.0
rgb_at_tissue_ps = rgb_at_tissue_ps.astype(np.uint8)
if len(rgb_at_tissue_ps.shape) == 2:
gray_tissue_mask = rgb_at_tissue_ps
elif len(rgb_at_tissue_ps.shape) == 3 and rgb_at_tissue_ps.shape[2] == 1:
gray_tissue_mask = np.squeeze(rgb_at_tissue_ps, axis=-1)
else:
gray_tissue_mask = cv2.cvtColor(rgb_at_tissue_ps, cv2.COLOR_RGB2GRAY)
tissue_mask_height, tissue_mask_width = gray_tissue_mask.shape[:2]
image_dimensions = self._image_dimensions()
if (
image_dimensions.height_px < self.patch_size
or image_dimensions.width_px < self.patch_size
):
raise ez_wsi_errors.InvalidPatchDimensionError(
'Patch dimensions are exceed image dimensions.'
)
# if stride size may be different along x and y axis only when
# mask is initialized directly by the user.
if (
self._user_provided_tissue_mask is None
and self.stride_width != self.stride_height
):
raise ValueError(
'Stride dimensions must be equal along both dimensions if not'
' initialized from user provided mask.'
)
stride_size = self.stride_width
if self._user_provided_tissue_mask is not None:
gray_image_at_output_res = rgb_at_tissue_ps
elif self.patch_size <= stride_size:
# computes tissue mask when patch size <= stride size.
# number of patches to sample across the image in the x and y axis.
width_steps = max(int(image_dimensions.width_px / stride_size), 1)
height_steps = max(int(image_dimensions.height_px / stride_size), 1)
# dimensions of the tissue across which patches will be sampled.
# in most cases does not = whole tissue dimensions due to integer math.
sampled_image_width = int(width_steps * stride_size)
sampled_image_height = int(height_steps * stride_size)
if (
sampled_image_width < image_dimensions.width_px
or sampled_image_height < image_dimensions.height_px
):
# if sampled area is less than actual tissue dimensions
# determine if image representing tissue for tissue mask should be
# cropped to remove areas which do not correspond to regions patches
# will be sampled from.
# determine how much smaller, proportionally, the sampled area is in the
# the source image.
width_scale_factor = float(sampled_image_width) / float(
image_dimensions.width_px
)
height_scale_factor = float(sampled_image_height) / float(
image_dimensions.height_px
)
# scale tissue mask by the scale factor.
tissue_mask_width = int(
min(
math.ceil(tissue_mask_width * width_scale_factor),
tissue_mask_width,
)
)
tissue_mask_height = int(
min(
math.ceil(tissue_mask_height * height_scale_factor),
tissue_mask_height,
)
)
# crop the tissue mask based on the newly computed dimensions.
# tissue mask mask now corresponds the region being sampled in the
# the source image.
gray_tissue_mask = gray_tissue_mask[
:tissue_mask_height, :tissue_mask_width, ...
]
# resize the tissue mask to number of strides that will be sampled.
# each pixel in the mask now represents one patch.
gray_image_at_output_res = cv2.resize(
gray_tissue_mask,
(width_steps, height_steps),
interpolation=cv2.INTER_AREA,
)
else:
# computing tissue mask when patch size > stride size, aka, patches
# overlap.
# determine how much larger in px patches are than strides
small_stride_adjustment = int(max(self.patch_size - stride_size, 0))
# The number of patches which can be sampled along both
# dimensions. Due to patches being larger than strides, the
# adjustment factor calculated previously is subtracted from the dim to
# guarantee that the patches are always sampled from within the image.
width_steps = max(
int(
(image_dimensions.width_px - small_stride_adjustment)
/ stride_size
),
1,
)
height_steps = max(
int(
(image_dimensions.height_px - small_stride_adjustment)
/ stride_size
),
1,
)
# Allocate a buffer to store the tissue mask results. Each pixel in this
# buffer will hold the mean value of pixels that fall within a tisse mask
# within scaled tissue mask patch.
gray_image_at_output_res = np.zeros(
(height_steps, width_steps), dtype=np.uint64
)
# Scale the dimensions of the origional patch to find the tissue mask
# patch size. Make sure that at least one pixel will be sampled.
tisse_mask_patch_width = max(
int(self.patch_size * tissue_mask_width / image_dimensions.width_px),
1,
)
tisse_mask_patch_height = max(
int(
self.patch_size * tissue_mask_height / image_dimensions.height_px
),
1,
)
# Allocate temporary buffers to use to speed up calculations.
column_sum = np.zeros(tissue_mask_width, dtype=np.uint64)
temp_buffer = np.zeros(tissue_mask_width, dtype=np.uint64)
# determine patch sampling indices along the horizontal axis.
# compute once and re-use across all rows.
x_start_offset = [
int(x * stride_size * tissue_mask_width / image_dimensions.width_px)
for x in range(width_steps)
]
last_start = -1
# compute the mean pixel value for each of the sampling patches in the
# tissue mask
for y in range(height_steps):
# upper left y position
patch_y_start = int(
y * stride_size * tissue_mask_height / image_dimensions.height_px
)
if last_start == -1:
# if it's the very first row, being calculated then
# calculate the sum of the pixels along each column of the patches
# for the width of the image
np.sum(
gray_tissue_mask[
patch_y_start : patch_y_start + tisse_mask_patch_height, ...
],
axis=0,
out=column_sum,
)
else:
# if not first row of the image then
# compute the sum of the columns which fell in the prior rows patches
# but not the current and subtract these from the column row totals
np.sum(
gray_tissue_mask[last_start:patch_y_start, ...],
axis=0,
out=temp_buffer,
)
column_sum -= temp_buffer
# if not first row of the image then
# compute the sum of the new column area, bottom of prior results to
# bottom of curren rows patches. Add these to column totals.
np.sum(
gray_tissue_mask[
last_start
+ tisse_mask_patch_height : patch_y_start
+ tisse_mask_patch_height,
...,
],
axis=0,
out=temp_buffer,
)
column_sum += temp_buffer
last_start = patch_y_start
for x in range(width_steps):
# Now step through each patch in the row. Calculate the total sum
# of the pixel values by summing across the columns which fall in the
# patch.
patch_x_start = x_start_offset[x]
gray_image_at_output_res[y, x] = np.sum(
column_sum[
patch_x_start : patch_x_start + tisse_mask_patch_width
],
)
# Pixel values in buffer equal total sum of all tissue mask pixel that
# are sampled for a given tissue mask patch. Divide by number of pixels
# in patch.
np.floor_divide(
gray_image_at_output_res,
int(tisse_mask_patch_width * tisse_mask_patch_height),
out=gray_image_at_output_res,
)
# Threshold mask buffer to determine if a patch will be generated.
normalized_tissue_mask = (
gray_image_at_output_res <= int(255.0 * self.max_luminance)
) & (gray_image_at_output_res >= int(255.0 * self.min_luminance))
if np.all(~normalized_tissue_mask): # pylint: disable=invalid-unary-operand-type
raise ez_wsi_errors.DicomImageMissingRegionError(
'Tissue mask has no regions with luminance value within threshold'
f' {self.min_luminance} - {self.max_luminance}.'
)
self._tissue_mask_cache = normalized_tissue_mask
return normalized_tissue_mask