Reading NDPI pyramid levels

I just shared a confidential NDPI file with Erin Diel that shows the behaviour that I’m about to describe. I’m using Bioformats 6.6.1.

Using:

package com.actelion.research.orbit.imageAnalysis.utils;

import loci.formats.FormatException;
import loci.formats.IFormatReader;
import loci.formats.ImageReader;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;

public class AnalyzeImagePyramid {

    public static String pyramidInfo(List<Path> files) {
        StringBuilder sb = new StringBuilder();

        for (Path path : files) {
            sb.append(path.toString()).append(pyramidInfo(path)).append("\n");
        }
        return sb.toString();
    }

    public static String pyramidInfo(Path file) {
        StringBuilder sb = new StringBuilder();

        try (IFormatReader r = new ImageReader())
        {
            sb.append(FormatTools.VERSION).append("\n");
            r.setFlattenedResolutions(false);
            r.setId(file.toString());
            sb.append("filename: ").append(file.toString()).append("\n");
            sb.append("images: ").append(r.getImageCount()).append("\n");
            sb.append("series count: ").append(r.getSeriesCount()).append("\n");

            sb.append("\n");
            for (int i = 0; i < r.getSeriesCount(); i++) {
                r.setSeries(i);
                sb.append("series: ").append(r.getSeries()).append(":").append("\n");

                sb.append("resolution count: ").append(r.getResolutionCount()).append("\n");
                for (int j = 0; j < r.getResolutionCount(); j++) {
                    r.setResolution(j);
                    sb.append("metadata: ").append(r.getSeriesMetadata());
                    sb.append("resolution: ")
                            .append(r.getResolution())
                            .append(":")
                            .append("\n");
                    sb.append("dimensions: ")
                            .append(r.getSizeX())
                            .append("x")
                            .append(r.getSizeY())
                            .append("  ratio: ")
                            .append(r.getSizeX() / (float) r.getSizeY())
                            .append(" interleaved: ")
                            .append(r.isInterleaved())
                            .append("\n");
                }
            }
        } catch (IOException | FormatException e) {
            e.printStackTrace();
        }

        return sb.toString();
    }


    public static void main(String[] args) throws IOException, FormatException {
        String imageName = "testimage.ndpi";
        System.out.println(pyramidInfo(Paths.get(imageName)));
    }
}

I get:


11:18:23.706 ERROR l.formats.tiff.TiffParser:1408 - Error reading IFD type at: 3162495094
11:18:23.714 ERROR l.formats.tiff.TiffParser:1408 - Error reading IFD type at: 3162495094
11:18:23.716 ERROR l.formats.tiff.TiffParser:1408 - Error reading IFD type at: 3162495094
11:18:23.718 ERROR l.formats.tiff.TiffParser:1408 - Error reading IFD type at: 3162495094
11:18:23.720 ERROR l.formats.tiff.TiffParser:1408 - Error reading IFD type at: 3162495094
6.6.1
filename: testimage.ndpi
images: 1
series count: 3

series: 0:
resolution count: 6
metadata: {}resolution: 0:
dimensions: 250880x209664  ratio: 1.1965812 interleaved: true
metadata: {}resolution: 1:
dimensions: 125440x104832  ratio: 1.1965812 interleaved: true
metadata: {}resolution: 2:
dimensions: 62720x52416  ratio: 1.1965812 interleaved: true
metadata: {}resolution: 3:
dimensions: 31360x26208  ratio: 1.1965812 interleaved: true
metadata: {}resolution: 4:
dimensions: 15680x13104  ratio: 1.1965812 interleaved: true
metadata: {}resolution: 5:
dimensions: 7840x6552  ratio: 1.1965812 interleaved: false
series: 1:
resolution count: 1
metadata: {}resolution: 0:
dimensions: 1024x696  ratio: 1.4712644 interleaved: false
series: 2:
resolution count: 1
metadata: {}resolution: 0:
dimensions: 512x348  ratio: 1.4712644 interleaved: false

But if I open the image using Orbit Image Analysis and the Native NDPRead2.dll then I see that there are several more layers read.

