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

move models to own files/simplify view #123

Merged
merged 6 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
12 changes: 2 additions & 10 deletions brainrender_napari/brainrender_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
NapariAtlasRepresentation,
)
from brainrender_napari.utils.brainglobe_logo import header_widget
from brainrender_napari.widgets.atlas_table_view import AtlasTableView
from brainrender_napari.widgets.atlas_viewer_view import AtlasViewerView
from brainrender_napari.widgets.structure_view import StructureView


Expand All @@ -43,7 +43,7 @@ def __init__(self, napari_viewer: Viewer):
self.layout().addWidget(header_widget())

# create widgets
self.atlas_table_view = AtlasTableView(parent=self)
self.atlas_table_view = AtlasViewerView(parent=self)

self.show_structure_names = QCheckBox()
self.show_structure_names.setChecked(False)
Expand Down Expand Up @@ -79,9 +79,6 @@ def __init__(self, napari_viewer: Viewer):
self.layout().addWidget(self.structure_tree_group)

# connect atlas view widget signals
self.atlas_table_view.download_atlas_confirmed.connect(
self._on_download_atlas_confirmed
)
self.atlas_table_view.add_atlas_requested.connect(
self._on_add_atlas_requested
)
Expand All @@ -102,11 +99,6 @@ def __init__(self, napari_viewer: Viewer):
self._on_add_structure_requested
)

def _on_download_atlas_confirmed(self, atlas_name):
"""Ensure structure view is displayed if new atlas downloaded."""
show_structure_names = self.show_structure_names.isChecked()
self.structure_view.refresh(atlas_name, show_structure_names)

