{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Getting started with the PANDA dataset\n\nThis notebook shows a few methods to load and display images from the PANDA challenge dataset. The dataset consists of around 11.000 whole-slide images (WSI) of prostate biopsies from Radboud University Medical Center and the Karolinska Institute. \n","metadata":{}},{"cell_type":"code","source":"import os\n\n# There are two ways to load the data from the PANDA dataset:\n# Option 1: Load images using openslide\nimport openslide\n# Option 2: Load images using skimage (requires that tifffile is installed)\nimport skimage.io\n\n# General packages\nimport pandas as pd\nimport numpy as np\nimport matplotlib\nimport matplotlib.pyplot as plt\nimport PIL\nfrom IPython.display import Image, display\n\n# Plotly for the interactive viewer (see last section)\nimport plotly.graph_objs as go\n","metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Location of the training images\ndata_dir = '/kaggle/input/prostate-cancer-grade-assessment/train_images'\nmask_dir = '/kaggle/input/prostate-cancer-grade-assessment/train_label_masks'\n\n# Location of training labels\ntrain_labels = pd.read_csv('/kaggle/input/prostate-cancer-grade-assessment/train.csv').set_index('image_id')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Quickstart: reading a patch in 4 lines\n\nThe example below shows in 4 lines how to extract a patch from one of the slides using OpenSlide.","metadata":{}},{"cell_type":"code","source":"# Open the image (does not yet read the image into memory)\nimage = openslide.OpenSlide(os.path.join(data_dir, '005e66f06bce9c2e49142536caf2f6ee.tiff'))\n\n# Read a specific region of the image starting at upper left coordinate (x=17800, y=19500) on level 0 and extracting a 256*256 pixel patch.\n# At this point image data is read from the file and loaded into memory.\npatch = image.read_region((17800,19500), 0, (256, 256))\n\n# Display the image\ndisplay(patch)\n\n# Close the opened slide after use\nimage.close()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Using OpenSlide to load the data\n\nIn the following sections we will load data from the slides with [OpenSlide](https://openslide.org/api/python/). The benefit of OpenSlide is that we can load arbitrary regions of the slide, without loading the whole image in memory. Want to interactively view a slide? We have added an [interactive viewer](#Interactive-viewer-for-slides) to this notebook in the last section.\n\nYou can read more about the OpenSlide python bindings in the documentation: https://openslide.org/api/python/\n\n## Loading a slide\n\nBefore we can load data from a slide, we need to open it. After a file is open we can retrieve data from it at arbitratry positions and levels.\n\n```python\nbiopsy = openslide.OpenSlide(path)\n# do someting with the slide here\nbiopsy.close()\n```","metadata":{}},{"cell_type":"markdown","source":"For this tutorial, we created a small function to show some basic information about a slide. Additionally, this function display a small thumbnail of the slide. All images in the dataset contain this metadata and you can use this in your data pipeline.","metadata":{}},{"cell_type":"code","source":"def print_slide_details(slide, show_thumbnail=True, max_size=(600,400)):\n    \"\"\"Print some basic information about a slide\"\"\"\n    # Generate a small image thumbnail\n    if show_thumbnail:\n        display(slide.get_thumbnail(size=max_size))\n\n    # Here we compute the \"pixel spacing\": the physical size of a pixel in the image.\n    # OpenSlide gives the resolution in centimeters so we convert this to microns.\n    spacing = 1 / (float(slide.properties['tiff.XResolution']) / 10000)\n    \n    print(f\"File id: {slide}\")\n    print(f\"Dimensions: {slide.dimensions}\")\n    print(f\"Microns per pixel / pixel spacing: {spacing:.3f}\")\n    print(f\"Number of levels in the image: {slide.level_count}\")\n    print(f\"Downsample factor per level: {slide.level_downsamples}\")\n    print(f\"Dimensions of levels: {slide.level_dimensions}\")\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Running the cell below loads four example biopsies using OpenSlide. Some things you can notice:\n\n- The image dimensions are quite large (typically between 5.000 and 40.000 pixels in both x and y).\n- Each slide has 3 levels you can load, corresponding to a downsampling of 1, 4 and 16. Intermediate levels can be created by downsampling a higher resolution level.\n- The dimensions of each level differ based on the dimensions of the original image.\n- Biopsies can be in different rotations. This rotation has no clinical value, and is only dependent on how the biopsy was collected in the lab.\n- There are noticable color differences between the biopsies, this is very common within pathology and is caused by different laboratory procedures.\n","metadata":{}},{"cell_type":"code","source":"example_slides = [\n    '005e66f06bce9c2e49142536caf2f6ee',\n    '00928370e2dfeb8a507667ef1d4efcbb',\n    '007433133235efc27a39f11df6940829',\n    '024ed1244a6d817358cedaea3783bbde',\n]\n\nfor case_id in example_slides:\n    biopsy = openslide.OpenSlide(os.path.join(data_dir, f'{case_id}.tiff'))\n    print_slide_details(biopsy)\n    biopsy.close()\n    \n    # Print the case-level label\n    print(f\"ISUP grade: {train_labels.loc[case_id, 'isup_grade']}\")\n    print(f\"Gleason score: {train_labels.loc[case_id, 'gleason_score']}\\n\\n\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Loading image regions/patches\n\nWith OpenSlide we can easily extract patches from the slide from arbitrary locations. Loading a specific region is done using the [read_region](https://openslide.org/api/python/#openslide.OpenSlide.read_region) function.\n\nAfter opening the slide we can, for example, load a 512x512 patch from the lowest level (level 0) at a specific coordinate.\n","metadata":{}},{"cell_type":"code","source":"biopsy = openslide.OpenSlide(os.path.join(data_dir, '00928370e2dfeb8a507667ef1d4efcbb.tiff'))\n\nx = 5150\ny = 21000\nlevel = 0\nwidth = 512\nheight = 512\n\nregion = biopsy.read_region((x,y), level, (width, height))\ndisplay(region)\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Using the `level` argument we can easily load in data from any level that is present in the slide. Coordinates passed to `read_region` are always relative to level 0 (the highest resolution).","metadata":{}},{"cell_type":"code","source":"x = 5140\ny = 21000\nlevel = 1\nwidth = 512\nheight = 512\n\nregion = biopsy.read_region((x,y), level, (width, height))\ndisplay(region)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"biopsy.close()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Loading label masks\n\nApart from the slide-level label (present in the csv file), almost all slides in the training set have an associated mask with additional label information. These masks directly indicate which parts of the tissue are healthy and which are cancerous. The information in the masks differ from the two centers:\n\n- **Radboudumc**: Prostate glands are individually labelled. Valid values are:\n  - 0: background (non tissue) or unknown\n  - 1: stroma (connective tissue, non-epithelium tissue)\n  - 2: healthy (benign) epithelium\n  - 3: cancerous epithelium (Gleason 3)\n  - 4: cancerous epithelium (Gleason 4)\n  - 5: cancerous epithelium (Gleason 5)\n- **Karolinska**: Regions are labelled. Valid values:\n  - 0: background (non tissue) or unknown\n  - 1: benign tissue (stroma and epithelium combined)\n  - 2: cancerous tissue (stroma and epithelium combined)\n\nThe label masks of Radboudumc were semi-automatically generated by several deep learning algorithms, contain noise, and can be considered as weakly-supervised labels. The label masks of Karolinska were semi-autotomatically generated based on annotations by a pathologist.\n\nThe label masks are stored in an RGB format so that they can be easily opened by image readers. The label information is stored in the red (R) channel, the other channels are set to zero and can be ignored. As with the slides itself, the label masks can be opened using OpenSlide.","metadata":{}},{"cell_type":"markdown","source":"### Visualizing the masks (using PIL)\n\nUsing a small helper function we can display some basic information about a mask. To more easily inspect the masks, we map the int labels to RGB colors using a color palette. If you prefer something like `matplotlib` you can also use `plt.imshow()` to directly show a mask (without converting it to an RGB image).","metadata":{}},{"cell_type":"code","source":"def print_mask_details(slide, center='radboud', show_thumbnail=True, max_size=(400,400)):\n    \"\"\"Print some basic information about a slide\"\"\"\n\n    if center not in ['radboud', 'karolinska']:\n        raise Exception(\"Unsupported palette, should be one of [radboud, karolinska].\")\n\n    # Generate a small image thumbnail\n    if show_thumbnail:\n        # Read in the mask data from the highest level\n        # We cannot use thumbnail() here because we need to load the raw label data.\n        mask_data = slide.read_region((0,0), slide.level_count - 1, slide.level_dimensions[-1])\n        # Mask data is present in the R channel\n        mask_data = mask_data.split()[0]\n\n        # To show the masks we map the raw label values to RGB values\n        preview_palette = np.zeros(shape=768, dtype=int)\n        if center == 'radboud':\n            # Mapping: {0: background, 1: stroma, 2: benign epithelium, 3: Gleason 3, 4: Gleason 4, 5: Gleason 5}\n            preview_palette[0:18] = (np.array([0, 0, 0, 0.5, 0.5, 0.5, 0, 1, 0, 1, 1, 0.7, 1, 0.5, 0, 1, 0, 0]) * 255).astype(int)\n        elif center == 'karolinska':\n            # Mapping: {0: background, 1: benign, 2: cancer}\n            preview_palette[0:9] = (np.array([0, 0, 0, 0.5, 0.5, 0.5, 1, 0, 0]) * 255).astype(int)\n        mask_data.putpalette(data=preview_palette.tolist())\n        mask_data = mask_data.convert(mode='RGB')\n        mask_data.thumbnail(size=max_size, resample=0)\n        display(mask_data)\n\n    # Compute microns per pixel (openslide gives resolution in centimeters)\n    spacing = 1 / (float(slide.properties['tiff.XResolution']) / 10000)\n    \n    print(f\"File id: {slide}\")\n    print(f\"Dimensions: {slide.dimensions}\")\n    print(f\"Microns per pixel / pixel spacing: {spacing:.3f}\")\n    print(f\"Number of levels in the image: {slide.level_count}\")\n    print(f\"Downsample factor per level: {slide.level_downsamples}\")\n    print(f\"Dimensions of levels: {slide.level_dimensions}\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The cells below shows two example masks from the dataset. The first mask is from Radboudumc and shows two different grades of cancer (shown in yellow and orange). The second mask is from Karolinska, the region that contains cancer is higlighted in red.\n\nNote that, eventhough a biopsy contains cancer, not all epithelial tissue has to be cancerous. Biopsies can contain a mix of cancerous and healthy tissue.","metadata":{}},{"cell_type":"code","source":"mask = openslide.OpenSlide(os.path.join(mask_dir, '08ab45297bfe652cc0397f4b37719ba1_mask.tiff'))\nprint_mask_details(mask, center='radboud')\nmask.close()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"mask = openslide.OpenSlide(os.path.join(mask_dir, '090a77c517a7a2caa23e443a77a78bc7_mask.tiff'))\nprint_mask_details(mask, center='karolinska')\nmask.close()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Visualizing masks (using matplotlib)\n\nGiven that the masks are just integer matrices, you can also use other packages to display the masks. For example, using matplotlib and a custom color map we can quickly visualize the different cancer regions:","metadata":{}},{"cell_type":"code","source":"mask = openslide.OpenSlide(os.path.join(mask_dir, '08ab45297bfe652cc0397f4b37719ba1_mask.tiff'))\nmask_data = mask.read_region((0,0), mask.level_count - 1, mask.level_dimensions[-1])\n\nplt.figure()\nplt.title(\"Mask with default cmap\")\nplt.imshow(np.asarray(mask_data)[:,:,0], interpolation='nearest')\nplt.show()\n\nplt.figure()\nplt.title(\"Mask with custom cmap\")\n# Optional: create a custom color map\ncmap = matplotlib.colors.ListedColormap(['black', 'gray', 'green', 'yellow', 'orange', 'red'])\nplt.imshow(np.asarray(mask_data)[:,:,0], cmap=cmap, interpolation='nearest', vmin=0, vmax=5)\nplt.show()\n\nmask.close()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Overlaying masks on the slides\n\nAs the masks have the same dimension as the slides, we can overlay the masks on the tissue to directly see which areas are cancerous. This overlay can help you identifying the different growth patterns. To do this, we load both the mask and the biopsy and merge them using PIL.\n\n**Tip:** Want to view the slides in a more interactive way? Using a WSI viewer you can interactively view the slides. Examples of open source viewers that can open the PANDA dataset are [ASAP](https://github.com/computationalpathologygroup/ASAP) and [QuPath](https://qupath.github.io/). ASAP can also overlay the masks on top of the images using the \"Overlay\" functionality. If you use Qupath, and the images do not load, try changing the file extension to `.vtif`.","metadata":{}},{"cell_type":"code","source":"def overlay_mask_on_slide(slide, mask, center='radboud', alpha=0.8, max_size=(800, 800)):\n    \"\"\"Show a mask overlayed on a slide.\"\"\"\n\n    if center not in ['radboud', 'karolinska']:\n        raise Exception(\"Unsupported palette, should be one of [radboud, karolinska].\")\n\n    # Load data from the highest level\n    slide_data = slide.read_region((0,0), slide.level_count - 1, slide.level_dimensions[-1])\n    mask_data = mask.read_region((0,0), mask.level_count - 1, mask.level_dimensions[-1])\n\n    # Mask data is present in the R channel\n    mask_data = mask_data.split()[0]\n\n    # Create alpha mask\n    alpha_int = int(round(255*alpha))\n    if center == 'radboud':\n        alpha_content = np.less(mask_data.split()[0], 2).astype('uint8') * alpha_int + (255 - alpha_int)\n    elif center == 'karolinska':\n        alpha_content = np.less(mask_data.split()[0], 1).astype('uint8') * alpha_int + (255 - alpha_int)\n    \n    alpha_content = PIL.Image.fromarray(alpha_content)\n    preview_palette = np.zeros(shape=768, dtype=int)\n    \n    if center == 'radboud':\n        # Mapping: {0: background, 1: stroma, 2: benign epithelium, 3: Gleason 3, 4: Gleason 4, 5: Gleason 5}\n        preview_palette[0:18] = (np.array([0, 0, 0, 0.5, 0.5, 0.5, 0, 1, 0, 1, 1, 0.7, 1, 0.5, 0, 1, 0, 0]) * 255).astype(int)\n    elif center == 'karolinska':\n        # Mapping: {0: background, 1: benign, 2: cancer}\n        preview_palette[0:9] = (np.array([0, 0, 0, 0, 1, 0, 1, 0, 0]) * 255).astype(int)\n    \n    mask_data.putpalette(data=preview_palette.tolist())\n    mask_rgb = mask_data.convert(mode='RGB')\n\n    overlayed_image = PIL.Image.composite(image1=slide_data, image2=mask_rgb, mask=alpha_content)\n    overlayed_image.thumbnail(size=max_size, resample=0)\n\n    display(overlayed_image)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"> Note: In the example below you can also observe a few pen markings on the slide (dark green smudges). These markings are not part of the tissue but were made by the pathologists who originally checked this case. These pen markings are available on some slides in the training set.","metadata":{}},{"cell_type":"code","source":"slide = openslide.OpenSlide(os.path.join(data_dir, '08ab45297bfe652cc0397f4b37719ba1.tiff'))\nmask = openslide.OpenSlide(os.path.join(mask_dir, '08ab45297bfe652cc0397f4b37719ba1_mask.tiff'))\noverlay_mask_on_slide(slide, mask, center='radboud')\nslide.close()\nmask.close()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"slide = openslide.OpenSlide(os.path.join(data_dir, '090a77c517a7a2caa23e443a77a78bc7.tiff'))\nmask = openslide.OpenSlide(os.path.join(mask_dir, '090a77c517a7a2caa23e443a77a78bc7_mask.tiff'))\noverlay_mask_on_slide(slide, mask, center='karolinska', alpha=0.6)\nslide.close()\nmask.close()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Using scikit-image & tifffile to load the data\n\nAs an alternative to OpenSlide, the slides in the PANDA dataset can also be loaded using [scikit-image](https://scikit-image.org/) with [tifffile](https://pypi.org/project/tifffile/) as the backend.\n\n> **Note:** scikit-image (<= 0.16.x) uses an internal version of the tif loader if the tifffile packages is not installed. This internal version does not support JPEG compression and can not be used to load the images in the dataset. Make sure tifffile is installed before running the examples below. This requirement is already met when running this code in a notebook on Kaggle.\n\n## Loading a slide\n\nLoading a slide with [scikit-image](https://scikit-image.org/) is similar to loading slides with OpenSlide. The major difference between scikit-image and OpenSlide is that scikit-image loads the image into memory. To extract a certain region of the image, you will need to load the whole image at one of the levels.\n\nThe images in the PANDA dataset are relatively small because each biopsy was individually extracted from the source slide. The small size makes it possible to load the slides directly into memory. Still, upon loading the image is uncompressed resulting in larger memory usage.\n\nSlides are loaded using the [MultiImage](https://scikit-image.org/docs/0.16.x/api/skimage.io.html?highlight=multiimage#skimage.io.MultiImage) class; this class gives the ability to access the individual levels of the image. By default, MultiImage tries to conserve memory usage by only caching the last image level that was accessed.\n","metadata":{}},{"cell_type":"code","source":"biopsy = skimage.io.MultiImage(os.path.join(data_dir, '0b373388b189bee3ef6e320b841264dd.tiff'))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The code below loads each individual level. You can check the memory usage of the kernel to see that loading the lowest level can require a considerate amount of memory.","metadata":{}},{"cell_type":"code","source":"for i,level in enumerate(biopsy):\n    print(f\"Biopsy level {i} dimensions: {level.shape}\")\n    print(f\"Biopsy level {i} memory size: {level.nbytes / 1024**2:.1f}mb\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"display(PIL.Image.fromarray(biopsy[-1]))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Deleting the object frees up memory\ndel biopsy","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"If you are only interested in the lowest level (highest magnification), you can also load level 0 using [imread](https://scikit-image.org/docs/0.16.x/api/skimage.io.html?highlight=imread#skimage.io.imread):","metadata":{}},{"cell_type":"code","source":"biopsy_level_0 = skimage.io.imread(os.path.join(data_dir, '0b373388b189bee3ef6e320b841264dd.tiff'))\nprint(biopsy_level_0.shape)\ndel biopsy_level_0","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Loading image regions\n\nSimilar to OpenSlide, we can extract regions from the whole image. Because the image is already in memory, this boils down to a slice on the numpy array. To illustrate we use the same coordinates as in the OpenSlide example:","metadata":{}},{"cell_type":"code","source":"biopsy = skimage.io.MultiImage(os.path.join(data_dir, '00928370e2dfeb8a507667ef1d4efcbb.tiff'))\n\nx = 5150\ny = 21000\nlevel = 0\nwidth = 512\nheight = 512\n\npatch = biopsy[0][y:y+width, x:x+height]\n\n# You can also visualize patches with matplotlib\nplt.figure()\nplt.imshow(patch)\nplt.show()\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"To load the same region from level 1, we have to devide the coordinates with the downsample factor (4 per level). This is different from Openslide that always works with coordinates from level 0.","metadata":{}},{"cell_type":"code","source":"x = 5150 // 4\ny = 21000 // 4\nwidth = 512\nheight = 512\n\npatch = biopsy[1][y:y+width, x:x+height]\n\nplt.figure()\nplt.imshow(patch)\nplt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"x = 5150 // (4*4)\ny = 21000 // (4*4)\nwidth = 512\nheight = 512\n\npatch = biopsy[2][y:y+width, x:x+height]\n\nplt.figure()\nplt.imshow(patch)\nplt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Free up memory\ndel biopsy","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Loading label masks\n\nLoading label masks using scikit-image is similar to loading the slides. As the label information is in the R channel, other channels can be discarded. Please refer to the \"OpenSlide - Loading label masks\" section for more information about the contents of the label masks.","metadata":{}},{"cell_type":"code","source":"maskfile = skimage.io.MultiImage(os.path.join(mask_dir, '090a77c517a7a2caa23e443a77a78bc7_mask.tiff'))\nmask_level_2 = maskfile[-1][:,:,0]\n\nplt.figure()\nplt.imshow(mask_level_2)\nplt.colorbar()\nplt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"del maskfile","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Interactive viewer for slides\n\nUsing [Plotly](https://kite.com/python/docs/plotly.graph_objs) we can make an interactive viewer that works inside a notebook. Using this viewer you can load any image from the PANDA dataset and interactively zoom in to specific regions. This viewer is a great way of inspecting the data in more detail.\n\n> **Note:** The code below only works when you run this notebook yourself. The output is not shown when purely viewing the notebook as it requires access to the source image.\n\nWant to investigate slides locally on your machine? Using a WSI viewer you can interactively view the slides on your own machine. Examples of open source viewers that can open the PANDA dataset are [ASAP](https://github.com/computationalpathologygroup/ASAP) and [QuPath](https://qupath.github.io/). ASAP can also overlay the masks on top of the images using the \"Overlay\" functionality. If you use Qupath, and the images do not load, try changing the file extension to `.vtif`.\n\n","metadata":{}},{"cell_type":"code","source":"class WSIViewer(object):\n    def __init__(self, plot_size = 1000):\n        self._plot_size = plot_size\n        \n    def set_slide(self, slide_path):      \n        self._slide = openslide.open_slide(slide_path)\n        self._base_dims = self._slide.level_dimensions[-1]\n        self._base_ds = self._slide.level_downsamples[-1]\n        img_arr = self._slide.read_region((0,0), len(self._slide.level_dimensions[-1]), (self._base_dims[0], self._base_dims[1]))\n        \n        self._fig = go.FigureWidget(data=[{'x': [0, self._base_dims[0]], \n                                           'y': [0, self._base_dims[1]], \n                                           'mode': 'markers',\n                                           'marker': {'opacity': 0}}], # invisible trace to init axes and to support autoresize\n                                    layout={'width': self._plot_size, 'height': self._plot_size, 'yaxis' : dict(scaleanchor = \"x\", scaleratio = 1)})  \n        # Set background image\n        self._fig.layout.images = [go.layout.Image(\n            source = img_arr,  # plotly now performs auto conversion of PIL image to png data URI\n            xref = \"x\",\n            yref = \"y\",\n            x = 0,\n            y = 0,\n            sizex = self._base_dims[0],\n            sizey = self._base_dims[1],\n            sizing = \"stretch\",\n            layer = \"below\")]\n        self._fig.update_layout(plot_bgcolor='rgba(0,0,0,0)',xaxis_showgrid=False, yaxis_showgrid=False, xaxis_zeroline=False, yaxis_zeroline=False);        \n        self._fig.layout.on_change(self._update_image, 'xaxis.range', 'yaxis.range', 'width', 'height')          \n\n    def _gen_zoomed_image(self, x_range, y_range):\n        # Below is a workaround which rounds image requests to multiples of 4, once the libpixman fix is in place these can be removed\n        #xstart = x_range[0] * self._base_ds\n        #ystart = (self._base_dims[1] - y_range[1]) * self._base_ds \n        xstart = 4 * round(x_range[0] * self._base_ds / 4)\n        ystart = 4 * round((self._base_dims[1] - y_range[1]) * self._base_ds / 4)\n        xsize0 = (x_range[1] - x_range[0]) * self._base_ds\n        ysize0 = (y_range[1] - y_range[0]) * self._base_ds\n        if (xsize0 > ysize0):\n            req_downs = xsize0 / self._plot_size\n        else:\n            req_downs = ysize0 / self._plot_size\n        req_level = self._slide.get_best_level_for_downsample(req_downs)\n        level_downs = self._slide.level_downsamples[req_level]\n        # Nasty workaround for buggy container\n        level_size_x = int(xsize0 / level_downs)\n        level_size_y = int(ysize0 / level_downs)\n        new_img = self._slide.read_region((int(xstart), int(ystart)), req_level, (level_size_x, level_size_y)).resize((1000,1000)) # Letting PIL do the resize is faster than plotly\n        return new_img\n    \n    def _update_image(self, layout, x_range, y_range, plot_width, plot_height):\n        img = self._fig.layout.images[0]\n        # Update with batch_update so all updates happen simultaneously\n        with self._fig.batch_update():\n            new_img = self._gen_zoomed_image(x_range, y_range)\n            img.x = x_range[0]\n            img.y = y_range[1]\n            img.sizex = x_range[1] - x_range[0]\n            img.sizey = y_range[1] - y_range[0]\n            img.source = new_img\n\n    def show(self):\n        return self._fig","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"viewer = WSIViewer()\nviewer.set_slide(os.path.join(data_dir, '08ab45297bfe652cc0397f4b37719ba1.tiff'))\nviewer.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}