11:20:28.550 INFO  c.a.r.o.i.c.OrbitImageAnalysis:1420 - loading image: testimage.ndpi
11:20:29.223 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:47 - loading NDPI image using native ndpi library: testimage.ndpi [0]
11:20:29.226 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:56 - testimage.ndpi loaded [250880 x 209664]
11:20:29.771 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:47 - loading NDPI image using native ndpi library: testimage.ndpi [0]
11:20:29.774 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:56 - testimage.ndpi loaded [250880 x 209664]
11:20:30.907 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:47 - loading NDPI image using native ndpi library: testimage.ndpi [1]
11:20:30.910 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:56 - testimage.ndpi loaded [125440 x 104832]
11:20:31.435 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:47 - loading NDPI image using native ndpi library: testimage.ndpi [1]
11:20:31.437 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:56 - testimage.ndpi loaded [125440 x 104832]
11:20:31.954 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:47 - loading NDPI image using native ndpi library: testimage.ndpi [2]
11:20:31.957 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:56 - testimage.ndpi loaded [62720 x 52416]
11:20:32.459 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:47 - loading NDPI image using native ndpi library: testimage.ndpi [2]
11:20:32.461 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:56 - testimage.ndpi loaded [62720 x 52416]
11:20:32.983 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:47 - loading NDPI image using native ndpi library: testimage.ndpi [3]
11:20:32.986 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:56 - testimage.ndpi loaded [31360 x 26208]
11:20:33.507 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:47 - loading NDPI image using native ndpi library: testimage.ndpi [3]
11:20:33.510 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:56 - testimage.ndpi loaded [31360 x 26208]
11:20:34.060 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:47 - loading NDPI image using native ndpi library: testimage.ndpi [4]
11:20:34.062 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:56 - testimage.ndpi loaded [15680 x 13104]
11:20:34.584 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:47 - loading NDPI image using native ndpi library: testimage.ndpi [4]
11:20:34.587 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:56 - testimage.ndpi loaded [15680 x 13104]
11:20:35.105 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:47 - loading NDPI image using native ndpi library: testimage.ndpi [5]
11:20:35.108 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:56 - testimage.ndpi loaded [7840 x 6552]
11:20:35.621 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:47 - loading NDPI image using native ndpi library: testimage.ndpi [5]
11:20:35.623 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:56 - testimage.ndpi loaded [7840 x 6552]
11:20:36.133 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:47 - loading NDPI image using native ndpi library: testimage.ndpi [6]
11:20:36.136 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:56 - testimage.ndpi loaded [3920 x 3276]
11:20:36.660 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:47 - loading NDPI image using native ndpi library: testimage.ndpi [6]
11:20:36.662 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:56 - testimage.ndpi loaded [3920 x 3276]
11:20:37.190 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:47 - loading NDPI image using native ndpi library: testimage.ndpi [7]
11:20:37.193 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:56 - testimage.ndpi loaded [1960 x 1638]
11:20:37.709 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:47 - loading NDPI image using native ndpi library: testimage.ndpi [7]
11:20:37.712 INFO  c.a.r.o.i.d.l.n.NDPIImageNative:56 - testimage.ndpi loaded [1960 x 1638]
11:20:38.763 INFO  c.a.r.o.i.d.l.ImageUtils:44 - native turbojpeg lib successfully loaded
11:20:38.763 INFO  c.a.r.o.i.d.l.ImageUtils:44 - native turbojpeg lib successfully loaded
11:20:38.781 INFO  c.a.r.o.i.d.l.ImageUtils:44 - native turbojpeg lib successfully loaded
11:20:38.792 INFO  c.a.r.o.i.d.l.ImageUtils:44 - native turbojpeg lib successfully loaded

So two questions. Is the fact that there are fewer layers read by Bioformats a bug? What do the Error reading IFD type messages mean? Should this be scary?

I’m happy to do some debugging here, but if someone could point me in the right direction that would be really appreciated.

So two questions. Is the fact that there are fewer layers read by Bioformats a bug?

It looks like we don’t yet have the problematic file, and I have not yet been able to reproduce the issue so far on other .ndpi files. If you are able to send the file to @erindiel, we can certainly investigate further.

What do the Error reading IFD type messages mean? Should this be scary?

These messages can be safely ignored. They occur during file type checking in the call to r.setId(file.toString()). NDPI files are specialized TIFFs, but files larger than 4 GB do not conform to the BigTIFF specification. The file type checking logic tries a few other TIFF-based readers before the NDPI reader, all of which will produce these errors indicating that basic TIFF/BigTIFF parsing fails. The NDPI reader has its own special logic for handling files larger than 4 GB.

Thank you for sending the file, @jfkotw. So far, I have not found any evidence that this is a bug in Bio-Formats. I compared the file offset and byte counts for each tile and IFD (metadata structure) in the .ndpi file against the overall file length to see if there were any unused bytes that might contain additional pyramid resolutions, with no luck.

One thing to consider is that Hamamatsu’s NDP.view2 viewer provides 8 preset magnifications for viewing (plus the overview image), as noted in page 31 section 7-3-2-2:

It may be that NDPRead2.dll always provides 8 layers to match those presets, and calculates any that aren’t present in the file. If all of your files have at least 8 layers reported by NDPRead2.dll, then that’s probably what is happening.