pyapp-kit / ndv

Simple, fast-loading, n-dimensional array viewer with minimal dependencies.
BSD 3-Clause "New" or "Revised" License
29 stars 4 forks source link

Custom DataWrapper example does not correctly display images in the NDViewer #36

Open kmdouglass opened 2 weeks ago

kmdouglass commented 2 weeks ago

Description

After a discussion with @tlambert03 on image.sc, I tested an example that he provided that demonstrates how to create custom DataWrappers: https://github.com/pyapp-kit/ndv/blob/5fefbd196242474c351587ded75aaff32ed8663c/examples/custom_store.py

Unfortunately, the resulting dataset that is displayed does not show the correct range of pixel values; they tend to be clustered around 109. Additionally, the sliders for navigating through the dataset do not change the displayed image when clicked.

What I Did

As a check, and because I'm ultimately interested in images with low contrast, I modified the example to show images of random integers between 90 and 110 with a dtype of np.uint16.

from typing import Any

import ndv
import numpy as np

if __name__ == "__main__":
    class MyArrayThing:
        def __init__(self, shape: tuple[int, ...]) -> None:
            self.shape = shape
            self._data = np.random.randint(90, 110, shape, dtype=np.uint16)

        def __getitem__(self, item: Any) -> np.ndarray:
            return self._data[item]  # type: ignore [no-any-return]

    class MyWrapper(ndv.DataWrapper[MyArrayThing]):
        @classmethod
        def supports(cls, data: Any) -> bool:
            if isinstance(data, MyArrayThing):
                return True
            return False

        def sizes(self):
            """Return a mapping of {dim: size} for the data"""
            return {f"dim_{k}": v for k, v in enumerate(self.data.shape)}

        def isel(self, indexers) -> Any:
            """Convert mapping of {dim: index} to conventional indexing"""
            idx = tuple(indexers.get(k, slice(None)) for k in range(len(self.data.shape)))
            return self.data[idx]

    data = MyArrayThing((10, 3, 256, 256))

    ndv.imshow(data)

A screenshot of what I see immediately after running the script follows:

image

I performed two sanity checks:

  1. I checked the value of the _data attribute of the MyArrayThing instance and the array contains the correct range of values.
  2. I used a normal numpy array data = np.random.randint(90, 110, (10, 3, 256, 256), dtype=np.uint16) in the call to ndv.imshow(data) and everything worked as expected.

I looked briefly into the code and it looks like the data is ultimately owned by a DataWrapper instance, so any distortion of the underlying values might occur there.

Edits