Skip to content

Commit

Permalink
vips reader now supports caching using fetch, memory profiling is nee…
Browse files Browse the repository at this point in the history
…ded, #8
  • Loading branch information
Mr-Milk committed Dec 10, 2023
1 parent ca05cf4 commit 9132e7c
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 30 deletions.
84 changes: 55 additions & 29 deletions lazyslide/readers/vips.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
except Exception as _:
pass


VIPS_FORMAT_TO_DTYPE = {
"uchar": np.uint8,
"char": np.int8,
Expand All @@ -38,13 +37,27 @@ def vips2numpy(
)


def buffer2numpy(
buffer,
) -> np.ndarray:
"""Converts a VIPS image into a numpy array"""
return np.ndarray(
buffer=buffer,
dtype=VIPS_FORMAT_TO_DTYPE[buffer.format],
shape=[buffer.height, buffer.width, buffer.bands],
)


class VipsReader(ReaderBase):
def __init__(
self,
file: Union[Path, str],
caching: bool = True,
):
self.file = file
self.caching = caching
self.__level_vips_handler = {} # cache level handler
self.__region_vips_handler = {} # cache region handler

_vips_img = self._get_vips_level(0)
_vips_fields = set(_vips_img.get_fields())
Expand All @@ -61,47 +74,60 @@ def get_patch(
top,
width,
height,
level: int = None,
downsample: float = None,
level: int = 0,
fill=255,
):
"""Get a patch by x, y from top-left corner"""
level = self.translate_level(level)
img = self._get_vips_level(level)
patch = self._get_vips_patch(img, left, top, width, height, fill=fill)
if downsample is not None:
if downsample != 1:
patch = patch.resize(1 / downsample)
patch = vips2numpy(patch)
return self._rgba_to_rgb(patch)
image = self._get_vips_level(level)
bg = [fill]

crop_left, crop_top, crop_w, crop_h, pos = get_crop_left_top_width_height(
img_width=image.width,
img_height=image.height,
left=left,
top=top,
width=width,
height=height,
)
patch = None
if self.caching:
cropped = self.__region_vips_handler[level].fetch(
crop_left, crop_top, crop_w, crop_h
)
if pos is None:
return np.ndarray(
buffer=cropped,
dtype=VIPS_FORMAT_TO_DTYPE[image.format],
shape=[crop_h, crop_w, image.bands],
)
else:
patch = vips.Image.new_from_buffer(cropped, "").gravity(
pos, width, height, background=bg
)
else:
cropped = image.crop(crop_left, crop_top, crop_w, crop_h)
if pos is None:
patch = cropped
else:
patch = cropped.gravity(pos, width, height, background=bg)

return vips2numpy(patch) # self._rgba_to_rgb(patch)

def get_level(self, level):
level = self.translate_level(level)
img = self._get_vips_level(level)
img = vips2numpy(img)
return self._rgba_to_rgb(img)
return img # self._rgba_to_rgb(img)

def _get_vips_level(self, level=0):
"""Lazy load and load only one for all image level"""
handler = self.__level_vips_handler.get(level)
if handler is None:
handler = vips.Image.new_from_file(str(self.file), fail=True, level=level)
handler = vips.Image.new_from_file(
str(self.file), fail=True, level=level, rgb=True
)
self.__level_vips_handler[level] = handler
if self.caching:
self.__region_vips_handler[level] = vips.Region.new(handler)
return handler

@staticmethod
def _get_vips_patch(image, left, top, width, height, fill=255):
bg = [fill]
crop_left, crop_top, crop_w, crop_h, pos = get_crop_left_top_width_height(
img_width=image.width,
img_height=image.height,
left=left,
top=top,
width=width,
height=height,
)
cropped = image.crop(crop_left, crop_top, crop_w, crop_h)
if pos is None:
return cropped
else:
return cropped.gravity(pos, width, height, background=bg)
4 changes: 3 additions & 1 deletion lazyslide/wsi.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ def __init__(
image: Path | str,
h5_file: Path | str = None,
reader="auto", # openslide, vips, cucim
reader_options=None,
):
from .utils import check_wsi_path

Expand All @@ -208,6 +209,7 @@ def __init__(
if h5_file is None:
h5_file = self.image.with_suffix(".coords.h5")

self.reader_options = {} if reader_options is None else reader_options
self.h5_file = H5File(h5_file)
self._reader_class = get_reader(reader)
self._reader = None
Expand All @@ -226,7 +228,7 @@ def __repr__(self):
@property
def reader(self):
if self._reader is None:
self._reader = self._reader_class(self.image)
self._reader = self._reader_class(self.image, **self.reader_options)
return self._reader

@property
Expand Down

0 comments on commit 9132e7c

Please sign in to comment.