def _on_add_structure_requested(self, structure_name: str):
"""Add given structure as napari atlas representation"""
selected_atlas = BrainGlobeAtlas(
Expand Down
96 changes: 96 additions & 0 deletions brainrender_napari/data_models/atlas_table_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from bg_atlasapi.list_atlases import (
get_all_atlases_lastversions,
get_atlases_lastversions,
get_downloaded_atlases,
get_local_atlas_version,
)
from qtpy.QtCore import QAbstractTableModel, QModelIndex, Qt

from brainrender_napari.utils.load_user_data import (
read_atlas_metadata_from_file,
)


class AtlasTableModel(QAbstractTableModel):
"""A table data model for atlases."""

def __init__(self):
super().__init__()
self.refresh_data()

def refresh_data(self) -> None:
"""Refresh model data by calling atlas API"""
all_atlases = get_all_atlases_lastversions()
data = []
for name, latest_version in all_atlases.items():
if name in get_atlases_lastversions().keys():
data.append(
[
name,
self._format_name(name),
get_local_atlas_version(name),
latest_version,
]
)
else:
data.append(
[name, self._format_name(name), "n/a", latest_version]
)
Comment on lines +31 to +44
Copy link
Member

Choose a reason for hiding this comment

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

What does 'get_local_atlas_version' return if the atlas isn't downloaded? If it's something sensible can that be used instead of querying 'get_atlases_lastversions()'?

Suggested change
for name, latest_version in all_atlases.items():
if name in get_atlases_lastversions().keys():
data.append(
[
name,
self._format_name(name),
get_local_atlas_version(name),
latest_version,
]
)
else:
data.append(
[name, self._format_name(name), "n/a", latest_version]
)
for name, latest_version in all_atlases.items():
local_version = get_local_atlas_version(name) if get_local_atlas_version else "n/a"
data.append(
[
name,
self._format_name(name),
local_version,
latest_version,
]
)

Copy link
Member Author

Choose a reason for hiding this comment

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

Don't think it currently returns something sensible unfortunately - I've tested locally and it gives a list index out of range error. I've opened brainglobe/brainglobe-atlasapi#187 as a possibility for the future.


self._data = data

def _format_name(self, name: str) -> str:
formatted_name = name.split("_")
formatted_name[0] = formatted_name[0].capitalize()
formatted_name[-1] = f"({formatted_name[-1].split('um')[0]} \u03BCm)"
return " ".join([formatted for formatted in formatted_name])
Comment on lines +27 to +52
Copy link
Member Author

Choose a reason for hiding this comment

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

These are the new functions added to the atlas table model.


def data(self, index: QModelIndex, role=Qt.DisplayRole):
if role == Qt.DisplayRole:
return self._data[index.row()][index.column()]
if role == Qt.ToolTipRole:
hovered_atlas_name = self._data[index.row()][0]
return AtlasTableModel._get_tooltip_text(hovered_atlas_name)

def rowCount(self, index: QModelIndex = QModelIndex()):
return len(self._data)

def columnCount(self, index: QModelIndex = QModelIndex()):
return len(self._data[0])

def headerData(
self, section: int, orientation: Qt.Orientation, role: Qt.ItemDataRole
):
"""Customises the horizontal header data of model,
and raises an error if an unexpected column is found."""
if role == Qt.DisplayRole and orientation == Qt.Orientation.Horizontal:
if section == 0:
return "Raw name"
elif section == 1:
return "Atlas"
elif section == 2:
return "Local version"
elif section == 3:
return "Latest version"
else:
raise ValueError("Unexpected horizontal header value.")
else:
return super().headerData(section, orientation, role)

@classmethod
def _get_tooltip_text(cls, atlas_name: str):
"""Returns the atlas metadata as a formatted string,
as well as instructions on how to interact with the atlas."""
if atlas_name in get_downloaded_atlases():
metadata = read_atlas_metadata_from_file(atlas_name)
metadata_as_string = ""
for key, value in metadata.items():
metadata_as_string += f"{key}:\t{value}\n"

tooltip_text = f"{atlas_name} (double-click to add to viewer)\
\n{metadata_as_string}"
elif atlas_name in get_all_atlases_lastversions().keys():
tooltip_text = f"{atlas_name} (double-click to download)"
else:
raise ValueError("Tooltip text called with invalid atlas name.")
return tooltip_text
144 changes: 144 additions & 0 deletions brainrender_napari/data_models/structure_tree_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
from typing import Dict, List

from bg_atlasapi.structure_tree_util import get_structures_tree
from qtpy.QtCore import QAbstractItemModel, QModelIndex, Qt
from qtpy.QtGui import QStandardItem


class StructureTreeItem(QStandardItem):
"""A class to hold items in a tree model."""

def __init__(self, data, parent=None):
self.parent_item = parent
self.item_data = data
self.child_items = []

def appendChild(self, item):
self.child_items.append(item)

def child(self, row):
return self.child_items[row]

def childCount(self):
return len(self.child_items)

def columnCount(self):
return len(self.item_data)

def data(self, column):
try:
return self.item_data[column]
except IndexError:
return None

def parent(self):
return self.parent_item

def row(self):
if self.parent_item:
return self.parent_item.child_items.index(self)
return 0


class StructureTreeModel(QAbstractItemModel):
"""Implementation of a read-only QAbstractItemModel to hold
the structure tree information provided by the Atlas API in a Qt Model"""

def __init__(self, data: List, parent=None):
super().__init__()
self.root_item = StructureTreeItem(data=("acronym", "name", "id"))
self.build_structure_tree(data, self.root_item)

def build_structure_tree(self, structures: List, root: StructureTreeItem):
"""Build the structure tree given a list of structures."""
tree = get_structures_tree(structures)
structure_id_dict = {}
for structure in structures:
structure_id_dict[structure["id"]] = structure

inserted_items: Dict[int, StructureTreeItem] = {}
for n_id in tree.expand_tree(): # sorts nodes by default,
# so parents will always be already in the QAbstractItemModel
# before their children
node = tree.get_node(n_id)
acronym = structure_id_dict[node.identifier]["acronym"]
name = structure_id_dict[node.identifier]["name"]
if (
len(structure_id_dict[node.identifier]["structure_id_path"])
== 1
):
parent_item = root
else:
parent_id = tree.parent(node.identifier).identifier
parent_item = inserted_items[parent_id]

item = StructureTreeItem(
data=(acronym, name, node.identifier), parent=parent_item
)
parent_item.appendChild(item)
inserted_items[node.identifier] = item

def data(self, index: QModelIndex, role=Qt.DisplayRole):
"""Provides read-only data for a given index if
intended for display, otherwise None."""
if not index.isValid():
return None

if role != Qt.DisplayRole:
return None

item = index.internalPointer()

return item.data(index.column())

def rowCount(self, parent: StructureTreeItem):
"""Returns the number of rows(i.e. children) of an item"""
if parent.column() > 0:
return 0

if not parent.isValid():
parent_item = self.root_item
else:
parent_item = parent.internalPointer()

return parent_item.childCount()

def columnCount(self, parent: StructureTreeItem):
"""The number of columns of an item."""
if parent.isValid():
return parent.internalPointer().columnCount()
else:
return self.root_item.columnCount()

def parent(self, index: QModelIndex):
"""The first-column index of parent of the item
at a given index. Returns an empty index if the root,
or an invalid index, is passed.
"""
if not index.isValid():
return QModelIndex()

child_item = index.internalPointer()
parent_item = child_item.parent()

if parent_item == self.root_item:
return QModelIndex()

return self.createIndex(parent_item.row(), 0, parent_item)

def index(self, row, column, parent=QModelIndex()):
"""The index of the item at (row, column) with a given parent.
By default, the given parent is assumed to be the root."""
if not self.hasIndex(row, column, parent):
return QModelIndex()

if not parent.isValid():
parent_item = self.root_item
else:
parent_item = parent.internalPointer()

child_item = parent_item.child(row)
if child_item:
return self.createIndex(row, column, child_item)
else:
return QModelIndex()
Loading