question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

`WSIReader` does not support `TiffFile` backend

See original GitHub issue

image_reader.WSIReader supports tifffile (https://github.com/Project-MONAI/MONAI/blob/e4624678cc15b5a5670776715c935279f7837c89/monai/data/image_reader.py#L1257), but it does not supported in wsi_reader.WSIReader. Can I ask what is the purpose here to remove the supports?

I’m not sure but it seems tifffile loads faster in some cases. For example: Screen Shot 2022-08-12 at 6 18 58 PM

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:11 (10 by maintainers)

github_iconTop GitHub Comments

1reaction
gigonycommented, Aug 18, 2022

Hi @yiheng-wang-nv and @drbeh

Image information

❯ tiffinfo notebooks/input/006388_0.tif
TIFF Directory at offset 0x4e3268cc (1311926476)
  Image Width: 34007 Image Length: 60797
  Tile Width: 128 Tile Length: 128
  Resolution: 10, 10 pixels/cm
  Bits/Sample: 8
  Sample Format: unsigned integer
  Compression Scheme: AdobeDeflate
  Photometric Interpretation: RGB color
  Orientation: row 0 top, col 0 lhs
  Samples/Pixel: 3
  Planar Configuration: single image plane
  Predictor: horizontal differencing 2 (0x2)

Test code

import os
from cucim import CuImage
from contextlib import ContextDecorator
from time import perf_counter
from tifffile import imread
import numpy as np
import torch

import monai
from monai.transforms import LoadImage

loader = LoadImage(reader=monai.data.image_reader.WSIReader,
                   backend="tifffile", image_only=True)
loader_cucim = LoadImage(
    reader=monai.data.wsi_reader.WSIReader, backend="cucim", image_only=True)


class Timer(ContextDecorator):
    def __init__(self, message):
        self.message = message
        self.end = None

    def elapsed_time(self):
        self.end = perf_counter()
        return self.end - self.start

    def __enter__(self):
        self.start = perf_counter()
        return self

    def __exit__(self, exc_type, exc, exc_tb):
        if not self.end:
            self.elapsed_time()
        print("{} : {}".format(self.message, self.end - self.start))


image_file = "/home/gbae/repo/cucim/notebooks/input/006388_0.tif"


with Timer("  Thread elapsed time (tifffile)") as timer:
    image = imread(image_file)
print(f"    type: {type(image)}, shape: {image.shape}, dtype: {image.dtype}")

del image

# https://github.com/rapidsai/cucim/wiki/release_notes_v22.02.00#example-api-usages

with Timer("  Thread elapsed time (cucim, num_workers=1)") as timer:
    img = CuImage(image_file)
    image = img.read_region()
    image = np.asarray(image)
print(f"    type: {type(image)}, shape: {image.shape}, dtype: {image.dtype}")

del image

with Timer("  Thread elapsed time (cucim, num_workers=1, specifying location/size)") as timer:
    img = CuImage(image_file)
    image = img.read_region((0, 0), (34007, 60797))
    image = np.asarray(image)
print(f"    type: {type(image)}, shape: {image.shape}, dtype: {image.dtype}")

del image

num_threads = os.cpu_count()
with Timer(f"  Thread elapsed time (cucim, num_workers={num_threads})") as timer:
    img = CuImage(image_file)
    image = img.read_region(num_workers=num_threads)
    image = np.asarray(image)
print(f"    type: {type(image)}, shape: {image.shape}, dtype: {image.dtype}")

del image

with Timer("  Thread elapsed time (tifffile loader)") as timer:
    image = loader(image_file)
print(f"    type: {type(image)}, shape: {image.shape}, dtype: {image.dtype}")

del image

with Timer("  Thread elapsed time (cucim loader)") as timer:
    image = loader_cucim(image_file)
print(f"    type: {type(image)}, shape: {image.shape}, dtype: {image.dtype}")

del image

with Timer("  Thread elapsed time (mimic monai cucim loader, num_workers=1)") as timer:
    img = CuImage(image_file)
    image = img.read_region(num_workers=1)
    image = np.moveaxis(image, -1, 0)
    image = np.ascontiguousarray(image)
    image = np.asarray(image, dtype=np.float32)
    image = torch.from_numpy(image)
print(f"    type: {type(image)}, shape: {image.shape}, dtype: {image.dtype}")

del image

with Timer(f"  Thread elapsed time (mimic monai cucim loader, num_workers={num_threads})") as timer:
    img = CuImage(image_file)
    image = img.read_region(num_workers=num_threads)
    image = np.moveaxis(image, -1, 0)
    image = np.ascontiguousarray(image)
    image = np.asarray(image, dtype=np.float32)
    image = torch.from_numpy(image)
print(f"    type: {type(image)}, shape: {image.shape}, dtype: {image.dtype}")

This is the result


  Thread elapsed time (tifffile) : 10.761376535985619
    type: <class 'numpy.ndarray'>, shape: (60797, 34007, 3), dtype: uint8
  Thread elapsed time (cucim, num_workers=1) : 6.1665689669316635
    type: <class 'numpy.ndarray'>, shape: (60797, 34007, 3), dtype: uint8
  Thread elapsed time (cucim, num_workers=1, specifying location/size) : 6.136442722985521
    type: <class 'numpy.ndarray'>, shape: (60797, 34007, 3), dtype: uint8
  Thread elapsed time (cucim, num_workers=32) : 1.1633656659396365
    type: <class 'numpy.ndarray'>, shape: (60797, 34007, 3), dtype: uint8
  Thread elapsed time (tifffile loader) : 15.5943678249605
    type: <class 'monai.data.meta_tensor.MetaTensor'>, shape: (3, 60797, 34007), dtype: torch.float32
  Thread elapsed time (cucim loader) : 14.059558755951002
    type: <class 'monai.data.meta_tensor.MetaTensor'>, shape: (3, 60797, 34007), dtype: torch.float32
  Thread elapsed time (mimic monai cucim loader, num_workers=1) : 10.625215215026401
    type: <class 'torch.Tensor'>, shape: torch.Size([3, 60797, 34007]), dtype: torch.float32
  Thread elapsed time (mimic monai cucim loader, num_workers=32) : 5.21964049898088
    type: <class 'torch.Tensor'>, shape: torch.Size([3, 60797, 34007]), dtype: torch.float32

@yiheng-wang-nv Since MONAI’s image loader force to have channel first tensor, if your pipeline works good with channel last tensor, please use cuCIM directly (14 secs-> 6 secs for 1 thread, 14 -> 1 sec for 32 thread)

And you can also get similar data with the image loaded by MONAI if you directly use cuCIM. In this case, you can use multithreadeds to load the image faster (5 secs) And if you use MONAI with the patch (https://github.com/Project-MONAI/MONAI/pull/4934) applied, monai cucim loader will get a similar performance with using cuCIM directly (14 secs -> 10 secs).

In current MONAI’s implementation, cuCIM backend has a disadvantage over tifffile backend (For tifffile backend, converting to contiguous array is applied first before converting data type [uint8 -> float32] ).

I think that could be a reason for the differences in a different system.

With #4934 applied, I can see the following results (14 secs to 10 secs for monai cucim loader)

  Thread elapsed time (tifffile) : 11.544818989932537
    type: <class 'numpy.ndarray'>, shape: (60797, 34007, 3), dtype: uint8
  Thread elapsed time (cucim, num_workers=1) : 5.978483060956933
    type: <class 'numpy.ndarray'>, shape: (60797, 34007, 3), dtype: uint8
  Thread elapsed time (cucim, num_workers=1, specifying location/size) : 6.006091748946346
    type: <class 'numpy.ndarray'>, shape: (60797, 34007, 3), dtype: uint8
  Thread elapsed time (cucim, num_workers=32) : 1.165883683017455
    type: <class 'numpy.ndarray'>, shape: (60797, 34007, 3), dtype: uint8
  Thread elapsed time (tifffile loader) : 15.213442040025257
    type: <class 'monai.data.meta_tensor.MetaTensor'>, shape: (3, 60797, 34007), dtype: torch.float32
  Thread elapsed time (cucim loader) : 10.268190009985119
    type: <class 'monai.data.meta_tensor.MetaTensor'>, shape: (3, 60797, 34007), dtype: torch.float32
  Thread elapsed time (mimic monai cucim loader, num_workers=1) : 10.394060018006712
    type: <class 'torch.Tensor'>, shape: torch.Size([3, 60797, 34007]), dtype: torch.float32
  Thread elapsed time (mimic monai cucim loader, num_workers=32) : 5.20623895409517
    type: <class 'torch.Tensor'>, shape: torch.Size([3, 60797, 34007]), dtype: torch.float32

Since data type and tensor shape conversion takes time, it might be a good idea to do that in GPU.

cc @grlee77 @jakirkham

1reaction
cgohlkecommented, Aug 12, 2022

FWIW, 006388_0.tif is not really a WSI, more a regular TIFF: no pyramid levels, wrong resolution metadata, small tiles, ZIP compression, horizontal predictor.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Source code for monai.data.image_reader
This function must return two objects, the first is a numpy array of image data, the second is a dictionary of metadata. Args:...
Read more >
imageio.plugins.tifffile
Backend: internal. Provides support for a wide range of Tiff images using the tifffile backend. ... If 1, no prediction scheme was used...
Read more >
CHANGELOG.md · Gitee 极速下载/MONAI - Gitee.com
Fixed issue of not skipping post activations in Convolution when input ... Issue of WSIReader when the backend is TiffFile; Issue of deprecated_args...
Read more >
An End-to-End Toolbox for Advanced Tissue Image Analytics
However, OpenSlide does not support several image formats. For ... OpenSlide5 backend, OME-TIFF files using a tifffile ...
Read more >
pandas remove trailing zeros from float - Kantam projects
Convert the bounding boxes of the current mode to corners. backend the name ... WebWe would like to show you a description here...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found