Skip to content
Draft
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
19 changes: 9 additions & 10 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[metadata]
name = napari-tiled
name = napari_tiled
version = 0.0.1
description = A Tiled browser plugin for napari
long_description = file: README.md
Expand All @@ -8,6 +8,7 @@ url = https://github.com/AbbyGi/napari-tiled
author = Abby Giles
author_email = agiles@bnl.gov
license = BSD-3-Clause
license_file = LICENSE
license_files = LICENSE
classifiers =
Development Status :: 2 - Pre-Alpha
Expand All @@ -31,17 +32,16 @@ project_urls =
[options]
packages = find:
install_requires =
numpy
magicgui
napari
numpy
qtpy

tiled
python_requires = >=3.8
include_package_data = True
package_dir =
=src

# add your package requirements here

[options.packages.find]
where = src

Expand All @@ -51,13 +51,12 @@ napari.manifest =

[options.extras_require]
testing =
tox
pytest # https://docs.pytest.org/en/latest/contents.html
pytest-cov # https://pytest-cov.readthedocs.io/en/latest/
pytest-qt # https://pytest-qt.readthedocs.io/en/latest/
napari
pyqt5

pytest
pytest-cov
pytest-qt
tox

[options.package_data]
* = *.yaml
90 changes: 90 additions & 0 deletions src/napari_tiled_browser/models/search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
from napari.utils.events import EmitterGroup, Event
from tiled.client import from_uri

DEFAULT_PAGE_LIMIT = 20


class ResultsPage:
def __init__(self, *, uri=None):
self._uri = None
self._client = None
self._total_length = 0
self._page_number = 0
self._page_limit = DEFAULT_PAGE_LIMIT
self.queries = []
self.results = []
self.events = EmitterGroup(
source=self,
auto_connect=True,
page_number=Event,
page_limit=Event,
refreshed=Event,
connected=Event,
uri=Event,
)
self.events.page_number.connect(self.refresh)
self.events.page_limit.connect(self.refresh)
self.events.connected.connect(self.refresh)
self.uri = uri

@property
def page_number(self):
return self._page_number

@page_number.setter
def page_number(self, value):
self._page_number = value
self.events.page_number(value=value)

@property
def page_limit(self):
return self._page_limit

@page_limit.setter
def page_limit(self, value):
self._page_limit = value
self.events.page_limit(value=value)

@property
def range(self):
return (
self.page_number * self.page_limit,
min((1 + self.page_number) * self.page_limit, self._total_length),
)

@property
def total_length(self):
return self._total_length

@property
def uri(self):
return self._uri

@uri.setter
def uri(self, value):
if value == self._uri:
return
self._uri = value
self.events.uri(value=value)
if value == "":
self._client = None
else:
self._client = from_uri(value)
self.events.connected()

def refresh(self, event):
if self._client is None:
return []
results = self._client
for query in self.queries:
results = results.search(query)
self._total_length = len(results)
self.results.clear()
self.results.extend(
results.keys()[
(self.page_number * self.page_limit) : ( # noqa E203
(1 + self.page_number) * self.page_limit
)
]
)
self.events.refreshed()
11 changes: 8 additions & 3 deletions src/napari_tiled_browser/napari.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ name: napari-tiled
display_name: Tiled Browser
contributions:
commands:
# - id: napari-tiled.make_qwidget
# python_name: napari_tiled_browser.tiled_widget:TiledBrowser
# title: Tiled Browser
- id: napari-tiled.make_qwidget
python_name: napari_tiled_browser.tiled_widget:TiledBrowser
title: Tiled Browser
python_name: napari_tiled_browser.tiled_widget2:TiledBrowser2
title: Tiled Browser 2
widgets:
# - command: napari-tiled.make_qwidget
# display_name: Tiled Browser
- command: napari-tiled.make_qwidget
display_name: Tiled Browser
display_name: Tiled Browser 2
166 changes: 166 additions & 0 deletions src/napari_tiled_browser/qt/search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
from math import floor

from qtpy.QtCore import Qt, Signal
from qtpy.QtWidgets import (
QComboBox,
QHBoxLayout,
QLabel,
QLineEdit,
QPushButton,
QSplitter,
QTableWidget,
QTableWidgetItem,
QVBoxLayout,
QWidget,
)


class SearchResults(QWidget):
# your QWidget.__init__ can optionally request the napari viewer instance
# in one of two ways:
# 1. use a parameter called `napari_viewer`, as done here
# 2. use a type annotation of 'napari.viewer.Viewer' for any parameter
def __init__(self, model):
super().__init__()
self.model = model

# Connection elements
self.url_entry = QLineEdit()
self.url_entry.setPlaceholderText("Enter a url")
self.connect_button = QPushButton("Connect")
self.connection_label = QLabel("No url connected")
self.connection_widget = QWidget()

# Connection layout
connection_layout = QVBoxLayout()
connection_layout.addWidget(self.url_entry)
connection_layout.addWidget(self.connect_button)
connection_layout.addWidget(self.connection_label)
connection_layout.addStretch()
self.connection_widget.setLayout(connection_layout)

