def _normalized_tissue_mask()

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