Skip to content

Commit

Permalink
paquo.projects: improve ux when adding large tiff files
Browse files Browse the repository at this point in the history
- add better internal state handling if a crash occurs while adding
  a new image
- log warning when we detect that thumbnailing requires resampling
- provide verbose RuntimeError hinting at too large image size

Relates to: #30
  • Loading branch information
ap-- committed Aug 4, 2020
1 parent d7d1a31 commit f379379
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 17 deletions.
2 changes: 2 additions & 0 deletions paquo/java.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class _JClass(metaclass=JClassType):
PathROIObject = JClass("qupath.lib.objects.PathROIObject")
PathTileObject = JClass("qupath.lib.objects.PathTileObject")
Point2 = JClass("qupath.lib.geom.Point2")
ProjectImportImagesCommand = JClass('qupath.lib.gui.commands.ProjectImportImagesCommand')
ProjectIO = JClass('qupath.lib.projects.ProjectIO')
Projects = JClass('qupath.lib.projects.Projects')
ROI = JClass("qupath.lib.roi.interfaces.ROI")
Expand All @@ -73,3 +74,4 @@ class _JClass(metaclass=JClassType):

IOException = JClass("java.io.IOException")
URISyntaxException = JClass("java.net.URISyntaxException")
NegativeArraySizeException = JClass('java.lang.NegativeArraySizeException')
67 changes: 50 additions & 17 deletions paquo/projects.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import collections.abc as collections_abc
import math
import pathlib
from contextlib import contextmanager
from typing import Union, Iterable, Tuple, Optional, Iterator, \
Dict, overload, Sequence, Hashable, Any

from paquo._base import QuPathBase
from paquo._logging import redirect
from paquo._logging import redirect, get_logger
from paquo.classes import QuPathPathClass
from paquo.images import QuPathProjectImageEntry, ImageProvider, SimpleURIImageProvider, QuPathImageType
from paquo.java import ImageServerProvider, BufferedImage, \
ProjectIO, File, Projects, String, ServerTools, DefaultProject, URI, GeneralTools, IOException
from paquo.java import ImageServerProvider, BufferedImage, ProjectImportImagesCommand, \
ProjectIO, File, Projects, String, ServerTools, DefaultProject, URI, GeneralTools, IOException, \
NegativeArraySizeException

_log = get_logger(__name__)


class _ProjectImageEntriesProxy(collections_abc.Sequence):
Expand Down Expand Up @@ -129,6 +134,20 @@ def images(self) -> Sequence[QuPathProjectImageEntry]:
"""project images"""
return self._image_entries_proxy

@contextmanager
def _stage_image_entry(self, server_builder):
"""internal contextmanager for staging new entries"""
entry = self.java_object.addImage(server_builder)
try:
yield entry
except Exception:
# todo: check if we could set removeAllData to True here
self.java_object.removeImage(entry, False)
raise
finally:
# update the proxy
self._image_entries_proxy.refresh()

@redirect(stderr=True, stdout=True) # type: ignore
def add_image(self,
filename: Union[str, pathlib.Path],
Expand Down Expand Up @@ -173,21 +192,35 @@ def add_image(self,
if not server_builders:
raise IOError("no supported server builders found") # pragma: no cover
server_builder = server_builders[0]
j_entry = self.java_object.addImage(server_builder)

# all of this happens in qupath.lib.gui.commands.ProjectImportImagesCommand
try:
server = server_builder.build()
except IOException:
_, _, _sb = server_builder.__class__.__name__.rpartition(".")
raise IOError(f"{_sb} can't open {img_path}")
j_entry.setImageName(ServerTools.getDisplayableImageName(server))
# basically getThumbnailRGB(server, None) without the resize...
thumbnail = server.getDefaultThumbnail(server.nZSlices() // 2, 0)
j_entry.setThumbnail(thumbnail)

# update the proxy
self._image_entries_proxy.refresh()
with self._stage_image_entry(server_builder) as j_entry:
# all of this happens in qupath.lib.gui.commands.ProjectImportImagesCommand
try:
server = server_builder.build()
except IOException:
_, _, _sb = server_builder.__class__.__name__.rpartition(".")
raise IOError(f"{_sb} can't open {img_path}")
j_entry.setImageName(ServerTools.getDisplayableImageName(server))

# add some informative logging
_md = server.getMetadata()
width = int(_md.getWidth())
height = int(_md.getHeight())
downsamples = [float(x) for x in _md.getPreferredDownsamplesArray()]
target_downsample = math.sqrt(width / 1024.0 * height / 1024.0)
_log.info(f"Image[{width}x{height}] with downsamples {downsamples}")
if not any(d >= target_downsample for d in downsamples):
_log.warn(f"No matching downsample for thumbnail! This might take a long time...")

# set the project thumbnail
try:
thumbnail = ProjectImportImagesCommand.getThumbnailRGB(server, None)
except NegativeArraySizeException:
raise RuntimeError(
"Thumbnailing FAILED. Image might be too large and has no embedded thumbnail."
)
else:
j_entry.setThumbnail(thumbnail)

py_entry = self._image_entries_proxy[-1]
if image_type is not None:
Expand Down

0 comments on commit f379379

Please sign in to comment.