# Navigation elements
self.rows_per_page_label = QLabel("Rows per page: ")
self.rows_per_page_selector = QComboBox()
# TODO: use model._page_limit as default option here?
self.rows_per_page_selector.addItems(["10", "20", "50"])
self.rows_per_page_selector.setCurrentIndex(1)

self.current_location_label = QLabel()
self.previous_page = ClickableQLabel("<")
self.next_page = ClickableQLabel(">")
self.navigation_widget = QWidget()

self._rows_per_page = int(self.rows_per_page_selector.currentText())

# Navigation layout
navigation_layout = QHBoxLayout()
navigation_layout.addWidget(self.rows_per_page_label)
navigation_layout.addWidget(self.rows_per_page_selector)
navigation_layout.addWidget(self.current_location_label)
navigation_layout.addWidget(self.previous_page)
navigation_layout.addWidget(self.next_page)
self.navigation_widget.setLayout(navigation_layout)

# Catalog table elements
self.catalog_table = QTableWidget(0, 1)
self.catalog_table.setHorizontalHeaderLabels(["ID"])
self._create_table_rows()
self.catalog_table_widget = QWidget()

# Catalog table layout
catalog_table_layout = QVBoxLayout()
catalog_table_layout.addWidget(self.catalog_table)
catalog_table_layout.addWidget(self.navigation_widget)
self.catalog_table_widget.setLayout(catalog_table_layout)
self.catalog_table_widget.setVisible(False)

self.splitter = QSplitter(self)
self.splitter.setOrientation(Qt.Orientation.Vertical)

self.splitter.addWidget(self.connection_widget)
self.splitter.addWidget(self.catalog_table_widget)

self.splitter.setStretchFactor(1, 2)

layout = QVBoxLayout()
layout.addWidget(self.splitter)
self.setLayout(layout)

self.connect_button.clicked.connect(self._on_connect_clicked)
self.previous_page.clicked.connect(self._on_prev_page_clicked)
self.next_page.clicked.connect(self._on_next_page_clicked)

self.rows_per_page_selector.currentTextChanged.connect(
self._on_rows_per_page_changed
)

self.model.events.connected.connect(self._on_connected)
self.model.events.refreshed.connect(self._on_refreshed)

if self.model.uri != "":
self.url_entry.setText(self.model.uri)
self.connection_label.setText(f"Connected to {self.model.uri}")
# Create the table if we already have a uri
self._set_current_location_label()
self._create_table_rows()
self._populate_table()
self.catalog_table_widget.setVisible(True)

def _on_connect_clicked(self):
url = self.url_entry.displayText()
self.model.uri = url
self.connection_label.setText(f"Connected to {url}")

def _on_connected(self, event):
self.catalog_table_widget.setVisible(True)

def _on_refreshed(self, event):
self._set_current_location_label()
self._create_table_rows()
self._populate_table()

def _on_rows_per_page_changed(self, value):
lower_bound, _ = self.model.range
self.model.page_limit = int(value)
self.model.page_number = min(
self.model.page_number,
floor(self.model.total_length / self.model.page_limit),
)

def _create_table_rows(self):
target_length = len(self.model.results)
while self.catalog_table.rowCount() > target_length:
self.catalog_table.removeRow(0)
while self.catalog_table.rowCount() < target_length:
last_row_position = self.catalog_table.rowCount()
self.catalog_table.insertRow(last_row_position)

def _populate_table(self):
for row_index, node in enumerate(self.model.results):
self.catalog_table.setItem(row_index, 0, QTableWidgetItem(node))

def _on_prev_page_clicked(self):
if self.model.page_number != 0:
self.model.page_number -= 1

def _on_next_page_clicked(self):
if (
self.model.page_limit * (self.model.page_number + 1)
< self.model.total_length
):
self.model.page_number += 1

def _set_current_location_label(self):
start, end = self.model.range
current_location_text = (
f"{1 + start}-{end} of {self.model.total_length}"
)
self.current_location_label.setText(current_location_text)


class ClickableQLabel(QLabel):
clicked = Signal()

def mousePressEvent(self, event):
self.clicked.emit()
28 changes: 28 additions & 0 deletions src/napari_tiled_browser/tiled_widget2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""
This module is an example of a barebones QWidget plugin for napari

It implements the Widget specification.
see: https://napari.org/plugins/guides.html?#widgets

Replace code below according to your needs.
"""

from qtpy.QtWidgets import QVBoxLayout, QWidget

from .models.search import ResultsPage
from .qt.search import SearchResults


class TiledBrowser2(QWidget):
# your QWidget.__init__ can optionally request the napari viewer instance
# in one of two ways:
# 1. use a parameter called `napari_viewer`, as done here
# 2. use a type annotation of 'napari.viewer.Viewer' for any parameter
def __init__(self, napari_viewer):
super().__init__()
self.viewer = napari_viewer
self.search = ResultsPage(uri=None)
widget = SearchResults(self.search)
layout = QVBoxLayout()
layout.addWidget(widget)
self.setLayout(layout)