Repository: brainglobe/brainrender-napari (language: python)

Issue:
[BUG] Atlas entries do not show on fresh install
**Describe the bug**
On a fresh pip install of either brainrender-napari or brainglobe when I open brainrender there is no atlases in the atlas viewer. I have never used brainglobe before, so maybe this is the expected behavior? Documentation makes me think otherwise. 

**Expected behaviour**
There would be a list of atlases I can click on as according to [the tutorial.](https://brainglobe.info/tutorials/visualise-atlas-napari.html )

**Screenshots**
![image](https://github.com/brainglobe/brainrender/assets/47310455/cc3fae06-bb3d-4657-ae5b-85f9c66de8b6)

**Computer used (please complete the following information):**
napari: 0.4.19.post1
Platform: Windows-10-10.0.22631-SP0
Python: 3.10.14 | packaged by conda-forge | (main, Mar 20 2024, 12:40:08) [MSC v.1938 64 bit (AMD64)]


Move version manager widget to bg-atlasapi
The functionality in the version manager widget is not really related to data visualisation, and therefore doesn't really belong in brainrender, but much more into the atlas API repo.

We could have `bg_atlasapi[gui]` and `bg_atlasapi[napari]` versions of the package, allowing reuse of the Qt widgets outside of napari.
[Feature] Version management views should be split into two
**Is your feature request related to a problem? Please describe.**
There are many atlases and we plan to add more - but we expect users to typically only download and interact with a few of them. So it should be easier to distinguish the local and not-local atlases.

**Describe the solution you'd like**
The version management widget should contain two views, one for the locally available atlases (with a signal to show which ones can be updated) and one for the not-yet-downloaded atlases.

**Describe alternatives you've considered**
Could also colour-code rows, but this is both more difficult to implement and less clear to users, I think.

**Additional context**
May be worth refactoring the code to use proxy models instead of different views in this process?



Discussion / hints:
Thanks for reporting @TimMonko 

You're right, the tutorial is out-of-sync with the latest version. The widget currently only shows the atlases you have already downloaded.

Our plan is to have two separate `brainrender-napari widgets - one for downloading atlases, and one for visualising them.

To get it to work in the meantime, you should be able [to use the command line](https://brainglobe.info/documentation/brainglobe-atlasapi/usage/command-line-interface.html) to download [the atlas you're interested in](https://brainglobe.info/documentation/brainglobe-atlasapi/index.html#atlases-available). For example, for the Allen Mouse atlas at 50 um resolution, you would do:

```bash
brainglobe install -a allen_mouse_25um
```

Then you should be able to close and reopen napari, and continue the tutorial from step 3.



Fix patch:
diff --git a/brainrender_napari/__init__.py b/brainrender_napari/__init__.py
index 5a57962..3d4fafe 100644
--- a/brainrender_napari/__init__.py
+++ b/brainrender_napari/__init__.py
@@ -1,4 +1,4 @@
 __version__ = "0.0.1"
 
 
-__all__ = "BrainrenderWidget"
+__all__ = "BrainrenderViewerWidget, BrainrenderManagerWidget"
diff --git a/brainrender_napari/brainrender_manager_widget.py b/brainrender_napari/brainrender_manager_widget.py
new file mode 100644
index 0000000..1391533
--- /dev/null
+++ b/brainrender_napari/brainrender_manager_widget.py
@@ -0,0 +1,53 @@
+"""
+A napari widget to download and update atlases.
+
+Available atlases are shown in a table view using the Qt model/view framework
+[Qt Model/View framework](https://doc.qt.io/qt-6/model-view-programming.html)
+"""
+
+from brainglobe_utils.qtpy.logo import header_widget
+from napari.viewer import Viewer
+from qtpy.QtWidgets import (
+    QGroupBox,
+    QVBoxLayout,
+    QWidget,
+)
+
+from brainrender_napari.widgets.atlas_manager_view import AtlasManagerView
+
+
+class BrainrenderManagerWidget(QWidget):
+    """The purpose of this class is
+    * to hold atlas visualisation widgets for napari
+    * coordinate between these widgets and napari
+    """
+
+    def __init__(self, napari_viewer: Viewer):
+        """Instantiates the atlas viewer widget
+        and sets up coordinating connections"""
+        super().__init__()
+
+        self._viewer = napari_viewer
+        self.setLayout(QVBoxLayout())
+        self.layout().addWidget(
+            header_widget(
+                "brainrender",
+                "Atlas management",
+                tutorial_file_name="manage-atlas-napari.html",
+                citation_doi="https://doi.org/10.21105/joss.02668",
+                github_repo_name="brainrender-napari",
+                help_text="For help, hover the cursor over the " "atlases.",
+            )
+        )
+
+        # create widgets
+        self.atlas_manager_view = AtlasManagerView(parent=self)
+
+        # add widgets to the layout as group boxes
+        self.atlas_manager_group = QGroupBox("Atlas Manager")
+        self.atlas_manager_group.setToolTip(
+            "Double-click on row to download/update an atlas"
+        )
+        self.atlas_manager_group.setLayout(QVBoxLayout())
+        self.atlas_manager_group.layout().addWidget(self.atlas_manager_view)
+        self.layout().addWidget(self.atlas_manager_group)
diff --git a/brainrender_napari/brainrender_widget.py b/brainrender_napari/brainrender_viewer_widget.py
similarity index 99%
rename from brainrender_napari/brainrender_widget.py
rename to brainrender_napari/brainrender_viewer_widget.py
index de776a7..8254be8 100644
--- a/brainrender_napari/brainrender_widget.py
+++ b/brainrender_napari/brainrender_viewer_widget.py
@@ -26,7 +26,7 @@
 from brainrender_napari.widgets.structure_view import StructureView
 
 
-class BrainrenderWidget(QWidget):
+class BrainrenderViewerWidget(QWidget):
     """The purpose of this class is
     * to hold atlas visualisation widgets for napari
     * coordinate between these widgets and napari
diff --git a/brainrender_napari/napari.yaml b/brainrender_napari/napari.yaml
index f4415b3..1e7fb67 100644
--- a/brainrender_napari/napari.yaml
+++ b/brainrender_napari/napari.yaml
@@ -2,9 +2,14 @@ name: brainrender-napari
 display_name: brainrender
 contributions:
   commands:
-    - id: brainrender-napari.make_brainrender_widget
-      python_name: brainrender_napari.brainrender_widget:BrainrenderWidget
-      title: Make Brainrender Napari
+    - id: brainrender-napari.make_brainrender_viewer_widget
+      python_name: brainrender_napari.brainrender_viewer_widget:BrainrenderViewerWidget
+      title: Make Brainrender Napari Viewer
+    - id: brainrender-napari.make_brainrender_manager_widget
+      python_name: brainrender_napari.brainrender_manager_widget:BrainrenderManagerWidget
+      title: Make Brainrender Napari Manager
   widgets:
-    - command: brainrender-napari.make_brainrender_widget
+    - command: brainrender-napari.make_brainrender_viewer_widget
       display_name: Brainrender
+    - command: brainrender-napari.make_brainrender_manager_widget
+      display_name: Manage atlas versions


Test patch:
diff --git a/tests/test_integration/test_brainrender_manager_widget.py b/tests/test_integration/test_brainrender_manager_widget.py
new file mode 100644
index 0000000..702e0dd
--- /dev/null
+++ b/tests/test_integration/test_brainrender_manager_widget.py
@@ -0,0 +1,32 @@
+"""These tests should just check that the subwidget signal and napari
+are connected as expected. Lower level tests should happen in the tests
+for the widget themselves."""
+
+import pytest
+
+from brainrender_napari.brainrender_manager_widget import (
+    BrainrenderManagerWidget,
+)
+
+
+@pytest.fixture
+def manager_widget(make_napari_viewer) -> BrainrenderManagerWidget:
+    """Fixture to expose the atlas viewer widget to tests.
+
+    Simultaneously acts as a smoke test that the widget
+    can be instantiated without crashing."""
+    viewer = make_napari_viewer()
+    return BrainrenderManagerWidget(viewer)
+
+
+def test_atlas_manager_view_tooltip(manager_widget):
+    for expected_keyword in [
+        "double-click",
+        "download/update",
+        "row",
+        "atlas",
+    ]:
+        assert (
+            expected_keyword
+            in manager_widget.atlas_manager_group.toolTip().lower()
+        )
diff --git a/tests/test_integration/test_brainrender_widget.py b/tests/test_integration/test_brainrender_viewer_widget.py
similarity index 64%
rename from tests/test_integration/test_brainrender_widget.py
rename to tests/test_integration/test_brainrender_viewer_widget.py
index e0a8d2c..bd9b248 100644
--- a/tests/test_integration/test_brainrender_widget.py
+++ b/tests/test_integration/test_brainrender_viewer_widget.py
@@ -4,17 +4,19 @@
 
 import pytest
 
-from brainrender_napari.brainrender_widget import BrainrenderWidget
+from brainrender_napari.brainrender_viewer_widget import (
+    BrainrenderViewerWidget,
+)
 
 
 @pytest.fixture
-def brainrender_widget(make_napari_viewer) -> BrainrenderWidget:
+def viewer_widget(make_napari_viewer) -> BrainrenderViewerWidget:
     """Fixture to expose the atlas viewer widget to tests.
 
     Simultaneously acts as a smoke test that the widget
     can be instantiated without crashing."""
     viewer = make_napari_viewer()
-    return BrainrenderWidget(viewer)
+    return BrainrenderViewerWidget(viewer)
 
 
 @pytest.mark.parametrize(
@@ -25,12 +27,12 @@ def brainrender_widget(make_napari_viewer) -> BrainrenderWidget:
     ],
 )
 def test_checkbox_visibility(
-    brainrender_widget, mocker, expected_visibility, atlas
+    viewer_widget, mocker, expected_visibility, atlas
 ):
     checkbox_visibility_mock = mocker.patch(
-        "brainrender_napari.brainrender_widget.QCheckBox.setVisible"
+        "brainrender_napari.brainrender_viewer_widget.QCheckBox.setVisible"
     )
-    brainrender_widget._on_atlas_selection_changed(atlas)
+    viewer_widget._on_atlas_selection_changed(atlas)
     checkbox_visibility_mock.assert_called_once_with(expected_visibility)
 
 
@@ -43,58 +45,54 @@ def test_checkbox_visibility(
     ],
 )
 def test_double_click_on_locally_available_atlas_row(
-    brainrender_widget, mocker, qtbot, expected_atlas_name
+    viewer_widget, mocker, qtbot, expected_atlas_name
 ):
     """Check for a few local low-res atlases that double-clicking them
     on the atlas viewer view calls the expected atlas representation function.
     """
     add_atlas_to_viewer_mock = mocker.patch(
-        "brainrender_napari.brainrender_widget"
+        "brainrender_napari.brainrender_viewer_widget"
         ".NapariAtlasRepresentation.add_to_viewer"
     )
-    with qtbot.waitSignal(
-        brainrender_widget.atlas_viewer_view.add_atlas_requested
-    ):
-        brainrender_widget.atlas_viewer_view.add_atlas_requested.emit(
+    with qtbot.waitSignal(viewer_widget.atlas_viewer_view.add_atlas_requested):
+        viewer_widget.atlas_viewer_view.add_atlas_requested.emit(
             expected_atlas_name
         )
     add_atlas_to_viewer_mock.assert_called_once()
 
 
-def test_structure_row_double_clicked(brainrender_widget, mocker):
+def test_structure_row_double_clicked(viewer_widget, mocker):
     """Checks that when the structure view widgets emit "VS" and
     the allen_mouse_100um atlas is selected, the NapariAtlasRepresentation
     function is called in the expected way.
     """
     add_structure_to_viewer_mock = mocker.patch(
-        "brainrender_napari.brainrender_widget"
+        "brainrender_napari.brainrender_viewer_widget"
         ".NapariAtlasRepresentation.add_structure_to_viewer"
     )
-    brainrender_widget.atlas_viewer_view.selectRow(
+    viewer_widget.atlas_viewer_view.selectRow(
         4
     )  # allen_mouse_100um is in row 4
 
-    brainrender_widget.structure_view.add_structure_requested.emit("VS")
+    viewer_widget.structure_view.add_structure_requested.emit("VS")
     add_structure_to_viewer_mock.assert_called_once_with("VS")
 
 
-def test_add_additional_reference_selected(brainrender_widget, mocker):
+def test_add_additional_reference_selected(viewer_widget, mocker):
     """Checks that when the atlas viewer view requests an additional
     reference, the NapariAtlasRepresentation function is called in
     the expected way."""
     add_additional_reference_mock = mocker.patch(
-        "brainrender_napari.brainrender_widget"
+        "brainrender_napari.brainrender_viewer_widget"
         ".NapariAtlasRepresentation.add_additional_reference"
     )
-    brainrender_widget.atlas_viewer_view.selectRow(
-        5
-    )  # mpin_zfish_1um is in row 5
+    viewer_widget.atlas_viewer_view.selectRow(5)  # mpin_zfish_1um is in row 5
     assert (
-        brainrender_widget.atlas_viewer_view.selected_atlas_name()
+        viewer_widget.atlas_viewer_view.selected_atlas_name()
         == "mpin_zfish_1um"
     )
     additional_reference_name = "GAD1b"
-    brainrender_widget.atlas_viewer_view.additional_reference_requested.emit(
+    viewer_widget.atlas_viewer_view.additional_reference_requested.emit(
         additional_reference_name
     )
     add_additional_reference_mock.assert_called_once_with(
@@ -102,23 +100,23 @@ def test_add_additional_reference_selected(brainrender_widget, mocker):
     )
 
 
-def test_show_structures_checkbox(brainrender_widget, mocker):
+def test_show_structures_checkbox(viewer_widget, mocker):
     structure_view_refresh_mock = mocker.patch(
-        "brainrender_napari.brainrender_widget.StructureView.refresh"
+        "brainrender_napari.brainrender_viewer_widget.StructureView.refresh"
     )
-    brainrender_widget.atlas_viewer_view.selectRow(
+    viewer_widget.atlas_viewer_view.selectRow(
         0
     )  # example_mouse_100um is in row 0
     structure_view_refresh_mock.assert_called_with(
         "example_mouse_100um", False
     )
 
-    brainrender_widget.show_structure_names.click()
+    viewer_widget.show_structure_names.click()
     assert structure_view_refresh_mock.call_count == 2
     structure_view_refresh_mock.assert_called_with("example_mouse_100um", True)
 
 
-def test_structure_view_tooltip(brainrender_widget):
+def test_structure_view_tooltip(viewer_widget):
     for expected_keyword in [
         "double-click",
         "atlas region",
@@ -131,11 +129,11 @@ def test_structure_view_tooltip(brainrender_widget):
     ]:
         assert (
             expected_keyword
-            in brainrender_widget.structure_tree_group.toolTip().lower()
+            in viewer_widget.structure_tree_group.toolTip().lower()
         )
 
 
-def test_atlas_viewer_view_tooltip(brainrender_widget):
+def test_atlas_viewer_view_tooltip(viewer_widget):
     for expected_keyword in [
         "double-click",
         "add",
@@ -146,5 +144,5 @@ def test_atlas_viewer_view_tooltip(brainrender_widget):
     ]:
         assert (
             expected_keyword
-            in brainrender_widget.atlas_viewer_group.toolTip().lower()
+            in viewer_widget.atlas_viewer_group.toolTip().lower()
         )