Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cancel obsolete requests #268

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
* text=auto eol=lf
*.py eol=lf diff=python
2 changes: 1 addition & 1 deletion tests/imageScene2D_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def testPaintDelay(self):
# during the while loops below;
# if your computer is verrry slow and the fudge too small
# the test will fail...
fudge = datetime.timedelta(milliseconds=50)
fudge = datetime.timedelta(milliseconds=100)
d = DirtyIndicator(t, delay=delay)

# make the image a little bit larger to accomodate the tile overlap
Expand Down
3 changes: 3 additions & 0 deletions volumina/pixelpipeline/datasources/minmaxsource.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ def wait(self):

return self._result

def cancel(self):
self._rawRequest.cancel()


class MinMaxSource(QObject, DataSourceABC):
"""
Expand Down
3 changes: 3 additions & 0 deletions volumina/pixelpipeline/imagesources/alphamodulated.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ def __init__(self, arrayrequest, tintColor, normalize=(0, 255)):
def wait(self):
return self.toImage()

def cancel(self):
self._arrayreq.cancel()

def toImage(self):
t = time.time()

Expand Down
3 changes: 3 additions & 0 deletions volumina/pixelpipeline/imagesources/colortable.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ def __init__(self, arrayrequest, colorTable, normalize, direct=False):
def wait(self):
return self.toImage()

def cancel(self):
self._arrayreq.cancel()

def toImage(self):
t = time.time()

Expand Down
6 changes: 6 additions & 0 deletions volumina/pixelpipeline/imagesources/dummy.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ def __init__(self, arrayreq, rect):
self.rect = rect
self._arrayreq = arrayreq

def cancel(self):
self._arrayreq.cancel()

def wait(self):
array_data = self._arrayreq.wait()
# Here's where we would do something with the data...
Expand Down Expand Up @@ -88,6 +91,9 @@ def __init__(self, arrayreq, rect):
self.rectf = QRectF(rect)
self._arrayreq = arrayreq

def cancel(self):
self._arrayreq.cancel()

def wait(self):
array_data = self._arrayreq.wait()
rectf = self.rectf
Expand Down
6 changes: 3 additions & 3 deletions volumina/pixelpipeline/imagesources/grayscale.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ def __init__(self, arrayrequest, normalize=None, direct=False):
def wait(self):
return self.toImage()

def cancel(self):
self._arrayreq.cancel()

def toImage(self):
t = time.time()

Expand Down Expand Up @@ -122,6 +125,3 @@ def toImage(self):
)

return img


# *******************************************************************************
4 changes: 4 additions & 0 deletions volumina/pixelpipeline/imagesources/rgba.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ def wait(self):
req.wait()
return self.toImage()

def cancel(self):
for req in self._requests:
req.cancel()

def toImage(self):
for i, req in enumerate(self._requests):
a = req.wait()
Expand Down
3 changes: 3 additions & 0 deletions volumina/pixelpipeline/imagesources/segmentationedges.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ def __init__(self, arrayreq, layer, rect, is_clickable):
self._layer = layer
self._is_clickable = is_clickable

def cancel(self):
self._arrayreq.cancel()

def wait(self):
array_data = self._arrayreq.wait()

Expand Down
4 changes: 4 additions & 0 deletions volumina/pixelpipeline/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ class RequestABC(ABC):
def wait(self):
"""waits until completion and returns result"""

@abstractmethod
def cancel(self):
...


class ImageSourceABC(QABC):
"""
Expand Down
21 changes: 18 additions & 3 deletions volumina/tiling/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
# PyQt
from PyQt5.QtCore import QRect, QRectF, QMutex, QObject, pyqtSignal
from PyQt5.QtWidgets import QGraphicsItem
from PyQt5.QtGui import QImage, QPainter, QTransform
from PyQt5.QtGui import QImage, QPainter, QTransform, QColor

# volumina
from volumina.patchAccessor import PatchAccessor
Expand Down Expand Up @@ -248,11 +248,11 @@ def __init__(self, tiling, stackedImageSources, cache_size: int = 100) -> None:
cache_size -- maximal number of encountered stacks
to cache, i.e. slices if the imagesources
draw from slicesources (default 10)
parent -- QObject

"""

QObject.__init__(self, parent=None)
self._current_requests = {}

self.tiling = tiling
self.axesSwapped = False
Expand Down Expand Up @@ -303,6 +303,8 @@ def waitForTiles(self, rectF=QRectF()):
while not finished:
finished = True
tiles = self.getTiles(rectF)
while self._current_requests:
time.sleep(0.01)
for tile in tiles:
finished &= tile.progress >= 1.0

Expand All @@ -315,6 +317,12 @@ def requestRefresh(self, rectF, stack_id=None, prefetch=False, layer_indexes=Non
"""
stack_id = stack_id or self._current_stack_id
tile_nos = self.tiling.intersected(rectF)
to_cancel = self._current_requests
self._current_requests = {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Volumina's code is kinda hard to navigate, so I'd love to see some type annotations like

self._current_requests: Mapping[Tuple[LAYER_ID, ImageSource, REQUEST_ID], ArrayRequest] = {}


for id_, rq in to_cancel.items():
rq.cancel()

for tile_no in tile_nos:
self._refreshTile(stack_id, tile_no, prefetch, layer_indexes)

Expand Down Expand Up @@ -405,6 +413,7 @@ def _refreshTile(self, stack_id, tile_no, prefetch=False, layer_indexes=None):
try:
# Create the request object right now, from the main thread.
ims_req = ims.request(dataRect, stack_id[1])
self._current_requests[(stack_id[1], ims, tile_no)] = ims_req
except IndeterminateRequestError:
# In ilastik, the viewer is still churning even as the user might be changing settings in the UI.
# Settings changes can cause 'slot not ready' errors during graph setup.
Expand All @@ -413,7 +422,7 @@ def _refreshTile(self, stack_id, tile_no, prefetch=False, layer_indexes=None):
logger.debug("Failed to create layer tile request", exc_info=True)
continue

timestamp = time.time()
timestamp = time.monotonic()
fetch_fn = partial(
self._fetch_layer_tile, timestamp, ims, transform, tile_no, stack_id, ims_req, self._cache
)
Expand Down Expand Up @@ -449,6 +458,7 @@ def _blendTile(self, stack_id, tile_nr):
"""
qimg = None
p = None

for i, (visible, layerOpacity, layerImageSource) in enumerate(reversed(self._sims)):
image_type = layerImageSource.image_type()
if issubclass(image_type, QGraphicsItem):
Expand Down Expand Up @@ -552,6 +562,11 @@ def _fetch_layer_tile(self, timestamp, ims, transform, tile_nr, stack_id, ims_re

if stack_id == self._current_stack_id and cache is self._cache:
self.sceneRectChanged.emit(tile_rect)
else:
ims_req.cancel()

self._current_requests.pop((stack_id[1], ims, tile_nr), None)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you using pop here because the request might have been already removed from self._current_requests?

This kinda makes me think if it would be possible for a request to overwrite an older one; If I, say, load an image in a layer, make a request, drop that layer, then load another image and make another request, is it possible for the second request to overwrite the previous one? Even worse, is it possible for the second one to be cancelled when we meant to cancel the first?


except BaseException:
logger.debug("Failed to fetch layer tile", exc_info=True)

Expand Down