From ce5f9cc39b6921e53496dfd8d9457fbc8e5a85cb Mon Sep 17 00:00:00 2001 From: Josh Lohmolder Date: Tue, 4 Jun 2024 15:45:32 -0400 Subject: [PATCH 01/10] Created tiled browser for silx --- examples/tiled_browser.py | 415 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 415 insertions(+) create mode 100644 examples/tiled_browser.py diff --git a/examples/tiled_browser.py b/examples/tiled_browser.py new file mode 100644 index 0000000000..f7840328e8 --- /dev/null +++ b/examples/tiled_browser.py @@ -0,0 +1,415 @@ +""" +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. +""" +import collections +from datetime import date, datetime +import functools +import json + +from qtpy.QtCore import Qt, Signal +from qtpy.QtGui import QIcon, QPixmap +from qtpy.QtWidgets import ( + QAbstractItemView, + QComboBox, + QHBoxLayout, + QLabel, + QLineEdit, + QPushButton, + QSplitter, + QStyle, + QTableWidget, + QTableWidgetItem, + QTextEdit, + QVBoxLayout, + QWidget, +) +from tiled.client import from_uri +from tiled.client.array import DaskArrayClient +from tiled.client.container import Container +from tiled.structures.core import StructureFamily + +from silx.gui import qt +from silx.gui.plot import PlotWidget + +def json_decode(obj): + if isinstance(obj, (datetime, date)): + return obj.isoformat() + return str(obj) + + +class DummyClient: + "Placeholder for a structure family we cannot (yet) handle" + + def __init__(self, *args, item, **kwargs): + self.item = item + + +STRUCTURE_CLIENTS = collections.defaultdict(lambda: DummyClient) +STRUCTURE_CLIENTS.update({"array": DaskArrayClient, "container": Container}) + + +class TiledBrowser(qt.QMainWindow): + NODE_ID_MAXLEN = 8 + SUPPORTED_TYPES = (StructureFamily.array, StructureFamily.container) + + # 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, parent=None): + super().__init__() + + self.set_root(None) + + # Create a PlotWidget + self._plot = PlotWidget(parent=self) + + # 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() + self.rows_per_page_selector.addItems(["5", "10", "25"]) + self.rows_per_page_selector.setCurrentIndex(0) + + 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) + + # Current path layout + self.current_path_label = QLabel() + self._rebuild_current_path_label() + + # Catalog table elements + self.catalog_table = QTableWidget(0, 1) + self.catalog_table.horizontalHeader().setStretchLastSection(True) + self.catalog_table.setEditTriggers( + QTableWidget.EditTrigger.NoEditTriggers + ) # disable editing + self.catalog_table.horizontalHeader().hide() # remove header + self.catalog_table.setSelectionMode( + QAbstractItemView.SelectionMode.SingleSelection + ) # disable multi-select + # disabled due to bad colour palette: + # self.catalog_table.setAlternatingRowColors(True) + self.catalog_table.itemDoubleClicked.connect( + self._on_item_double_click + ) + self.catalog_table.itemSelectionChanged.connect(self._on_item_selected) + self.catalog_table_widget = QWidget() + self.catalog_breadcrumbs = None + + # Info layout + self.info_box = QTextEdit() + self.info_box.setReadOnly(True) + self.load_button = QPushButton("Open") + self.load_button.setEnabled(False) + self.load_button.clicked.connect(self._on_load) + catalog_info_layout = QHBoxLayout() + catalog_info_layout.addWidget(self.catalog_table) + load_layout = QVBoxLayout() + load_layout.addWidget(self.info_box) + load_layout.addWidget(self.load_button) + catalog_info_layout.addLayout(load_layout) + + # Catalog table layout + catalog_table_layout = QVBoxLayout() + catalog_table_layout.addWidget(self.current_path_label) + catalog_table_layout.addLayout(catalog_info_layout) + catalog_table_layout.addWidget(self.navigation_widget) + catalog_table_layout.addStretch(1) + 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) + + browser_layout = QVBoxLayout() + browser_layout.addWidget(self.splitter) + + layout = QHBoxLayout() + layout.addWidget(self._plot) + layout.addLayout( browser_layout ) + + centralWidget = qt.QWidget(self) + centralWidget.setLayout(layout) + self.setCentralWidget(centralWidget) + + 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 + ) + + def _on_connect_clicked(self): + url = self.url_entry.displayText().strip() + # url = "https://tiled-demo.blueskyproject.io/api" + if not url: + print("Please specify a url.") + return + try: + root = from_uri(url, STRUCTURE_CLIENTS) + if isinstance(root, DummyClient): + print("Unsupported tiled type detected") + except Exception: + print("Could not connect. Please check the url.") + else: + self.connection_label.setText(f"Connected to {url}") + self.set_root(root) + + def set_root(self, root): + self.root = root + self.node_path = () + self._current_page = 0 + if root is not None: + self.catalog_table_widget.setVisible(True) + self._rebuild() + + def get_current_node(self): + return self.get_node(self.node_path) + + @functools.lru_cache(maxsize=1) + def get_node(self, node_path): + if node_path: + return self.root[node_path] + return self.root + + def enter_node(self, node_id): + self.node_path += (node_id,) + self._current_page = 0 + self._rebuild() + + def exit_node(self): + self.node_path = self.node_path[:-1] + self._current_page = 0 + self._rebuild() + + def open_node(self, node_id): + node = self.get_current_node()[node_id] + family = node.item["attributes"]["structure_family"] + if isinstance(node, DummyClient): + print(f"Cannot open type: '{family}'") + return + if family == StructureFamily.array: + plot = self.getPlotWidget() + plot.clear() + plot.getDefaultColormap().setName("viridis") + plot.addImage(node) + plot.resetZoom() + elif family == StructureFamily.container: + self.enter_node(node_id) + else: + print(f"Type not supported:'{family}") + + def _on_load(self): + selected = self.catalog_table.selectedItems() + if not selected: + return + item = selected[0] + if item is self.catalog_breadcrumbs: + return + self.open_node(item.text()) + + def _on_rows_per_page_changed(self, value): + self._rows_per_page = int(value) + self._current_page = 0 + self._rebuild_table() + self._set_current_location_label() + + def _on_item_double_click(self, item): + if item is self.catalog_breadcrumbs: + self.exit_node() + return + self.open_node(item.text()) + + def _on_item_selected(self): + selected = self.catalog_table.selectedItems() + if not selected or (item := selected[0]) is self.catalog_breadcrumbs: + self._clear_metadata() + return + + name = item.text() + node_path = self.node_path + (name,) + node = self.get_node(node_path) + + attrs = node.item["attributes"] + family = attrs["structure_family"] + metadata = json.dumps(attrs["metadata"], indent=2, default=json_decode) + + info = f"type: {family}
" + if family == StructureFamily.array: + shape = attrs["structure"]["shape"] + info += f"shape: {tuple(shape)}
" + info += f"metadata: {metadata}" + self.info_box.setText(info) + + if family in self.SUPPORTED_TYPES: + self.load_button.setEnabled(True) + else: + self.load_button.setEnabled(False) + + def _clear_metadata(self): + self.info_box.setText("") + self.load_button.setEnabled(False) + + def _rebuild_current_path_label(self): + path = ["root"] + for node_id in self.node_path: + if len(node_id) > self.NODE_ID_MAXLEN: + node_id = node_id[: self.NODE_ID_MAXLEN - 3] + "..." + path.append(node_id) + path.append("") + + self.current_path_label.setText(" / ".join(path)) + + def _rebuild_table(self): + prev_block = self.catalog_table.blockSignals(True) + # Remove all rows first + while self.catalog_table.rowCount() > 0: + self.catalog_table.removeRow(0) + + if self.node_path: + # add breadcrumbs + self.catalog_breadcrumbs = QTableWidgetItem("..") + self.catalog_table.insertRow(0) + self.catalog_table.setItem(0, 0, self.catalog_breadcrumbs) + + # Then add new rows + for row in range(self._rows_per_page): + last_row_position = self.catalog_table.rowCount() + self.catalog_table.insertRow(last_row_position) + node_offset = self._rows_per_page * self._current_page + # Fetch a page of keys. + items = self.get_current_node().items()[ + node_offset : node_offset + self._rows_per_page + ] + # Loop over rows, filling in keys until we run out of keys. + start = 1 if self.node_path else 0 + for row_index, (key, value) in zip( + range(start, self.catalog_table.rowCount()), items + ): + family = value.item["attributes"]["structure_family"] + if family == StructureFamily.container: + icon = self.style().standardIcon(QStyle.SP_DirHomeIcon) + elif family == StructureFamily.array: + icon = self.style().standardIcon( + QStyle.SP_FileIcon + ) + else: + icon = self.style().standardIcon( + QStyle.SP_TitleBarContextHelpButton + ) + self.catalog_table.setItem( + row_index, 0, QTableWidgetItem(icon, key) + ) + + # remove extra rows + for row in range(self._rows_per_page - len(items)): + self.catalog_table.removeRow(self.catalog_table.rowCount() - 1) + + headers = [ + str(x + 1) + for x in range( + node_offset, node_offset + self.catalog_table.rowCount() + ) + ] + if self.node_path: + headers = [""] + headers + + self.catalog_table.setVerticalHeaderLabels(headers) + self._clear_metadata() + self.catalog_table.blockSignals(prev_block) + + def _rebuild(self): + self._rebuild_table() + self._rebuild_current_path_label() + self._set_current_location_label() + + def _on_prev_page_clicked(self): + if self._current_page != 0: + self._current_page -= 1 + self._rebuild() + + def _on_next_page_clicked(self): + if ( + self._current_page * self._rows_per_page + ) + self._rows_per_page < len(self.get_current_node()): + self._current_page += 1 + self._rebuild() + + def _set_current_location_label(self): + starting_index = self._current_page * self._rows_per_page + 1 + ending_index = min( + self._rows_per_page * (self._current_page + 1), + len(self.get_current_node()), + ) + current_location_text = f"{starting_index}-{ending_index} of {len(self.get_current_node())}" + self.current_location_label.setText(current_location_text) + + def getPlotWidget(self): + """Returns the PlotWidget contains in this window""" + return self._plot + + def showImage(self): + plot = self.getPlotWidget() + plot.clear() + plot.getDefaultColormap().setName("viridis") + + +class ClickableQLabel(QLabel): + clicked = Signal() + + def mousePressEvent(self, event): + self.clicked.emit() + + +def main(): + app = qt.QApplication([]) + w = TiledBrowser() + w.show() + app.exec() + +if __name__ == "__main__": + main() + +# TODO: handle changing the location label/current_page when on last page and +# increasing rows per page From b8b65d78d26e49fb1da90dd6edaeb5210b9a9e7b Mon Sep 17 00:00:00 2001 From: Josh Lohmolder Date: Wed, 5 Jun 2024 10:08:09 -0400 Subject: [PATCH 02/10] Added 'first page' and 'last page' ClickeableQLabels --- examples/tiled_browser.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/examples/tiled_browser.py b/examples/tiled_browser.py index f7840328e8..6f6074ce10 100644 --- a/examples/tiled_browser.py +++ b/examples/tiled_browser.py @@ -93,6 +93,8 @@ def __init__(self, parent=None): self.current_location_label = QLabel() self.previous_page = ClickableQLabel("<") self.next_page = ClickableQLabel(">") + self.first_page = ClickableQLabel("<<") + self.last_page = ClickableQLabel(">>") self.navigation_widget = QWidget() self._rows_per_page = int( @@ -104,8 +106,10 @@ def __init__(self, parent=None): 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.first_page) navigation_layout.addWidget(self.previous_page) navigation_layout.addWidget(self.next_page) + navigation_layout.addWidget(self.last_page) self.navigation_widget.setLayout(navigation_layout) # Current path layout @@ -175,6 +179,8 @@ def __init__(self, parent=None): 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.first_page.clicked.connect(self._on_first_page_clicked) + self.last_page.clicked.connect(self._on_last_page_clicked) self.rows_per_page_selector.currentTextChanged.connect( self._on_rows_per_page_changed @@ -376,6 +382,15 @@ def _on_next_page_clicked(self): self._current_page += 1 self._rebuild() + def _on_first_page_clicked(self): + if self._current_page != 0: + self._current_page = 0 + self._rebuild() + + def _on_last_page_clicked(self): + print(self.node_path) + print(self.node_path[:-1]) + def _set_current_location_label(self): starting_index = self._current_page * self._rows_per_page + 1 ending_index = min( From 24cac460e781d72242daecf213e74dec57b21ddc Mon Sep 17 00:00:00 2001 From: Josh Lohmolder Date: Wed, 5 Jun 2024 11:33:00 -0400 Subject: [PATCH 03/10] Finished 'last page (>>)' ClickableQWidget --- examples/tiled_browser.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/examples/tiled_browser.py b/examples/tiled_browser.py index 6f6074ce10..45d9ddef03 100644 --- a/examples/tiled_browser.py +++ b/examples/tiled_browser.py @@ -112,9 +112,16 @@ def __init__(self, parent=None): navigation_layout.addWidget(self.last_page) self.navigation_widget.setLayout(navigation_layout) - # Current path layout + # Current path elements self.current_path_label = QLabel() + self.current_path0 = ClickableQLabel("root") + self.current_path_widget = QWidget() + + # Current path layout + current_path_layout = QHBoxLayout() + current_path_layout.addWidget(self.current_path_label) self._rebuild_current_path_label() + self.current_path_widget.setLayout(current_path_layout) # Catalog table elements self.catalog_table = QTableWidget(0, 1) @@ -150,7 +157,7 @@ def __init__(self, parent=None): # Catalog table layout catalog_table_layout = QVBoxLayout() - catalog_table_layout.addWidget(self.current_path_label) + catalog_table_layout.addWidget(self.current_path_widget) catalog_table_layout.addLayout(catalog_info_layout) catalog_table_layout.addWidget(self.navigation_widget) catalog_table_layout.addStretch(1) @@ -388,8 +395,14 @@ def _on_first_page_clicked(self): self._rebuild() def _on_last_page_clicked(self): - print(self.node_path) - print(self.node_path[:-1]) + while True: + if ( + self._current_page * self._rows_per_page + ) + self._rows_per_page < len(self.get_current_node()): + self._current_page += 1 + else: + self._rebuild() + break def _set_current_location_label(self): starting_index = self._current_page * self._rows_per_page + 1 From 78f565bacc82c9b3adef205ce6e2bbe375797ea9 Mon Sep 17 00:00:00 2001 From: Josh Lohmolder Date: Mon, 10 Jun 2024 08:59:43 -0400 Subject: [PATCH 04/10] Converted path_layout QLabel to seperate ClickableQLabels for each node. --- examples/tiled_browser.py | 57 ++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/examples/tiled_browser.py b/examples/tiled_browser.py index 45d9ddef03..aa0538f507 100644 --- a/examples/tiled_browser.py +++ b/examples/tiled_browser.py @@ -112,16 +112,11 @@ def __init__(self, parent=None): navigation_layout.addWidget(self.last_page) self.navigation_widget.setLayout(navigation_layout) - # Current path elements - self.current_path_label = QLabel() - self.current_path0 = ClickableQLabel("root") - self.current_path_widget = QWidget() - # Current path layout - current_path_layout = QHBoxLayout() - current_path_layout.addWidget(self.current_path_label) - self._rebuild_current_path_label() - self.current_path_widget.setLayout(current_path_layout) + self.current_path_layout = QHBoxLayout() + self.current_path_layout.setSpacing(10) + self.current_path_layout.setAlignment(Qt.AlignLeft) + self._rebuild_current_path_layout() # Catalog table elements self.catalog_table = QTableWidget(0, 1) @@ -157,7 +152,7 @@ def __init__(self, parent=None): # Catalog table layout catalog_table_layout = QVBoxLayout() - catalog_table_layout.addWidget(self.current_path_widget) + catalog_table_layout.addLayout( self.current_path_layout ) catalog_table_layout.addLayout(catalog_info_layout) catalog_table_layout.addWidget(self.navigation_widget) catalog_table_layout.addStretch(1) @@ -304,16 +299,40 @@ def _clear_metadata(self): self.info_box.setText("") self.load_button.setEnabled(False) - def _rebuild_current_path_label(self): - path = ["root"] + def _clear_current_path_layout(self): + for i in reversed(range(self.current_path_layout.count())): + widget = self.current_path_layout.itemAt(i).widget() + self.current_path_layout.removeWidget(widget) + widget.deleteLater() + + def _rebuild_current_path_layout(self): + # Add root to widget list. + root = ClickableQLabel("root") + widgets = [root] + + # Appropriately shorten node_id. for node_id in self.node_path: if len(node_id) > self.NODE_ID_MAXLEN: - node_id = node_id[: self.NODE_ID_MAXLEN - 3] + "..." - path.append(node_id) - path.append("") - - self.current_path_label.setText(" / ".join(path)) - + node_id = node_id[: self.NODE_ID_MAXLEN - 3] + "..." + + # Convert node_id into a ClickableQWidget and add to widget list. + clickable_label = ClickableQLabel(node_id) + clickable_label.clicked.connect(self._on_item_double_click) # TODO: Find correct argument for method + widgets.append(clickable_label) + + # Add nodes to node path. + if len(self.current_path_layout) < len(widgets): + for widget in widgets: + widget = widgets[-1] + self.current_path_layout.addWidget(widget) + + # Remove nodes from node path after they are left. + elif len(self.current_path_layout) > len(widgets): + self._clear_current_path_layout() + while len(self.current_path_layout) < len(widgets): + for widget in widgets: + self.current_path_layout.addWidget(widget) + def _rebuild_table(self): prev_block = self.catalog_table.blockSignals(True) # Remove all rows first @@ -374,7 +393,7 @@ def _rebuild_table(self): def _rebuild(self): self._rebuild_table() - self._rebuild_current_path_label() + self._rebuild_current_path_layout() self._set_current_location_label() def _on_prev_page_clicked(self): From d1cbbb88e8df938dba665a73491db144787b8890 Mon Sep 17 00:00:00 2001 From: Josh Lohmolder Date: Mon, 10 Jun 2024 15:49:14 -0400 Subject: [PATCH 05/10] The nodes converted to ClickableQLabels now transport user to the correct now and rebuild the table and layout appropriately. --- examples/tiled_browser.py | 41 +++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/examples/tiled_browser.py b/examples/tiled_browser.py index aa0538f507..f753ddb7b9 100644 --- a/examples/tiled_browser.py +++ b/examples/tiled_browser.py @@ -299,6 +299,29 @@ def _clear_metadata(self): self.info_box.setText("") self.load_button.setEnabled(False) + def _on_breadcrumb_clicked(self, node): + # If root is selected. + if node == "root": + self.node_path = () + self._rebuild() + + # For any node other than root. + else: + try: + index = self.node_path.index(node) + self. node_path = self.node_path[:index + 1] + self._rebuild() + + # If node ID has been truncated. + except ValueError: + for i, node_id in enumerate(self.node_path): + if node == node_id[:self.NODE_ID_MAXLEN - 3] + "...": + index = i + break + + self.node_path = self.node_path[:index + 1] + self._rebuild() + def _clear_current_path_layout(self): for i in reversed(range(self.current_path_layout.count())): widget = self.current_path_layout.itemAt(i).widget() @@ -307,7 +330,8 @@ def _clear_current_path_layout(self): def _rebuild_current_path_layout(self): # Add root to widget list. - root = ClickableQLabel("root") + root = ClickableQLabelWithText("root") + root.clicked_with_text.connect(self._on_breadcrumb_clicked) widgets = [root] # Appropriately shorten node_id. @@ -316,8 +340,8 @@ def _rebuild_current_path_layout(self): node_id = node_id[: self.NODE_ID_MAXLEN - 3] + "..." # Convert node_id into a ClickableQWidget and add to widget list. - clickable_label = ClickableQLabel(node_id) - clickable_label.clicked.connect(self._on_item_double_click) # TODO: Find correct argument for method + clickable_label = ClickableQLabelWithText(node_id) + clickable_label.clicked_with_text.connect(self._on_breadcrumb_clicked) # TODO: Find correct argument for method widgets.append(clickable_label) # Add nodes to node path. @@ -332,7 +356,7 @@ def _rebuild_current_path_layout(self): while len(self.current_path_layout) < len(widgets): for widget in widgets: self.current_path_layout.addWidget(widget) - + def _rebuild_table(self): prev_block = self.catalog_table.blockSignals(True) # Remove all rows first @@ -448,6 +472,15 @@ class ClickableQLabel(QLabel): def mousePressEvent(self, event): self.clicked.emit() +class ClickableQLabelWithText(ClickableQLabel): + # Define a new signal that emits the text of the label when clicked + clicked_with_text = Signal(str) + + def mousePressEvent(self, event): + # Emit the oringal clicked signal + super().mousePressEvent(event) + self.clicked_with_text.emit(self.text()) + def main(): app = qt.QApplication([]) From d3395fda4a9e3aa84b7890e6df4d6326992176a9 Mon Sep 17 00:00:00 2001 From: Josh Lohmolder Date: Tue, 11 Jun 2024 08:45:33 -0400 Subject: [PATCH 06/10] Combined ClickableQLableWithText subclass into ClickableQLabel class, so there is only one class. --- examples/tiled_browser.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/examples/tiled_browser.py b/examples/tiled_browser.py index f753ddb7b9..6c9dfb2c16 100644 --- a/examples/tiled_browser.py +++ b/examples/tiled_browser.py @@ -91,9 +91,9 @@ def __init__(self, parent=None): self.rows_per_page_selector.setCurrentIndex(0) self.current_location_label = QLabel() + self.first_page = ClickableQLabel("<<") self.previous_page = ClickableQLabel("<") self.next_page = ClickableQLabel(">") - self.first_page = ClickableQLabel("<<") self.last_page = ClickableQLabel(">>") self.navigation_widget = QWidget() @@ -330,7 +330,7 @@ def _clear_current_path_layout(self): def _rebuild_current_path_layout(self): # Add root to widget list. - root = ClickableQLabelWithText("root") + root = ClickableQLabel("root") root.clicked_with_text.connect(self._on_breadcrumb_clicked) widgets = [root] @@ -340,8 +340,8 @@ def _rebuild_current_path_layout(self): node_id = node_id[: self.NODE_ID_MAXLEN - 3] + "..." # Convert node_id into a ClickableQWidget and add to widget list. - clickable_label = ClickableQLabelWithText(node_id) - clickable_label.clicked_with_text.connect(self._on_breadcrumb_clicked) # TODO: Find correct argument for method + clickable_label = ClickableQLabel(node_id) + clickable_label.clicked_with_text.connect(self._on_breadcrumb_clicked) widgets.append(clickable_label) # Add nodes to node path. @@ -468,17 +468,10 @@ def showImage(self): class ClickableQLabel(QLabel): clicked = Signal() - - def mousePressEvent(self, event): - self.clicked.emit() - -class ClickableQLabelWithText(ClickableQLabel): - # Define a new signal that emits the text of the label when clicked clicked_with_text = Signal(str) def mousePressEvent(self, event): - # Emit the oringal clicked signal - super().mousePressEvent(event) + self.clicked.emit() self.clicked_with_text.emit(self.text()) From 5d1af1e60c38442d89cde665b810b0c0a792f5a7 Mon Sep 17 00:00:00 2001 From: Josh Lohmolder Date: Wed, 12 Jun 2024 15:24:49 -0400 Subject: [PATCH 07/10] added one dimension to 1D arrays so they would be plotted --- examples/tiled_browser.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/examples/tiled_browser.py b/examples/tiled_browser.py index 6c9dfb2c16..88a44f854e 100644 --- a/examples/tiled_browser.py +++ b/examples/tiled_browser.py @@ -10,6 +10,7 @@ from datetime import date, datetime import functools import json +import numpy as np from qtpy.QtCore import Qt, Signal from qtpy.QtGui import QIcon, QPixmap @@ -238,11 +239,19 @@ def open_node(self, node_id): print(f"Cannot open type: '{family}'") return if family == StructureFamily.array: - plot = self.getPlotWidget() - plot.clear() - plot.getDefaultColormap().setName("viridis") - plot.addImage(node) - plot.resetZoom() + if node.ndim == 1: + node = node[:, np.newaxis] + plot = self.getPlotWidget() + plot.clear() + plot.getDefaultColormap().setName("viridis") + plot.addImage(node) + plot.resetZoom() + else: + plot = self.getPlotWidget() + plot.clear() + plot.getDefaultColormap().setName("viridis") + plot.addImage(node) + plot.resetZoom() elif family == StructureFamily.container: self.enter_node(node_id) else: From d9b9f8d7fc5d0119f28fa554e3a2205f96897ed6 Mon Sep 17 00:00:00 2001 From: Josh Lohmolder Date: Wed, 12 Jun 2024 15:30:48 -0400 Subject: [PATCH 08/10] Refactored code into generate_plot method. --- examples/tiled_browser.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/examples/tiled_browser.py b/examples/tiled_browser.py index 88a44f854e..553d81617d 100644 --- a/examples/tiled_browser.py +++ b/examples/tiled_browser.py @@ -232,6 +232,13 @@ def exit_node(self): self._current_page = 0 self._rebuild() + def generate_plot(self, node): + plot = self.getPlotWidget() + plot.clear() + plot.getDefaultColormap().setName("viridis") + plot.addImage(node) + plot.resetZoom() + def open_node(self, node_id): node = self.get_current_node()[node_id] family = node.item["attributes"]["structure_family"] @@ -241,17 +248,9 @@ def open_node(self, node_id): if family == StructureFamily.array: if node.ndim == 1: node = node[:, np.newaxis] - plot = self.getPlotWidget() - plot.clear() - plot.getDefaultColormap().setName("viridis") - plot.addImage(node) - plot.resetZoom() + self.generate_plot(node) else: - plot = self.getPlotWidget() - plot.clear() - plot.getDefaultColormap().setName("viridis") - plot.addImage(node) - plot.resetZoom() + self.generate_plot(node) elif family == StructureFamily.container: self.enter_node(node_id) else: From 4e06dbdc9b26435039453d2eb9d5337c6b4e336c Mon Sep 17 00:00:00 2001 From: Josh Lohmolder Date: Thu, 13 Jun 2024 10:06:38 -0400 Subject: [PATCH 09/10] Convert arrays more than three dimensions to three dimensions so they can be viewed. Cannot view arrays where the last dimension does not have 3 or 4 elements. --- examples/tiled_browser.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/examples/tiled_browser.py b/examples/tiled_browser.py index 553d81617d..aa24dfb113 100644 --- a/examples/tiled_browser.py +++ b/examples/tiled_browser.py @@ -11,6 +11,7 @@ import functools import json import numpy as np +import dask.array as da from qtpy.QtCore import Qt, Signal from qtpy.QtGui import QIcon, QPixmap @@ -249,6 +250,23 @@ def open_node(self, node_id): if node.ndim == 1: node = node[:, np.newaxis] self.generate_plot(node) + elif node.ndim > 3: + # Convert DaskArrayClient Object to Dask Array. + node_arr = node.compute() + node = da.from_array(node_arr) + + # Determine dimensions of array an which to slice. + num_dims = node.ndim + slicing_indices = tuple([0] * (num_dims - 3) + [slice(None)] * 3) + print(slicing_indices) + + # Convert array to three dimensions and plot data. + node_3d = node[slicing_indices] + try: + self.generate_plot(node_3d) + except ValueError: + print("RGB(A) image is expected to have 3" + "or 4 elements as last dimension. Got too many") else: self.generate_plot(node) elif family == StructureFamily.container: From 82e6cdb69d1ae9ef5f62093644a8da0c6db1d2e8 Mon Sep 17 00:00:00 2001 From: Josh Lohmolder Date: Thu, 13 Jun 2024 11:35:47 -0400 Subject: [PATCH 10/10] Removed two lines converting DaskArrayClient object to dask array. They weren't necessary. --- examples/tiled_browser.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/tiled_browser.py b/examples/tiled_browser.py index aa24dfb113..4f883789eb 100644 --- a/examples/tiled_browser.py +++ b/examples/tiled_browser.py @@ -252,13 +252,12 @@ def open_node(self, node_id): self.generate_plot(node) elif node.ndim > 3: # Convert DaskArrayClient Object to Dask Array. - node_arr = node.compute() - node = da.from_array(node_arr) + #node_arr = node.compute() + #node = da.from_array(node_arr) # Determine dimensions of array an which to slice. num_dims = node.ndim slicing_indices = tuple([0] * (num_dims - 3) + [slice(None)] * 3) - print(slicing_indices) # Convert array to three dimensions and plot data. node_3d = node[slicing_indices]