Closed gselzer closed 2 months ago
Attention: Patch coverage is 92.85714%
with 2 lines
in your changes missing coverage. Please review.
Project coverage is 77.43%. Comparing base (
3ee756c
) to head (bcf41e9
). Report is 1 commits behind head on main.
Files with missing lines | Patch % | Lines |
---|---|---|
src/ndv/viewer/_backends/_vispy.py | 92.85% | 2 Missing :warning: |
:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.
Re-implemented this calculation on the backend
The bug that I describe in https://github.com/gselzer/pymmcore-plus-sandbox/issues/15 actually originates from the reusage of
PImageHandle
s within this project. This PR provides one simple solution for solving this issue, however I'm not sure that it is the best solution, hence the draft status.The Problem:
Suppose you start up an
NDViewer
instance with data of shape(512, 512)
, and after viewing it you then callviewer.setData(new_data)
wherenew_data.shape
is(256, 256)
. My concrete use case here isNDViewer
to display a snap returned by pymmcore-plusCamera
device to2
, halving the size of images returnedNDViewer
to display a new snapThe image now appears within the top left quadrant of the canvas, which I think is not necessarily bad, however if you then click the "Set Range" button, the range does not change, and the image stays within the top left quadrant.
Under the Hood
The issue stems from a couple different shortcuts taken within
ndv
:NDViewer._update_canvas_data
, handles are reused when available. Therefore, we only create a handle for the(512, 512)
image, and reuse it for the(256, 256)
image.VispyViewerCanvas.set_range
, the range itself comes from theself._current_shape
variable, which is only updated when a new handle is created. Thus, when we reuse the handle for the new data, this member of the canvas is not updated.Note that
self._current_shape
variable does not accommodate multiplePImageHandle
objects on the same canvas either.The Solution:
Since I'm assuming it won't be terribly expensive to recompute the total shape every time we press the button, we might as well try it, but the question is where that computation should occur. It could either occur within the
PCanvas.set_range
function, implemented by each backend, or it could occur withinNDViewer._on_set_range_clicked
. I started out implementing the function in the latter as it covers both backends simultaneously, however it's probably smarter to fix this within each backend, especially since both have aself._elements
dictionary whose values should contain all handles. The other downside is that more work would be required to ensure that only active handles are involved in the computation, as I do not believe that stale handles are purged from either dictionary upon removal.Happy to hear your thoughts @tlambert03