Skip to content

Updates to NAP-9: Multiple Views #730

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

Open
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

brisvag
Copy link
Contributor

@brisvag brisvag commented Jun 10, 2025

References and relevant issues

Current (before this PR) rendered version of this NAP: https://napari.org/stable/naps/9-multiple-canvases.html

You can click on the "Check the rendered docs here!" below to see the rendered version of this update!

Description

It's time to pick up this work again! A lot of the changes should hopefully be self-explanatory, but there are a few general things to note:

  1. Naming change from "canvas" to "view": there were several discussions about this, which I tried to summarize in the Alternatives > Terminology section. Feel free to discuss and propose more!
  2. There's now a lot more reliance on the Async work. When the NAP was first drafted, we pinpointed the potential overlap and mentioned it, but by now I think that work has advanced enough to see that we should simply finish it and rely on it.
  3. Switching from a single shared layerlist (as proposed by @aganders3 in his original draft in Add NAP for multicanvas #249) to a per-view layerlist: this was my main disagreement on that version of the implementation, and I tried to highlight some of the reasons in the Alternatives > Single LayerList section. I'd like particular attention/discussion on this part so we can be more comprehensive on the pros/cons and reach a more consensus decision.
  4. I removed the architecture diagrams (sorry @aganders3 😅): they were outdated, and I found they didn't help me much with following the design logic due to the high level of detail and unrelated methods/attributes being mentioned. If you disagree and think they should be updated and added back in, let me know.

As usual for NAPs (as per the NAP workflow), remember that this is just another iteration in the discussion, so this PR will be merged relatively liberally, after enough discussion and changes have accumulated. Declaring the NAP as Accepted is not part of this PR.

@github-actions github-actions bot added the documentation Improvements or additions to documentation label Jun 10, 2025
@brisvag brisvag requested a review from a team June 10, 2025 13:52
@brisvag brisvag added the NAP napari Advancement Proposal label Jun 10, 2025
@psobolewskiPhD
Copy link
Member

I would strongly prefer anything other than View. Viewbox, viewport, canvas, ...

@TimMonko
Copy link
Contributor

I would strongly prefer anything other than View. Viewbox, viewport, canvas, ...

Why is this @psobolewskiPhD? I think Lorenzo's layout of the reasoning to choose 'view' strongly corresponds with why I prefer view as well. So, I'd like to hear what you have against it.


*Layer* - The base unit of the napari image data model. A `ViewerModel` maintains an ordered list of Layers that it may display on its Canvas.
*View* - New term introduced by this NAP in order to formalize and disentangle the previous notions of *Canvas* and *Viewer*. This NAP introduces a `View` class in napari with its own `Layerlist`, `Dims`, and `Camera`.
Copy link
Contributor

Choose a reason for hiding this comment

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

Would this also introduce a view.canvas? or, do we want canvas elements (overlays, color, etc) to live directly on the view object (and by extent, remain on viewer)? I could see both implementations being sensible

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not in my mind. I think everything that so far was under the "canvas" umbrella can go directly in View (bgcolor, size, canvas overlays). I don't see the need for adding an extra nesting level.

Copy link
Contributor

Choose a reason for hiding this comment

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

My impression was that creating a canvas object would help us untangle Qt and VisPy. By adding some of the VisPy things (like color and overlays) to Viewer/View we are mixing Qt and VisPy. If we create a canvas object we can start to separate these, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think the previous discussions about the "Canvas model" should apply to the View model here. The same is true here: by having explicit model-based API for things like "background" and "overlays" we remove the necessity of digging through vispy/qt-specific code from the napari/python side, and it's instead the callbacks who need to do the work, which allows us to detach the two parts. Detaching specifically Qt from vispy was never really part of this discussion (though still important, but harder because our vispy-side code in Canvas assumes everything happens in QT).

* For now, canvas arrangement (for example: tiling behavior) will be handled in the view only (left to Qt or custom Qt widgets). Making this state (de)serializable is out of scope for this project, but may be relevant when implementing a “savable viewer state” feature.
* Normalizing slice data for different layer types, though this may benefit in the course of this work.
* Specific UI implementations will be explored as part of this work, but I expect UX and UI will be formalized later (possibly in a separate NAP).
* For now, view arrangement (for example: tiling behavior) will be handled in the viewer only (left to Qt or custom Qt widgets). Making this state (de)serializable is out of scope for this project, but may be relevant when implementing a “savable viewer state” feature.
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you clarify two things:

  1. which tiling behavior is this referring to? In that I could rearrange the various views?
  2. what would it even mean for the views to be serializable or not? isn't serialization generally out of scope for our current model?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. yes
  2. I wouldn't say viewer serialization is "generally" out of scope: it's a pretty big and old standing goal of napari. The reason why this line is here is to address some expecations that arised during discussion in NAP-3 and later conversations, where some folks wanted this work to allow saving a "viewer workspace setup" so it could be restored.


Fig. 1: napari architecture today.
With this NAP and the above considerations, we expect to separate the above components roughly as follows:
Copy link
Contributor

Choose a reason for hiding this comment

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

This section is great, really helps me understand 🍻

@TimMonko
Copy link
Contributor

Switching from a single shared layerlist (as proposed by aganders3 in his original draft in #249) to a per-view layerlist: this was my main disagreement on that version of the implementation, and I tried to highlight some of the reasons in the Alternatives > Single LayerList section. I'd like particular attention/discussion on this part so we can be more comprehensive on the pros/cons and reach a more consensus decision.

@brisvag your description of why not to do single layerlist has mostly convinced me, I think. Though there is perhaps some hybrid method that I haven't seen described, and would happy to be corrected.
In this hybrid method there would be a full, single layerlist that has a tickbox (or equivalent) appear next to each layer corresponding to each view. Then, if the view is ticked, that layer is also moved/copied into the layerlist for that view.

For me, toggling and dragging layers only between views would be irritating. I think it also makes sense that if I adjust contrast on a single layer in the main layer list, then that layer would be updated the same in each view, but then if I only update view1's contrast of that layer, the other views are left alone.

* The application shall natively display multiple views simultaneously.
* There shall be a minimum of one view (current status) per viewer.
* Each view shall have independent:
* Layer list (necessary for visualizing different data)
Copy link
Member

Choose a reason for hiding this comment

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

Can you expand on this use case (visualizing different data)? It's unclear to me that this is a requirement.

To clarify my comment: at the community meeting we discussed this approach vs "views" of a single shared layer list, and (iirc) we found that if we could express things both as subsets of a single layer list, or as separate layer list, as long as layers could be in more than one place at once. At this point, it becomes an engineering choice rather than a requirement.

However, if you have compelling use cases that completely independent lists can achieve better, then it makes sense to expand on them here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, my point here was not meant to be about the single layerlist vs multiple layerlists. Rather about the necessity of one of the two. I'll phrase is differently.

Copy link
Contributor

Choose a reason for hiding this comment

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

Juan brings up another way to look at my discussion about the 'hybrid' design. I.e., why is multiple lists required for anything?
The one thing you bring up in the recent commits (I think) is that a single list would require de-duplication of the camera and dims and such for each view. But, why can't this single layer list store only a reference to the data itself, and not the state. Then each view specifically handle the views and state of that data. Perhaps this goes beyond my understanding in how async and lazy data would be handled (since all the data would not live in memory at once). I think with in-memory data this seems entirely reasonable, and good design to keep separate state and data, but can't say I would fully grasp why this might not work

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm sorry but I don't really follow your explanation here 😅

require de-duplication of the camera and dims and such for each view.

What do you mean with de-duplication, and where do I talk about this?

But, why can't this single layer list store only a reference to the data itself, and not the state

What do you mean by data, and by state? If you mean Layer and SliceResponse, then yes: Then each view specifically handle the views and state of that data., and this was the proposal from @aganders3. I don't think this is really a big difference between the two versions, ultimately they behave almost identically to the user (though this version might be slightly more unintuitive to implement because there's no 1-1 mapping, but it's not a big deal :P)

and good design to keep separate state and data

Again, assuming I understand what you mean above: this is true for both implementations as well!

### Part 1: View model

* Introduce a minimally disruptive `_views` attribute on the ViewerModel, and implement a `View` model to hold a `Layerlist`, a `Dims`, and a `Camera` (as well as some related concepts and models).
* using a `SelectableEventedList` for `_views` lets us easily introduce the concept of an `active` View. This, together with some simple dispatch of current viewer elements to the active view, will make this part of the implementation a drop-in replacement.
Copy link
Member

Choose a reason for hiding this comment

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

I am nodding vigorously reading this section. 😃

camera: Camera
cursor: Cursor
dims: Dims
grid: GridCanvas
Copy link
Member

Choose a reason for hiding this comment

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

I find this weird, because I thought that GridCanvas was the first implementation of multi(canvas/view). That is, the way I envisioned things instead is that grid remains in the ViewerModel and triggers the creation of many Views with linked Cameras. What am I missing?

Copy link
Member

Choose a reason for hiding this comment

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

I guess you could compose orthoviews with grid mode. But that's getting very hairy very quickly... (But certainly now I'm curious to see it 😂)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This has been a sticking point many times over... I guess I didn't explain myself very well about this. I added new sections describing why I think this are and should be separate. See the new ### Grid mode vs Multi-View in design considerations and Multi-View grid mode in Alternatives.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@psobolewskiPhD This should hopefully also answer this comment of yours.

Copy link
Contributor

Choose a reason for hiding this comment

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

For me grid mode should remain distinct in that it's, by it's implementation, relatively inflexible. This gives a more foolproof multi-layer gridded viewing experience. Sure, it uses viewboxes, but does not require its own view because of sharing dim and camera states (which, to me, is one of the largest differences about multi-view in general)

* Callbacks (interaction, events) will need to be specifically connected to individual `CanvasModel` objects where relevant (dims, camera) rather than the `ViewerModel`.
:::{admonition} TODO
Discuss whether the LayerSlicer should be singleton on the viewer or one per view.
:::
Copy link
Member

Choose a reason for hiding this comment

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

gut feeling is: one per view, with some form of linking. Orthogonal views become hard to reason about in a single object I think... A collection of SubSlicers? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree, i think multiple slicers are easier both to reason about and implement. The only reason I brought this up was in case there are performance considerations (async, threading, etc).


:::{seealso}
An alternative implementation could maintain a single centralized `LayerList`, with `Views` only having control over layer visibility. See [#Alternative single LayerList](#single-layerlist) for pros and cons.
:::
Copy link
Member

Choose a reason for hiding this comment

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

This admonition probably belongs with the requirements, or wherever you first mention the independent layer lists.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added a link in that first mention to clarify (as mentioned in oyour other comment), so I think this more extensive one can live here!

@brisvag
Copy link
Contributor Author

brisvag commented Jun 16, 2025

In this hybrid method there would be a full, single layerlist that has a tickbox (or equivalent) appear next to each layer corresponding to each view. Then, if the view is ticked, that layer is also moved/copied into the layerlist for that view.

I don't think this would be really different from @aganders3's proposal. The only difference is in the GUI, but implementation-wise it's still a single centralized layelist with Views only knowing whether a certain layer is visualised there or not.

I think it also makes sense that if I adjust contrast on a single layer in the main layer list, then that layer would be updated the same in each view, but then if I only update view1's contrast of that layer, the other views are left alone.

I didn't consider something like this until now, but I can see the uselfulness of something like this! However, I'm pretty sure we're opening the can of worms of "separating data from visualisation", as mentioned in the non-goals.
Not to say this isn't worth doing, just that we might have to expand this NAP even more into a few sub-naps :P

@psobolewskiPhD
Copy link
Member

@napari-bot make slimgallery

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation NAP napari Advancement Proposal
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants