From 0fbcc59e122c0d345b7bf3048cd644c7ef55b92c Mon Sep 17 00:00:00 2001 From: Joshua Lohmolder Date: Mon, 8 Jul 2024 15:00:19 -0400 Subject: [PATCH 01/26] Added QTiledWidget.py to PyMca5/PyMcaGui/io. This has the TiledBrowser class in it. All the code containing the plot window has been commented out. --- PyMca5/PyMcaGui/io/QTiledWidget.py | 505 +++++++++++++++++++++++++++++ 1 file changed, 505 insertions(+) create mode 100644 PyMca5/PyMcaGui/io/QTiledWidget.py diff --git a/PyMca5/PyMcaGui/io/QTiledWidget.py b/PyMca5/PyMcaGui/io/QTiledWidget.py new file mode 100644 index 000000000..f4a848fc9 --- /dev/null +++ b/PyMca5/PyMcaGui/io/QTiledWidget.py @@ -0,0 +1,505 @@ +""" +This module is an example of a barebones QWidget plugin for PyMca + +Replace code below according to your needs. +""" +import collections +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 +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 PyMca5.PyMcaGui import PyMcaQt as 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.first_page = ClickableQLabel("<<") + self.previous_page = ClickableQLabel("<") + self.next_page = ClickableQLabel(">") + self.last_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.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 + 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) + 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.addLayout( self.current_path_layout ) + 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.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 + ) + + 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 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"] + if isinstance(node, DummyClient): + print(f"Cannot open type: '{family}'") + return + if family == StructureFamily.array: + if node.ndim == 1: + node = node[:, np.newaxis] + # self.generate_plot(node) + elif node.ndim > 3: + # Determine dimensions of array an which to slice. + num_dims = node.ndim + slicing_indices = tuple([0] * (num_dims - 3) + [slice(None)] * 3) + + # 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: + 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 _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() + self.current_path_layout.removeWidget(widget) + widget.deleteLater() + + def _rebuild_current_path_layout(self): + # Add root to widget list. + root = ClickableQLabel("root") + root.clicked_with_text.connect(self._on_breadcrumb_clicked) + 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] + "..." + + # Convert node_id into a ClickableQWidget and add to widget list. + clickable_label = ClickableQLabel(node_id) + clickable_label.clicked_with_text.connect(self._on_breadcrumb_clicked) + 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 + 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_layout() + 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 _on_first_page_clicked(self): + if self._current_page != 0: + self._current_page = 0 + self._rebuild() + + def _on_last_page_clicked(self): + 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 + 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() + clicked_with_text = Signal(str) + + def mousePressEvent(self, event): + self.clicked.emit() + self.clicked_with_text.emit(self.text()) + + +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 \ No newline at end of file From 1dd57747ccfc261ef34ff9600d58fb17341065a8 Mon Sep 17 00:00:00 2001 From: Joshua Lohmolder Date: Mon, 8 Jul 2024 15:02:00 -0400 Subject: [PATCH 02/26] Added bluesky icon to IconDict0, which will then add it to IconDict for PyMca_Icons.py --- PyMca5/PyMcaGui/plotting/PyMca_Icons.py | 61 +++++++++++++++++++++---- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/PyMca5/PyMcaGui/plotting/PyMca_Icons.py b/PyMca5/PyMcaGui/plotting/PyMca_Icons.py index b13251826..5eb690235 100644 --- a/PyMca5/PyMcaGui/plotting/PyMca_Icons.py +++ b/PyMca5/PyMcaGui/plotting/PyMca_Icons.py @@ -3585,6 +3585,44 @@ "..##################..", "......................"] +bluesky = [ +"32 32 2 1 ", +" c None", +"B c #87CEEB", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" BBBBBBB ", +" BBBBBBB ", +" BBBBBBBBBBBBBB ", +" BBBBBBBBBBBBBB ", +" BBBBBBBBBBBBBBBBBB ", +" BBBBBBBBBBBBBBBBBBB BBB ", +" BBBBBBBBBBBBBBBBBBB BBB ", +" BBBBBBBBBBBBBBBBBBBB BBBBBBBB ", +" BBBBBBBBBBBBBBBBBBBB BBBBBBBB ", +" BBBBBBBBBBBBBBBBBBBB BBBB BB", +" BBBBBBBBBBBBBBBBBBBB BBBB BB", +" BBBBBBBBBBBBBBBBBBBB BBBBBB BB", +" BBBBBBBBBBBBBBBBBBBB BBBBBB BB", +" BBBBBBBBBBBBBBBBBBBB BBBB BB ", +" BBBBBBBBBBBBBBBBBBBB BBBB BB ", +" BBBBBBBBBBBBBBBBBBBBBBBBB ", +" BBBBBBBBBBBBBBBBBBBBBBBBB ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +] + IconDict0 = { @@ -3597,6 +3635,7 @@ "fileprint": image_print_data, "spec": spec, "bliss": bliss, + "bluesky": bluesky, "normal": normal, "normalize16": normalize16, "reload": reload_, @@ -3825,7 +3864,9 @@ def __setitem__(self, key, item): IconDict = _PatchedIconDict(IconDict0) - +print(f"ID: {id(IconDict)}") +for key in IconDict.keys(): + print(key) def change_icons(plot): """Replace some of the silx icons with PyMca icons. @@ -3863,12 +3904,12 @@ def showIcons(): w.show() return w -if __name__ == '__main__': - from PyMca5.PyMcaGui import PyMcaQt as qt - app = qt.QApplication(sys.argv) - app.lastWindowClosed.connect(app.quit) - logging.basicConfig() - _logger.setLevel(logging.DEBUG) - w = showIcons() - app.exec() - app = None +#if __name__ == '__main__': +# from PyMca5.PyMcaGui import PyMcaQt as qt +# app = qt.QApplication(sys.argv) +# app.lastWindowClosed.connect(app.quit) +# logging.basicConfig() +# _logger.setLevel(logging.DEBUG) +# w = showIcons() +# app.exec() +# app = None From 1cd65a8273848a95378bbd7b281c00767c6d8bd3 Mon Sep 17 00:00:00 2001 From: Joshua Lohmolder Date: Mon, 8 Jul 2024 15:04:39 -0400 Subject: [PATCH 03/26] Added a tiledButton to the source selector that will have the bluesky icon on it eventually. --- PyMca5/PyMcaGui/io/QSourceSelector.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/PyMca5/PyMcaGui/io/QSourceSelector.py b/PyMca5/PyMcaGui/io/QSourceSelector.py index 0f834b06b..c27ddadad 100644 --- a/PyMca5/PyMcaGui/io/QSourceSelector.py +++ b/PyMca5/PyMcaGui/io/QSourceSelector.py @@ -86,6 +86,7 @@ def __init__(self, parent=None, filetypelist=None, pluginsIcon=False): self.specIcon = qt.QIcon(qt.QPixmap(icons.IconDict["bliss"])) else: self.specIcon = qt.QIcon(qt.QPixmap(icons.IconDict["spec"])) + self.tiledIcon = qt.QIcon(qt.QPixmap(icons.IconDict["bliss"])) openButton.setIcon(self.openIcon) openButton.setSizePolicy(qt.QSizePolicy(qt.QSizePolicy.Fixed, qt.QSizePolicy.Minimum)) @@ -106,8 +107,13 @@ def __init__(self, parent=None, filetypelist=None, pluginsIcon=False): else: specButton.setToolTip("Open new shared memory source") + tiledButton = qt.QPushButton(self.fileWidget) + tiledButton.setIcon(self.tiledIcon) + tiledButton.setToolTip("Open tiled data source") + closeButton.setSizePolicy(qt.QSizePolicy(qt.QSizePolicy.Fixed, qt.QSizePolicy.Minimum)) specButton.setSizePolicy(qt.QSizePolicy(qt.QSizePolicy.Fixed, qt.QSizePolicy.Minimum)) + tiledButton.setSizePolicy(qt.QSizePolicy(qt.QSizePolicy.Fixed, qt.QSizePolicy.Minimum)) refreshButton.setSizePolicy(qt.QSizePolicy(qt.QSizePolicy.Fixed, qt.QSizePolicy.Minimum)) openButton.clicked.connect(self._openFileSlot) @@ -121,10 +127,13 @@ def __init__(self, parent=None, filetypelist=None, pluginsIcon=False): _logger.debug("Using deprecated signal") self.fileCombo.activated[str].connect(self._fileSelection) + tiledButton.clicked.connect(self._tiledConnection) + fileWidgetLayout.addWidget(self.fileCombo) fileWidgetLayout.addWidget(openButton) fileWidgetLayout.addWidget(closeButton) fileWidgetLayout.addWidget(specButton) + fileWidgetLayout.addWidget(tiledButton) if sys.platform == "win32":specButton.hide() fileWidgetLayout.addWidget(refreshButton) self.specButton = specButton @@ -331,6 +340,10 @@ def openSpec(self): lambda i=spec:self.openFile(i, specsession=True)) menu.exec(self.cursor().pos()) + def _tiledConnection(self): + print(f"ID: {id(icons.IconDict)}") + for key in icons.IconDict.keys(): + print(key) def _fileSelection(self, qstring): _logger.debug("file selected %s", qstring) From 995142b2d29e4b5855f05809c36154f2f8b5b36b Mon Sep 17 00:00:00 2001 From: Joshua Lohmolder Date: Mon, 8 Jul 2024 15:09:13 -0400 Subject: [PATCH 04/26] Added key value pairs to both source_types and source_widgets dicts. The key value pair for the former was the TiledDataSource.SOURCE_TYPE and TiledDataSource.TiledDataSource. The later was TiledDataSource.SOURCE_TYPE and QTiledWidget.TiledBrowser --- PyMca5/PyMcaGui/pymca/QDataSource.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/PyMca5/PyMcaGui/pymca/QDataSource.py b/PyMca5/PyMcaGui/pymca/QDataSource.py index 55e3c6570..612fbcb86 100644 --- a/PyMca5/PyMcaGui/pymca/QDataSource.py +++ b/PyMca5/PyMcaGui/pymca/QDataSource.py @@ -75,6 +75,17 @@ source_types[NexusDataSource.SOURCE_TYPE] = NexusDataSource.NexusDataSource source_widgets[NexusDataSource.SOURCE_TYPE] = PyMcaNexusWidget.PyMcaNexusWidget +Tiled = True +try: + from PyMca5.PyMcaCore import TiledDataSource + from PyMca5.PyMcaGui.io import QTiledWidget +except Exception: + Tiled = False + +if Tiled: + source_types[TiledDataSource.SOURCE_TYPE] = TiledDataSource.TiledDataSource + source_widgets[TiledDataSource.SOURCE_TYPE] = QTiledWidget.TiledBrowser + def getSourceType(sourceName0): if type(sourceName0) == type([]): sourceName = sourceName0[0] From 02760b6ce9f41115baed916916985244cfddef20 Mon Sep 17 00:00:00 2001 From: Joshua Lohmolder Date: Mon, 8 Jul 2024 15:11:56 -0400 Subject: [PATCH 05/26] Changed relative reference of QDataSource in the imports to reference the specific path to the directory. --- PyMca5/PyMcaGui/pymca/QDispatcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PyMca5/PyMcaGui/pymca/QDispatcher.py b/PyMca5/PyMcaGui/pymca/QDispatcher.py index 856a408f7..9e3db0db7 100644 --- a/PyMca5/PyMcaGui/pymca/QDispatcher.py +++ b/PyMca5/PyMcaGui/pymca/QDispatcher.py @@ -33,7 +33,7 @@ from PyMca5.PyMcaGui import PyMcaQt as qt QTVERSION = qt.qVersion() from PyMca5.PyMcaGui.io import QSourceSelector -from . import QDataSource +from PyMca5.PyMcaGui.pymca import QDataSource #import weakref _logger = logging.getLogger(__name__) From e6781dc480093218f70b0c94afe398dcfbaea157 Mon Sep 17 00:00:00 2001 From: Joshua Lohmolder Date: Mon, 8 Jul 2024 15:13:26 -0400 Subject: [PATCH 06/26] Created a file for QDataSource in order to create a Tab for Tiled data in the QDispatcher. --- PyMca5/PyMcaCore/TiledDataSource.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 PyMca5/PyMcaCore/TiledDataSource.py diff --git a/PyMca5/PyMcaCore/TiledDataSource.py b/PyMca5/PyMcaCore/TiledDataSource.py new file mode 100644 index 000000000..17e1ee7d2 --- /dev/null +++ b/PyMca5/PyMcaCore/TiledDataSource.py @@ -0,0 +1,8 @@ +SOURCE_TYPE = 'Tiled' + +class TiledDataSource(object): + def __init__(self, name): + self.name = name + self.sourceName = name + self.source_type = SOURCE_TYPE + \ No newline at end of file From bec9a91f137c962548f67f73381528cfe4267875 Mon Sep 17 00:00:00 2001 From: Joshua Lohmolder Date: Fri, 12 Jul 2024 11:15:52 -0400 Subject: [PATCH 07/26] Removed debugging code from QSourceSelector.py and PyMca_Icons.py --- PyMca5/PyMcaGui/io/QSourceSelector.py | 7 +++---- PyMca5/PyMcaGui/plotting/PyMca_Icons.py | 3 --- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/PyMca5/PyMcaGui/io/QSourceSelector.py b/PyMca5/PyMcaGui/io/QSourceSelector.py index c27ddadad..dc3eae7e4 100644 --- a/PyMca5/PyMcaGui/io/QSourceSelector.py +++ b/PyMca5/PyMcaGui/io/QSourceSelector.py @@ -86,7 +86,7 @@ def __init__(self, parent=None, filetypelist=None, pluginsIcon=False): self.specIcon = qt.QIcon(qt.QPixmap(icons.IconDict["bliss"])) else: self.specIcon = qt.QIcon(qt.QPixmap(icons.IconDict["spec"])) - self.tiledIcon = qt.QIcon(qt.QPixmap(icons.IconDict["bliss"])) + self.tiledIcon = qt.QIcon(qt.QPixmap(icons.IconDict["bluesky"])) openButton.setIcon(self.openIcon) openButton.setSizePolicy(qt.QSizePolicy(qt.QSizePolicy.Fixed, qt.QSizePolicy.Minimum)) @@ -341,9 +341,8 @@ def openSpec(self): menu.exec(self.cursor().pos()) def _tiledConnection(self): - print(f"ID: {id(icons.IconDict)}") - for key in icons.IconDict.keys(): - print(key) + # TODO: Add connectivity to Tiled Tab + pass def _fileSelection(self, qstring): _logger.debug("file selected %s", qstring) diff --git a/PyMca5/PyMcaGui/plotting/PyMca_Icons.py b/PyMca5/PyMcaGui/plotting/PyMca_Icons.py index 5eb690235..a617525c3 100644 --- a/PyMca5/PyMcaGui/plotting/PyMca_Icons.py +++ b/PyMca5/PyMcaGui/plotting/PyMca_Icons.py @@ -3864,9 +3864,6 @@ def __setitem__(self, key, item): IconDict = _PatchedIconDict(IconDict0) -print(f"ID: {id(IconDict)}") -for key in IconDict.keys(): - print(key) def change_icons(plot): """Replace some of the silx icons with PyMca icons. From 11b16649b962709f810e6ebabd1f41141c595521 Mon Sep 17 00:00:00 2001 From: Joshua Lohmolder Date: Thu, 18 Jul 2024 15:08:13 -0400 Subject: [PATCH 08/26] Connected bluesky pushbutton to the Tiled Tab so that when the icon is clicked the Tiled Browser will be opened in the tab. --- PyMca5/PyMcaGui/io/QSourceSelector.py | 8 ++++---- PyMca5/PyMcaGui/io/QTiledWidget.py | 10 ++++++---- PyMca5/PyMcaGui/pymca/QDataSource.py | 19 ++++++------------- PyMca5/PyMcaGui/pymca/QDispatcher.py | 3 +++ PyMca5/__init__.py | 6 +++--- 5 files changed, 22 insertions(+), 24 deletions(-) diff --git a/PyMca5/PyMcaGui/io/QSourceSelector.py b/PyMca5/PyMcaGui/io/QSourceSelector.py index dc3eae7e4..51a90c10d 100644 --- a/PyMca5/PyMcaGui/io/QSourceSelector.py +++ b/PyMca5/PyMcaGui/io/QSourceSelector.py @@ -127,7 +127,7 @@ def __init__(self, parent=None, filetypelist=None, pluginsIcon=False): _logger.debug("Using deprecated signal") self.fileCombo.activated[str].connect(self._fileSelection) - tiledButton.clicked.connect(self._tiledConnection) + tiledButton.clicked.connect(self.tiledConnection) fileWidgetLayout.addWidget(self.fileCombo) fileWidgetLayout.addWidget(openButton) @@ -340,9 +340,9 @@ def openSpec(self): lambda i=spec:self.openFile(i, specsession=True)) menu.exec(self.cursor().pos()) - def _tiledConnection(self): - # TODO: Add connectivity to Tiled Tab - pass + def tiledConnection(self): + ddict = {"event": "Open Tiled Tab"} + self.sigSourceSelectorSignal.emit(ddict) def _fileSelection(self, qstring): _logger.debug("file selected %s", qstring) diff --git a/PyMca5/PyMcaGui/io/QTiledWidget.py b/PyMca5/PyMcaGui/io/QTiledWidget.py index f4a848fc9..4ed11a0f0 100644 --- a/PyMca5/PyMcaGui/io/QTiledWidget.py +++ b/PyMca5/PyMcaGui/io/QTiledWidget.py @@ -55,10 +55,12 @@ 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 + # Added to have 'Tiled' tab appear in QDispatcher.py + sigAddSelection = qt.pyqtSignal(object) + sigRemoveSelection = qt.pyqtSignal(object) + sigReplaceSelection = qt.pyqtSignal(object) + sigOtherSignals = qt.pyqtSignal(object) + def __init__(self, parent=None): super().__init__() diff --git a/PyMca5/PyMcaGui/pymca/QDataSource.py b/PyMca5/PyMcaGui/pymca/QDataSource.py index 612fbcb86..1c4cb1eb7 100644 --- a/PyMca5/PyMcaGui/pymca/QDataSource.py +++ b/PyMca5/PyMcaGui/pymca/QDataSource.py @@ -38,9 +38,11 @@ from PyMca5.PyMcaCore import SpecFileDataSource from PyMca5.PyMcaCore import EdfFileDataSource +from PyMca5.PyMcaCore import TiledDataSource from PyMca5.PyMcaIO import BlissSpecFile from PyMca5.PyMcaGui.io import QEdfFileWidget from PyMca5.PyMcaGui.io import QSpecFileWidget +from PyMca5.PyMcaGui.io import QTiledWidget if sys.platform == "win32": source_types = { SpecFileDataSource.SOURCE_TYPE: SpecFileDataSource.SpecFileDataSource, @@ -55,11 +57,13 @@ from PyMca5.PyMcaGui.io import QSpsWidget source_types = { SpecFileDataSource.SOURCE_TYPE: SpecFileDataSource.SpecFileDataSource, EdfFileDataSource.SOURCE_TYPE: EdfFileDataSource.EdfFileDataSource, - QSpsDataSource.SOURCE_TYPE: QSpsDataSource.QSpsDataSource} + QSpsDataSource.SOURCE_TYPE: QSpsDataSource.QSpsDataSource, + TiledDataSource.SOURCE_TYPE: TiledDataSource.TiledDataSource} source_widgets = { SpecFileDataSource.SOURCE_TYPE: QSpecFileWidget.QSpecFileWidget, EdfFileDataSource.SOURCE_TYPE: QEdfFileWidget.QEdfFileWidget, - QSpsDataSource.SOURCE_TYPE: QSpsWidget.QSpsWidget} + QSpsDataSource.SOURCE_TYPE: QSpsWidget.QSpsWidget, + TiledDataSource.SOURCE_TYPE: QTiledWidget.TiledBrowser} NEXUS = True try: @@ -75,17 +79,6 @@ source_types[NexusDataSource.SOURCE_TYPE] = NexusDataSource.NexusDataSource source_widgets[NexusDataSource.SOURCE_TYPE] = PyMcaNexusWidget.PyMcaNexusWidget -Tiled = True -try: - from PyMca5.PyMcaCore import TiledDataSource - from PyMca5.PyMcaGui.io import QTiledWidget -except Exception: - Tiled = False - -if Tiled: - source_types[TiledDataSource.SOURCE_TYPE] = TiledDataSource.TiledDataSource - source_widgets[TiledDataSource.SOURCE_TYPE] = QTiledWidget.TiledBrowser - def getSourceType(sourceName0): if type(sourceName0) == type([]): sourceName = sourceName0[0] diff --git a/PyMca5/PyMcaGui/pymca/QDispatcher.py b/PyMca5/PyMcaGui/pymca/QDispatcher.py index 9e3db0db7..752f62483 100644 --- a/PyMca5/PyMcaGui/pymca/QDispatcher.py +++ b/PyMca5/PyMcaGui/pymca/QDispatcher.py @@ -260,6 +260,9 @@ def _sourceSelectorSlot(self, ddict): _logger.debug("connecting source of type %s" % sourceType) source.sigUpdated.connect(self._selectionUpdatedSlot) + elif ddict["event"] == "Open Tiled Tab": + self.tabWidget.setCurrentWidget(self.selectorWidget["Tiled"]) + elif (ddict["event"] == "SourceSelected") or \ (ddict["event"] == "SourceReloaded"): found = 0 diff --git a/PyMca5/__init__.py b/PyMca5/__init__.py index eb68f9530..deb9ad27a 100644 --- a/PyMca5/__init__.py +++ b/PyMca5/__init__.py @@ -47,9 +47,9 @@ import ctypes from ctypes.wintypes import MAX_PATH -if os.path.exists(os.path.join(\ - os.path.dirname(os.path.dirname(__file__)), 'bootstrap.py')): - raise ImportError('PyMca cannot be imported from source directory') +#if os.path.exists(os.path.join(\ +# os.path.dirname(os.path.dirname(__file__)), 'bootstrap.py')): +# raise ImportError('PyMca cannot be imported from source directory') def version(): return __version__ From 659713b7d4648bb9a34b5f676b3943fdecf90c50 Mon Sep 17 00:00:00 2001 From: Joshua Lohmolder Date: Thu, 18 Jul 2024 15:24:00 -0400 Subject: [PATCH 09/26] Added shells of TiledDataSource.py and TiledFile.py. TiledDataSource.py seems necessary if we want a Tiled Tab which holds the Tiled Browser. There may be a module we can reference in Tiled repo in TiledFile.py or maybe instead of TiledFiled.py. --- PyMca5/PyMcaCore/TiledDataSource.py | 132 +++++++++++++++++++++++++- PyMca5/PyMcaGui/io/QSourceSelector.py | 3 + PyMca5/PyMcaIO/TiledFile.py | 3 + 3 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 PyMca5/PyMcaIO/TiledFile.py diff --git a/PyMca5/PyMcaCore/TiledDataSource.py b/PyMca5/PyMcaCore/TiledDataSource.py index 17e1ee7d2..686c417f9 100644 --- a/PyMca5/PyMcaCore/TiledDataSource.py +++ b/PyMca5/PyMcaCore/TiledDataSource.py @@ -1,8 +1,134 @@ +import logging +import time + +from PyMca5.PyMcaIO import TiledFile + SOURCE_TYPE = 'Tiled' +_logger = logging.getLogger(__name__) + class TiledDataSource(object): - def __init__(self, name): - self.name = name - self.sourceName = name + """ + Creates instance of a Tiled Data Source class. This is neccesary + to create a Tiled Tab in the QDispatcher, which houses the Tiled + Browser. + + This is largely based on the NexusDataSource class, but all Data + Source tabs (Spec, EDF, SPS) have an analogous class. + + See QDataSource.py + """ + + def __init__(self, nameInput): + self.nameList = nameInput self.source_type = SOURCE_TYPE + self.__sourceNameList = self.__sourceNameList + self._sourceObjectList = [] + self.refresh() + + def refresh(self): + pass + + def getSourceInfo(self): + """ + Returns a dictionary with the key "KeyList" (list of all available keys + in this source). Each element in "KeyList" has the form 'n1.n2' where + n1 is the source number and n2 entry number in file both starting at 1. + """ + return self.__getSourceInfo() + + def __getSourceInfo(self): + SourceInfo={} + SourceInfo["SourceType"]=SOURCE_TYPE + SourceInfo["KeyList"]=[] + i = 0 + for sourceObject in self._sourceObjectList: + i+=1 + nEntries = len(sourceObject["/"].keys()) + for n in range(nEntries): + SourceInfo["KeyList"].append("%d.%d" % (i,n+1)) + SourceInfo["Size"]=len(SourceInfo["KeyList"]) + return SourceInfo + + def getKeyInfo(self, key): + if key in self.getSourceInfo()['KeyList']: + return self.__getKeyInfo(key) + else: + #should we raise a KeyError? + _logger.debug("Error key not in list ") + return {} + + def __getKeyInfo(self,key): + try: + index, entry = key.split(".") + index = int(index)-1 + entry = int(entry)-1 + except Exception: + #should we rise an error? + _logger.debug("Error trying to interpret key = %s", key) + return {} + + sourceObject = self._sourceObjectList[index] + info = {} + info["SourceType"] = SOURCE_TYPE + #doubts about if refer to the list or to the individual file + info["SourceName"] = self.sourceName[index] + info["Key"] = key + #specific info of interest + info['FileName'] = sourceObject.name + return info + + def getDataObject(self): + pass + + def isUpdated(self, sourceName, key): + #sourceName is redundant? + index, entry = key.split(".") + index = int(index)-1 + lastmodified = os.path.getmtime(self.__sourceNameList[index]) + if lastmodified != self.__lastKeyInfo[key]: + self.__lastKeyInfo[key] = lastmodified + return True + else: + return False + +source_types = { SOURCE_TYPE: NexusDataSource} + +def DataSource(name="", source_type=SOURCE_TYPE): + try: + sourceClass = source_types[source_type] + except KeyError: + #ERROR invalid source type + raise TypeError("Invalid Source Type, source type should be one of %s" %\ + source_types.keys()) + return sourceClass(name) + + +if __name__ == "__main__": + try: + sourcename=sys.argv[1] + key =sys.argv[2] + except Exception: + print("Usage: NexusDataSource ") + sys.exit() + #one can use this: + obj = NexusDataSource(sourcename) + #or this: + obj = DataSource(sourcename) + #data = obj.getData(key,selection={'pos':(10,10),'size':(40,40)}) + #data = obj.getDataObject(key,selection={'pos':None,'size':None}) + t0 = time.time() + data = obj.getDataObject(key,selection=None) + print("elapsed = ",time.time() - t0) + print("info = ",data.info) + if data.data is not None: + print("data shape = ",data.data.shape) + print(numpy.ravel(data.data)[0:10]) + else: + print(data.y[0].shape) + print(numpy.ravel(data.y[0])[0:10]) + data = obj.getDataObject('1.1',selection=None) + r = int(key.split('.')[-1]) + print(" data[%d,0:10] = " % (r-1),data.data[r-1 ,0:10]) + print(" data[0:10,%d] = " % (r-1),data.data[0:10, r-1]) \ No newline at end of file diff --git a/PyMca5/PyMcaGui/io/QSourceSelector.py b/PyMca5/PyMcaGui/io/QSourceSelector.py index 51a90c10d..d5d9a8b93 100644 --- a/PyMca5/PyMcaGui/io/QSourceSelector.py +++ b/PyMca5/PyMcaGui/io/QSourceSelector.py @@ -341,9 +341,12 @@ def openSpec(self): menu.exec(self.cursor().pos()) def tiledConnection(self): + """When 'bluesky' icon clicked it opens the TiledBrowser in the Tiled Tab""" ddict = {"event": "Open Tiled Tab"} self.sigSourceSelectorSignal.emit(ddict) + # Potentially add a authorization window when clicked + def _fileSelection(self, qstring): _logger.debug("file selected %s", qstring) key = str(qstring) diff --git a/PyMca5/PyMcaIO/TiledFile.py b/PyMca5/PyMcaIO/TiledFile.py new file mode 100644 index 000000000..edcc6cefb --- /dev/null +++ b/PyMca5/PyMcaIO/TiledFile.py @@ -0,0 +1,3 @@ +class TiledFile(object): + def __init__(self): + self.header = {} \ No newline at end of file From 3ff42ea7645cda69ba7c1a704124b15c2dd1c33a Mon Sep 17 00:00:00 2001 From: Joshua Lohmolder Date: Thu, 18 Jul 2024 16:01:37 -0400 Subject: [PATCH 10/26] Replaced all Nexus references with Tiled in TiledDataSource.py. --- PyMca5/PyMcaCore/TiledDataSource.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/PyMca5/PyMcaCore/TiledDataSource.py b/PyMca5/PyMcaCore/TiledDataSource.py index 686c417f9..467841e35 100644 --- a/PyMca5/PyMcaCore/TiledDataSource.py +++ b/PyMca5/PyMcaCore/TiledDataSource.py @@ -1,4 +1,6 @@ import logging +import numpy as np +import os import time from PyMca5.PyMcaIO import TiledFile @@ -81,7 +83,7 @@ def __getKeyInfo(self,key): def getDataObject(self): pass - def isUpdated(self, sourceName, key): + def isUpdated(self, sourceName, key): #sourceName is redundant? index, entry = key.split(".") index = int(index)-1 @@ -92,7 +94,7 @@ def isUpdated(self, sourceName, key): else: return False -source_types = { SOURCE_TYPE: NexusDataSource} +source_types = { SOURCE_TYPE: TiledDataSource} def DataSource(name="", source_type=SOURCE_TYPE): try: @@ -112,7 +114,7 @@ def DataSource(name="", source_type=SOURCE_TYPE): print("Usage: NexusDataSource ") sys.exit() #one can use this: - obj = NexusDataSource(sourcename) + obj = TiledDataSource(sourcename) #or this: obj = DataSource(sourcename) #data = obj.getData(key,selection={'pos':(10,10),'size':(40,40)}) @@ -123,10 +125,10 @@ def DataSource(name="", source_type=SOURCE_TYPE): print("info = ",data.info) if data.data is not None: print("data shape = ",data.data.shape) - print(numpy.ravel(data.data)[0:10]) + print(np.ravel(data.data)[0:10]) else: print(data.y[0].shape) - print(numpy.ravel(data.y[0])[0:10]) + print(np.ravel(data.y[0])[0:10]) data = obj.getDataObject('1.1',selection=None) r = int(key.split('.')[-1]) print(" data[%d,0:10] = " % (r-1),data.data[r-1 ,0:10]) From 5866fe5c12d2b54ce4a1af0380c16a887f03eee9 Mon Sep 17 00:00:00 2001 From: Joshua Lohmolder Date: Thu, 25 Jul 2024 10:41:06 -0400 Subject: [PATCH 11/26] Changed relative imports to absolute imports and fixed way McaWindow inherits the ScanWindow class. --- PyMca5/PyMcaGui/plotting/PlotWindow.py | 14 +++++++------- PyMca5/PyMcaGui/pymca/McaWindow.py | 19 ++++++++++--------- PyMca5/PyMcaGui/pymca/ScanWindow.py | 2 +- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/PyMca5/PyMcaGui/plotting/PlotWindow.py b/PyMca5/PyMcaGui/plotting/PlotWindow.py index bfe7a6abe..de3197a8b 100644 --- a/PyMca5/PyMcaGui/plotting/PlotWindow.py +++ b/PyMca5/PyMcaGui/plotting/PlotWindow.py @@ -42,12 +42,12 @@ import numpy from numpy import argsort, nonzero, take -from . import LegendSelector -from .ObjectPrintConfigurationDialog import ObjectPrintConfigurationDialog -from . import McaROIWidget -from . import PlotWidget -from . import MaskImageTools -from . import RenameCurveDialog +from PyMca5.PyMcaGui.plotting import LegendSelector +from PyMca5.PyMcaGui.plotting.ObjectPrintConfigurationDialog import ObjectPrintConfigurationDialog +from PyMca5.PyMcaGui.plotting import McaROIWidget +from PyMca5.PyMcaGui.plotting import PlotWidget +from PyMca5.PyMcaGui.plotting import MaskImageTools +from PyMca5.PyMcaGui.plotting import RenameCurveDialog try: from . import ColormapDialog @@ -55,7 +55,7 @@ except Exception: COLORMAP_DIALOG = False -from .PyMca_Icons import IconDict +from PyMca5.PyMcaGui.plotting.PyMca_Icons import IconDict from PyMca5.PyMcaGui import PyMcaQt as qt from PyMca5.PyMcaGui.io import PyMcaFileDialogs diff --git a/PyMca5/PyMcaGui/pymca/McaWindow.py b/PyMca5/PyMcaGui/pymca/McaWindow.py index ce5f2d2a4..a0fd52546 100644 --- a/PyMca5/PyMcaGui/pymca/McaWindow.py +++ b/PyMca5/PyMcaGui/pymca/McaWindow.py @@ -47,13 +47,13 @@ from PyMca5.PyMcaGui.io import PyMcaFileDialogs from PyMca5.PyMcaGui.plotting.PyMca_Icons import IconDict -from .ScanWindow import ScanWindow -from . import McaCalibrationControlGUI +from PyMca5.PyMcaGui.pymca import ScanWindow +from PyMca5.PyMcaGui.pymca import McaCalibrationControlGUI from PyMca5.PyMcaIO import ConfigDict from PyMca5.PyMcaGui.physics.xrf import McaAdvancedFit from PyMca5.PyMcaGui.physics.xrf import McaCalWidget from PyMca5.PyMcaCore import DataObject -from . import McaSimpleFit +from PyMca5.PyMcaGui.pymca import McaSimpleFit from PyMca5.PyMcaMath.fitting import Specfit from PyMca5.PyMcaMath.fitting import SpecfitFuns from PyMca5.PyMcaGui.plotting import PyMcaPrintPreview @@ -74,15 +74,16 @@ # _logger.setLevel(logging.DEBUG -class McaWindow(ScanWindow): - def __init__(self, parent=None, name="Mca Window", specfit=None, backend=None, +class McaWindow(ScanWindow.ScanWindow): + def __init__(self, parent=None, title="Mca Window", specfit=None, backend=None, plugins=True, newplot=False, roi=True, fit=True, **kw): - ScanWindow.__init__(self, parent, - name=name, - newplot=newplot, - plugins=plugins, + super().__init__(parent=parent, + name=title, + specfit=specfit, backend=backend, + plugins=plugins, + newplot=newplot, roi=roi, fit=fit, **kw) diff --git a/PyMca5/PyMcaGui/pymca/ScanWindow.py b/PyMca5/PyMcaGui/pymca/ScanWindow.py index ac981dc07..a43cb8971 100644 --- a/PyMca5/PyMcaGui/pymca/ScanWindow.py +++ b/PyMca5/PyMcaGui/pymca/ScanWindow.py @@ -45,7 +45,7 @@ from PyMca5.PyMcaGui.io import PyMcaFileDialogs from PyMca5.PyMcaGui.plotting import PlotWindow -from . import ScanFit +from PyMca5.PyMcaGui.pymca import ScanFit from PyMca5.PyMcaMath import SimpleMath from PyMca5.PyMcaCore import DataObject import copy From 79855241c41c58ee6615e40738bc6faebdddf1f8 Mon Sep 17 00:00:00 2001 From: Joshua Lohmolder Date: Mon, 29 Jul 2024 15:21:08 -0400 Subject: [PATCH 12/26] Created Data Channel Table for Tiled Browser. --- PyMca5/PyMcaGui/io/QTiledWidget.py | 91 +++++++---- PyMca5/PyMcaGui/io/TiledDataChannelTable.py | 162 ++++++++++++++++++++ 2 files changed, 221 insertions(+), 32 deletions(-) create mode 100644 PyMca5/PyMcaGui/io/TiledDataChannelTable.py diff --git a/PyMca5/PyMcaGui/io/QTiledWidget.py b/PyMca5/PyMcaGui/io/QTiledWidget.py index 4ed11a0f0..852fd99cb 100644 --- a/PyMca5/PyMcaGui/io/QTiledWidget.py +++ b/PyMca5/PyMcaGui/io/QTiledWidget.py @@ -7,7 +7,6 @@ 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 @@ -26,14 +25,14 @@ QVBoxLayout, QWidget, ) +from PyMca5.PyMcaGui import PyMcaQt as qt +from PyMca5.PyMcaGui.io import TiledDataChannelTable + 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 PyMca5.PyMcaGui import PyMcaQt as qt -#from silx.gui.plot import PlotWidget - def json_decode(obj): if isinstance(obj, (datetime, date)): return obj.isoformat() @@ -84,6 +83,10 @@ def __init__(self, parent=None): connection_layout.addStretch() self.connection_widget.setLayout(connection_layout) + # Search By elements + + # Search By layout + # Navigation elements self.rows_per_page_label = QLabel("Rows per page: ") self.rows_per_page_selector = QComboBox() @@ -159,11 +162,38 @@ def __init__(self, parent=None): self.catalog_table_widget.setLayout(catalog_table_layout) self.catalog_table_widget.setVisible(False) + # Data Channels Table + self.data_channels_table = TiledDataChannelTable.TiledDataChannelTable() + self.data_channels_table.setVisible(False) + + # Command Button Elements + self.buttonWidget = qt.QWidget(self) + self.buttonWidget.setSizePolicy(qt.QSizePolicy.Minimum, + qt.QSizePolicy.Minimum) + addButton = qt.QPushButton("ADD", self.buttonWidget) + removeButton = qt.QPushButton("REMOVE", self.buttonWidget) + replaceButton = qt.QPushButton("REPLACE", self.buttonWidget) + + # Command Buttons Layout + buttonLayout = qt.QHBoxLayout(self.buttonWidget) + buttonLayout.addWidget(addButton) + buttonLayout.addWidget(removeButton) + buttonLayout.addWidget(replaceButton) + buttonLayout.setContentsMargins(5, 5, 5, 5) + self.buttonWidget.setVisible(False) + + # Command Buttons Connections +# addButton.connect(self.__addClicked) +# replaceButton.connect(self.__replaceClicked) +# removeButton.connect(self.__removeClicked) + 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.addWidget(self.data_channels_table) + self.splitter.addWidget(self.buttonWidget) self.splitter.setStretchFactor(1, 2) @@ -171,7 +201,6 @@ def __init__(self, parent=None): browser_layout.addWidget(self.splitter) layout = QHBoxLayout() - # layout.addWidget(self._plot) layout.addLayout( browser_layout ) centralWidget = qt.QWidget(self) @@ -188,6 +217,8 @@ def __init__(self, parent=None): self._on_rows_per_page_changed ) + self.data = None + def _on_connect_clicked(self): url = self.url_entry.displayText().strip() # url = "https://tiled-demo.blueskyproject.io/api" @@ -204,12 +235,24 @@ def _on_connect_clicked(self): self.connection_label.setText(f"Connected to {url}") self.set_root(root) + def setData(self, filedata): + self.data = filedata + self.refreshData() + + def refreshData(self): + pass + + def clearData(self): + self.data = None + 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.data_channels_table.setVisible(True) + self.buttonWidget.setVisible(True) self._rebuild() def get_current_node(self): @@ -222,22 +265,21 @@ def get_node(self, node_path): return self.root def enter_node(self, node_id): - self.node_path += (node_id,) - self._current_page = 0 - self._rebuild() + if 'raw' in self.node_path: + self.node_path += (node_id,) + rawDataChannels = tuple(self.get_current_node().items()) + dataChannels = [channel[0] for channel in rawDataChannels] + self.data_channels_table.build_table(dataChannels) + else: + 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 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"] @@ -245,23 +287,8 @@ def open_node(self, node_id): print(f"Cannot open type: '{family}'") return if family == StructureFamily.array: - if node.ndim == 1: - node = node[:, np.newaxis] - # self.generate_plot(node) - elif node.ndim > 3: - # Determine dimensions of array an which to slice. - num_dims = node.ndim - slicing_indices = tuple([0] * (num_dims - 3) + [slice(None)] * 3) - - # 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) + # TODO: find a way set data to self.data + self.setData(node) elif family == StructureFamily.container: self.enter_node(node_id) else: diff --git a/PyMca5/PyMcaGui/io/TiledDataChannelTable.py b/PyMca5/PyMcaGui/io/TiledDataChannelTable.py new file mode 100644 index 000000000..1359ed200 --- /dev/null +++ b/PyMca5/PyMcaGui/io/TiledDataChannelTable.py @@ -0,0 +1,162 @@ +from PyMca5.PyMcaGui import PyMcaQt as qt +from PyMca5.PyMcaGui.io.SpecFileCntTable import CheckBoxItem + +class TiledDataChannelTable(qt.QTableWidget): + """ + Creates the data channel table (second table) inside the QTiledWidget. + The selections for the x and y set the axes in the scan window. + """ + sigTiledDataChannelTableSignal = qt.pyqtSignal(object) + def __init__(self, parent=None): + qt.QTableWidget.__init__(self, parent) + self.dataChannelList = [] + self.xSelection = [] + self.ySelection = [] + self.monSelection = [] + + # Columns & Column Headers + labels = ['Data Channel', 'x', 'y', 'Mon'] + self.setColumnCount(len(labels)) + for i in range(len(labels)): + item = self.horizontalHeaderItem(i) + if item is None: + item = qt.QTableWidgetItem(labels[i]) + item.setText(labels[i]) + self.setHorizontalHeaderItem(i, item) + + def build_table(self, channelList): + """ + Builds the table in QTiledWidget based on the datachannels in selected + scan file. + """ + n = len(channelList) + self.setRowCount(n) + if n > 0: + for i in range(n): + self._addLine(i, channelList[i]) + + def _addLine(self, i, channelLabel): + """ + Adds individual line to Data Channel Table based on the label of the + Data Channel in the scan file. + """ + # Data Channel name + item = self.item(i, 0) + if item is None: + item = qt.QTableWidgetItem(channelLabel) + item.setTextAlignment(qt.Qt.AlignHCenter | qt.Qt.AlignVCenter) + self.setItem(i, 0, item) + else: + item.setText(channelLabel) + + # Data Channel name is not selectable + item.setFlags(qt.Qt.ItemIsEnabled) + + # Checkboxes + for j in range(1, 4): + widget = self.cellWidget(i, j) + if widget is None: + widget = CheckBoxItem(self, i, j) + self.setCellWidget(i, j, widget) + widget.sigCheckBoxItemSignal.connect(self._mySlot) + else: + pass + + def _mySlot(self, ddict): + row = ddict["row"] + col = ddict["col"] + + # 'x' column + if col == 1: + if ddict["state"]: + if row not in self.xSelection: + self.xSelection.append(row) + else: + if row in self.xSelection: + del self.xSelection[self.xSelection.index(row)] + # 'y' column + if col == 2: + if ddict["state"]: + if row not in self.ySelection: + self.ySelection.append(row) + else: + if row in self.ySelection: + del self.ySelection[self.ySelection.index(row)] + + # 'Mon' column + if col == 3: + if ddict["state"]: + if row not in self.monSelection: + self.monSelection.append(row) + else: + if row in self.monSelection: + del self.monSelection[self.monSelection.index(row)] + + self._update() + + def _update(self): + for i in range(self.rowCount()): + + # 'x' column + j = 1 + widget = self.cellWidget(i, j) + if i in self.xSelection: + if not widget.isChecked(): + widget.setChecked(True) + else: + if widget.isChecked(): + widget.setChecked(False) + + # 'y' column + j = 2 + widget = self.cellWidget(i, j) + if i in self.ySelection: + if not widget.isChecked(): + widget.setChecked(True) + else: + if widget.isChecked(): + widget.setChecked(False) + + # 'Mon' column + j = 3 + widget = self.cellWidget(i, j) + if i in self.monSelection: + if not widget.isChecked(): + widget.setChecked(True) + + else: + if widget.isChecked(): + widget.setChecked(False) + + ddict = {"event": "updated"} + self.sigTiledDataChannelTableSignal.emit(ddict) + + def getChannelSelection(self): + ddict = { + "Data Channel List": self.dataChannelList[:], + 'x': self.xSelection[:], + 'y' : self.ySelection[:], + 'm' : self.monSelection[:], + } + + return ddict + + def setChannelSelection(self, ddict): + dataChannelList = ddict.get("DataChannel List", self.dataChannelList[:]) + + self.xSelection = [item for item in ddict.get('x', []) if item < len(dataChannelList)] + self.ySelection = [item for item in ddict.get('y', []) if item < len(dataChannelList)] + self.monSelection = [item for item in ddict.get('m', []) if item < len(dataChannelList)] + + self._update() + +def main(): + app = qt.QApplication([]) + table = TiledDataChannelTable() + table.build_table(['Ch1', 'Ch2', 'Ch3']) + table.setChannelSelection({'x': [1,2], 'y':[4], 'ChList': ['dummy', 'Ch0', 'Ch1', 'Ch2', 'Ch3']}) + table.show() + app.exec() + +if __name__ == "__main__": + main() From 6426a70c837bf6982f27314344dc100905009d62 Mon Sep 17 00:00:00 2001 From: Joshua Lohmolder Date: Tue, 30 Jul 2024 10:24:42 -0400 Subject: [PATCH 13/26] Formatted Data Channel Table and set it up so the data channels are cleared if a different scan is selected. --- PyMca5/PyMcaGui/io/QTiledWidget.py | 38 ++++++++++++++++++--- PyMca5/PyMcaGui/io/TiledDataChannelTable.py | 30 +++++++++++++++- 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/PyMca5/PyMcaGui/io/QTiledWidget.py b/PyMca5/PyMcaGui/io/QTiledWidget.py index 852fd99cb..eb664a650 100644 --- a/PyMca5/PyMcaGui/io/QTiledWidget.py +++ b/PyMca5/PyMcaGui/io/QTiledWidget.py @@ -84,8 +84,10 @@ def __init__(self, parent=None): self.connection_widget.setLayout(connection_layout) # Search By elements + # TODO: Create drop down menu that searches scans by different metadata # Search By layout + # TODO: Incorporate Search By elements into a layout # Navigation elements self.rows_per_page_label = QLabel("Rows per page: ") @@ -183,9 +185,9 @@ def __init__(self, parent=None): self.buttonWidget.setVisible(False) # Command Buttons Connections -# addButton.connect(self.__addClicked) -# replaceButton.connect(self.__replaceClicked) -# removeButton.connect(self.__removeClicked) +# addButton.connect(self._addClicked) +# replaceButton.connect(self._replaceClicked) +# removeButton.connect(self._removeClicked) self.splitter = QSplitter(self) self.splitter.setOrientation(Qt.Orientation.Vertical) @@ -195,7 +197,11 @@ def __init__(self, parent=None): self.splitter.addWidget(self.data_channels_table) self.splitter.addWidget(self.buttonWidget) - self.splitter.setStretchFactor(1, 2) + # Set stretch factors for widgets + self.splitter.setStretchFactor(0, 1) # Strech factor for Connection Widget + self.splitter.setStretchFactor(1, 2) # Strech factor for Catalog Table + self.splitter.setStretchFactor(2, 3) # Strech factor for Data Channel Table + self.splitter.setStretchFactor(3, 1) # Strech factor for the Command Buttons browser_layout = QVBoxLayout() browser_layout.addWidget(self.splitter) @@ -465,6 +471,7 @@ def _rebuild(self): self._rebuild_table() self._rebuild_current_path_layout() self._set_current_location_label() + self.data_channels_table.clear_table() def _on_prev_page_clicked(self): if self._current_page != 0: @@ -502,6 +509,29 @@ def _set_current_location_label(self): current_location_text = f"{starting_index}-{ending_index} of {len(self.get_current_node())}" self.current_location_label.setText(current_location_text) + def _addClicked(self, emit=True): + sel_list = [] + channel_sel = self.data_channels_table.getChannelSelection() + if len(channel_sel['Data Channel List']): + if len(channel_sel['y']): + sel = { + 'SourceName': self.data.SourceName, + 'SourceType': self.data.sourceType, + 'Key': scan, + 'selection': {'x': channel_sel['x'], + 'y': channel_sel['y'], + 'm': channel_sel['m'], + 'Channel List': channel_sel['Data Channel List']}, + 'scanselection': True, + } + sel_list.append(sel) + + if emit: + if len(sel_list): + self.sigAddSelection.emit(sel_list) + else: + return sel_list + #def getPlotWidget(self): # """Returns the PlotWidget contains in this window""" # return self._plot diff --git a/PyMca5/PyMcaGui/io/TiledDataChannelTable.py b/PyMca5/PyMcaGui/io/TiledDataChannelTable.py index 1359ed200..bdd2b11a3 100644 --- a/PyMca5/PyMcaGui/io/TiledDataChannelTable.py +++ b/PyMca5/PyMcaGui/io/TiledDataChannelTable.py @@ -1,3 +1,5 @@ +from PyQt5 import QtWidgets + from PyMca5.PyMcaGui import PyMcaQt as qt from PyMca5.PyMcaGui.io.SpecFileCntTable import CheckBoxItem @@ -14,7 +16,10 @@ def __init__(self, parent=None): self.ySelection = [] self.monSelection = [] - # Columns & Column Headers + def format_table(self): + """Sets the column headers and the size of the columns for the table.""" + + # Column Labels labels = ['Data Channel', 'x', 'y', 'Mon'] self.setColumnCount(len(labels)) for i in range(len(labels)): @@ -24,6 +29,20 @@ def __init__(self, parent=None): item.setText(labels[i]) self.setHorizontalHeaderItem(i, item) + def clear_table(self): + """Clears the table if a different scan is selected.""" + # Clear contents of the table + self.setRowCount(0) + self.setColumnCount(0) + + # Reset internal state + self.dataChannelList = [] + self.xSelection = [] + self.ySelection = [] + self.monSelection = [] + + self.format_table() + def build_table(self, channelList): """ Builds the table in QTiledWidget based on the datachannels in selected @@ -62,6 +81,15 @@ def _addLine(self, i, channelLabel): else: pass + # Resize columns to fit contents + self.resizeColumnsToContents() + + # Stretch Columns to fill the table + column_count = self.columnCount() + for column in range(column_count): + self.horizontalHeader().setStretchLastSection(True) + self.horizontalHeader().setSectionResizeMode(column, QtWidgets.QHeaderView.Stretch) + def _mySlot(self, ddict): row = ddict["row"] col = ddict["col"] From 4711c8e762cb066b0ed729642a8d3899e80bd190 Mon Sep 17 00:00:00 2001 From: Joshua Lohmolder Date: Tue, 30 Jul 2024 15:50:57 -0400 Subject: [PATCH 14/26] Connect 'ADD', 'REPLACE', and 'REMOVE' buttons in Tiled Browser to methods to add, replace, and remove scans. The methods need to be completed. --- PyMca5/PyMcaGui/io/QTiledWidget.py | 17 ++++++++++++----- PyMca5/PyMcaGui/io/TiledDataChannelTable.py | 1 + 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/PyMca5/PyMcaGui/io/QTiledWidget.py b/PyMca5/PyMcaGui/io/QTiledWidget.py index eb664a650..3e0b8f5ca 100644 --- a/PyMca5/PyMcaGui/io/QTiledWidget.py +++ b/PyMca5/PyMcaGui/io/QTiledWidget.py @@ -185,9 +185,9 @@ def __init__(self, parent=None): self.buttonWidget.setVisible(False) # Command Buttons Connections -# addButton.connect(self._addClicked) -# replaceButton.connect(self._replaceClicked) -# removeButton.connect(self._removeClicked) + addButton.clicked.connect(self._addClicked) + replaceButton.clicked.connect(self._replaceClicked) + removeButton.clicked.connect(self._removeClicked) self.splitter = QSplitter(self) self.splitter.setOrientation(Qt.Orientation.Vertical) @@ -510,6 +510,8 @@ def _set_current_location_label(self): self.current_location_label.setText(current_location_text) def _addClicked(self, emit=True): + """Plots scan to the scan window after it is selected and the add button is clicked.""" + sel_list = [] channel_sel = self.data_channels_table.getChannelSelection() if len(channel_sel['Data Channel List']): @@ -517,7 +519,6 @@ def _addClicked(self, emit=True): sel = { 'SourceName': self.data.SourceName, 'SourceType': self.data.sourceType, - 'Key': scan, 'selection': {'x': channel_sel['x'], 'y': channel_sel['y'], 'm': channel_sel['m'], @@ -531,7 +532,13 @@ def _addClicked(self, emit=True): self.sigAddSelection.emit(sel_list) else: return sel_list - + + def _replaceClicked(self): + pass + + def _removeClicked(self): + pass + #def getPlotWidget(self): # """Returns the PlotWidget contains in this window""" # return self._plot diff --git a/PyMca5/PyMcaGui/io/TiledDataChannelTable.py b/PyMca5/PyMcaGui/io/TiledDataChannelTable.py index bdd2b11a3..71c8a2b95 100644 --- a/PyMca5/PyMcaGui/io/TiledDataChannelTable.py +++ b/PyMca5/PyMcaGui/io/TiledDataChannelTable.py @@ -48,6 +48,7 @@ def build_table(self, channelList): Builds the table in QTiledWidget based on the datachannels in selected scan file. """ + self.dataChannelList = channelList n = len(channelList) self.setRowCount(n) if n > 0: From dda425cd27b1791877c69019b6fbe7adb418f1b0 Mon Sep 17 00:00:00 2001 From: Joshua Lohmolder Date: Fri, 2 Aug 2024 16:26:34 -0400 Subject: [PATCH 15/26] Cleaned up Data Channel Table. Added Search By Widgets to Tiled Browser. Still need to connect them to functions. --- PyMca5/PyMcaGui/io/QTiledWidget.py | 53 +++++++++++++-------- PyMca5/PyMcaGui/io/TiledDataChannelTable.py | 14 ++++-- 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/PyMca5/PyMcaGui/io/QTiledWidget.py b/PyMca5/PyMcaGui/io/QTiledWidget.py index 3e0b8f5ca..5d2403f00 100644 --- a/PyMca5/PyMcaGui/io/QTiledWidget.py +++ b/PyMca5/PyMcaGui/io/QTiledWidget.py @@ -65,9 +65,6 @@ def __init__(self, parent=None): 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") @@ -84,10 +81,32 @@ def __init__(self, parent=None): self.connection_widget.setLayout(connection_layout) # Search By elements - # TODO: Create drop down menu that searches scans by different metadata - - # Search By layout - # TODO: Incorporate Search By elements into a layout + searchBy_tuple = ('plan', 'sample', 'scan_id', 'uid') + + self.search_dropdown = QComboBox() + self.search_dropdown.addItems(searchBy_tuple) + self.search_label = QLabel("Search By") + self.search_entry = QLineEdit() + self.search_entry.setPlaceholderText("Enter Search") + self.search_drop_widget = QWidget() + self.search_widget = QWidget() + + # Search Drop Down + search_dropdown_layout = QHBoxLayout() + search_dropdown_layout.addWidget(self.search_dropdown) + search_dropdown_layout.addWidget(self.search_label) + search_dropdown_layout.setContentsMargins(0, 0, 0, 0) + search_dropdown_layout.setSpacing(5) + self.search_drop_widget.setLayout(search_dropdown_layout) + + # Larger Search Widget + search_layout = QVBoxLayout() + search_layout.addWidget(self.search_drop_widget) + search_layout.addWidget(self.search_entry) + search_layout.setContentsMargins(0, 0, 0, 0) + search_layout.setSpacing(10) + self.search_widget.setLayout(search_layout) + self.search_widget.setVisible(False) # Navigation elements self.rows_per_page_label = QLabel("Rows per page: ") @@ -193,15 +212,17 @@ def __init__(self, parent=None): self.splitter.setOrientation(Qt.Orientation.Vertical) self.splitter.addWidget(self.connection_widget) + self.splitter.addWidget(self.search_widget) self.splitter.addWidget(self.catalog_table_widget) self.splitter.addWidget(self.data_channels_table) self.splitter.addWidget(self.buttonWidget) # Set stretch factors for widgets self.splitter.setStretchFactor(0, 1) # Strech factor for Connection Widget - self.splitter.setStretchFactor(1, 2) # Strech factor for Catalog Table - self.splitter.setStretchFactor(2, 3) # Strech factor for Data Channel Table - self.splitter.setStretchFactor(3, 1) # Strech factor for the Command Buttons + self.splitter.setStretchFactor(1, 1) # Strech factor for Search Widget + self.splitter.setStretchFactor(2, 3) # Strech factor for Catalog Table + self.splitter.setStretchFactor(3, 4) # Strech factor for Data Channel Table + self.splitter.setStretchFactor(4, 1) # Strech factor for the Command Buttons browser_layout = QVBoxLayout() browser_layout.addWidget(self.splitter) @@ -241,7 +262,7 @@ def _on_connect_clicked(self): self.connection_label.setText(f"Connected to {url}") self.set_root(root) - def setData(self, filedata): + def setDataSource(self, filedata): self.data = filedata self.refreshData() @@ -256,6 +277,7 @@ def set_root(self, root): self.node_path = () self._current_page = 0 if root is not None: + self.search_widget.setVisible(True) self.catalog_table_widget.setVisible(True) self.data_channels_table.setVisible(True) self.buttonWidget.setVisible(True) @@ -516,6 +538,7 @@ def _addClicked(self, emit=True): channel_sel = self.data_channels_table.getChannelSelection() if len(channel_sel['Data Channel List']): if len(channel_sel['y']): + # TODO: find was to give self.data a SourceName method. sel = { 'SourceName': self.data.SourceName, 'SourceType': self.data.sourceType, @@ -539,14 +562,6 @@ def _replaceClicked(self): def _removeClicked(self): pass - #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): diff --git a/PyMca5/PyMcaGui/io/TiledDataChannelTable.py b/PyMca5/PyMcaGui/io/TiledDataChannelTable.py index 71c8a2b95..d9280c118 100644 --- a/PyMca5/PyMcaGui/io/TiledDataChannelTable.py +++ b/PyMca5/PyMcaGui/io/TiledDataChannelTable.py @@ -9,6 +9,7 @@ class TiledDataChannelTable(qt.QTableWidget): The selections for the x and y set the axes in the scan window. """ sigTiledDataChannelTableSignal = qt.pyqtSignal(object) + def __init__(self, parent=None): qt.QTableWidget.__init__(self, parent) self.dataChannelList = [] @@ -29,6 +30,11 @@ def format_table(self): item.setText(labels[i]) self.setHorizontalHeaderItem(i, item) + # Strech Columns to fill table + self.horizontalHeader().setStretchLastSection(True) + for column in range(self.columnCount()): + self.horizontalHeader().setSectionResizeMode(column, QtWidgets.QHeaderView.Stretch) + def clear_table(self): """Clears the table if a different scan is selected.""" # Clear contents of the table @@ -52,8 +58,8 @@ def build_table(self, channelList): n = len(channelList) self.setRowCount(n) if n > 0: - for i in range(n): - self._addLine(i, channelList[i]) + for (i, channelLabel) in enumerate(channelList): + self._addLine(i, channelLabel) def _addLine(self, i, channelLabel): """ @@ -73,14 +79,12 @@ def _addLine(self, i, channelLabel): item.setFlags(qt.Qt.ItemIsEnabled) # Checkboxes - for j in range(1, 4): + for j in range(1, self.columnCount()): widget = self.cellWidget(i, j) if widget is None: widget = CheckBoxItem(self, i, j) self.setCellWidget(i, j, widget) widget.sigCheckBoxItemSignal.connect(self._mySlot) - else: - pass # Resize columns to fit contents self.resizeColumnsToContents() From 56aa0a9fe661faae31d40b5e02a4a014ef077ac1 Mon Sep 17 00:00:00 2001 From: Joshua Lohmolder Date: Fri, 2 Aug 2024 16:29:27 -0400 Subject: [PATCH 16/26] Cleanned up files per comments on last PR. --- PyMca5/PyMcaCore/TiledDataSource.py | 65 ++++++++++++++++--------- PyMca5/PyMcaGui/plotting/PyMca_Icons.py | 19 ++++---- PyMca5/PyMcaGui/pymca/McaWindow.py | 4 +- 3 files changed, 53 insertions(+), 35 deletions(-) diff --git a/PyMca5/PyMcaCore/TiledDataSource.py b/PyMca5/PyMcaCore/TiledDataSource.py index 467841e35..b79a417de 100644 --- a/PyMca5/PyMcaCore/TiledDataSource.py +++ b/PyMca5/PyMcaCore/TiledDataSource.py @@ -2,7 +2,9 @@ import numpy as np import os import time +import sys +from PyMca5.PyMcaCore import DataObject from PyMca5.PyMcaIO import TiledFile SOURCE_TYPE = 'Tiled' @@ -22,14 +24,21 @@ class TiledDataSource(object): """ def __init__(self, nameInput): - self.nameList = nameInput + if isinstance(nameInput, list): + nameList = nameInput + else: + nameList = [nameInput] + self.sourceName = nameList self.source_type = SOURCE_TYPE - self.__sourceNameList = self.__sourceNameList + self.__sourceNameList = self.sourceName self._sourceObjectList = [] self.refresh() def refresh(self): - pass + self._sourceObjectList = [] + for name in self.__sourceNameList: + self._sourceObjectList.append(TiledFile.TiledFile(name)) + self.__lastKeyInfo = {} def getSourceInfo(self): """ @@ -40,16 +49,16 @@ def getSourceInfo(self): return self.__getSourceInfo() def __getSourceInfo(self): - SourceInfo={} - SourceInfo["SourceType"]=SOURCE_TYPE - SourceInfo["KeyList"]=[] + SourceInfo = {} + SourceInfo["SourceType"] = SOURCE_TYPE + SourceInfo["KeyList"] = [] i = 0 for sourceObject in self._sourceObjectList: - i+=1 + i += 1 nEntries = len(sourceObject["/"].keys()) for n in range(nEntries): SourceInfo["KeyList"].append("%d.%d" % (i,n+1)) - SourceInfo["Size"]=len(SourceInfo["KeyList"]) + SourceInfo["Size"] = len(SourceInfo["KeyList"]) return SourceInfo def getKeyInfo(self, key): @@ -60,28 +69,38 @@ def getKeyInfo(self, key): _logger.debug("Error key not in list ") return {} - def __getKeyInfo(self,key): + def __getKeyInfo(self, key): try: index, entry = key.split(".") - index = int(index)-1 - entry = int(entry)-1 + index = int(index) - 1 + entry = int(entry) - 1 except Exception: #should we rise an error? _logger.debug("Error trying to interpret key = %s", key) return {} sourceObject = self._sourceObjectList[index] - info = {} - info["SourceType"] = SOURCE_TYPE - #doubts about if refer to the list or to the individual file - info["SourceName"] = self.sourceName[index] - info["Key"] = key - #specific info of interest - info['FileName'] = sourceObject.name + + info = { + "SourceType": SOURCE_TYPE, + "SourceName": self.sourceName[index], + "Key": key, + "FileName": sourceObject.name, + } + return info - def getDataObject(self): - pass + def getDataObject(self, key, selection=None): + data = DataObject.DataObject() + index, entry = key.split(".") + index = int(index) - 1 + entry = int(entry) - 1 + + data.info = self.__getKeyInfo(key) + data.info['selection'] = selection + + sourceObject = self._sourceObjectList[index] + data.data = sourceObject.getData() def isUpdated(self, sourceName, key): #sourceName is redundant? @@ -108,10 +127,10 @@ def DataSource(name="", source_type=SOURCE_TYPE): if __name__ == "__main__": try: - sourcename=sys.argv[1] - key =sys.argv[2] + sourcename = sys.argv[1] + key = sys.argv[2] except Exception: - print("Usage: NexusDataSource ") + print("Usage: TiledDataSource ") sys.exit() #one can use this: obj = TiledDataSource(sourcename) diff --git a/PyMca5/PyMcaGui/plotting/PyMca_Icons.py b/PyMca5/PyMcaGui/plotting/PyMca_Icons.py index a617525c3..f68abe789 100644 --- a/PyMca5/PyMcaGui/plotting/PyMca_Icons.py +++ b/PyMca5/PyMcaGui/plotting/PyMca_Icons.py @@ -3876,7 +3876,6 @@ def change_icons(plot): if hasattr(plot, "printPreview"): plot.printPreview.setIcon(qt.QIcon(qt.QPixmap(IconDict["fileprint"]))) - def showIcons(): w = qt.QWidget() g = qt.QGridLayout(w) @@ -3901,12 +3900,12 @@ def showIcons(): w.show() return w -#if __name__ == '__main__': -# from PyMca5.PyMcaGui import PyMcaQt as qt -# app = qt.QApplication(sys.argv) -# app.lastWindowClosed.connect(app.quit) -# logging.basicConfig() -# _logger.setLevel(logging.DEBUG) -# w = showIcons() -# app.exec() -# app = None +if __name__ == '__main__': + from PyMca5.PyMcaGui import PyMcaQt as qt + app = qt.QApplication(sys.argv) + app.lastWindowClosed.connect(app.quit) + logging.basicConfig() + _logger.setLevel(logging.DEBUG) + w = showIcons() + app.exec() + app = None diff --git a/PyMca5/PyMcaGui/pymca/McaWindow.py b/PyMca5/PyMcaGui/pymca/McaWindow.py index a0fd52546..092bac8df 100644 --- a/PyMca5/PyMcaGui/pymca/McaWindow.py +++ b/PyMca5/PyMcaGui/pymca/McaWindow.py @@ -47,8 +47,8 @@ from PyMca5.PyMcaGui.io import PyMcaFileDialogs from PyMca5.PyMcaGui.plotting.PyMca_Icons import IconDict -from PyMca5.PyMcaGui.pymca import ScanWindow from PyMca5.PyMcaGui.pymca import McaCalibrationControlGUI +from PyMca5.PyMcaGui.pymca.ScanWindow import ScanWindow from PyMca5.PyMcaIO import ConfigDict from PyMca5.PyMcaGui.physics.xrf import McaAdvancedFit from PyMca5.PyMcaGui.physics.xrf import McaCalWidget @@ -74,7 +74,7 @@ # _logger.setLevel(logging.DEBUG -class McaWindow(ScanWindow.ScanWindow): +class McaWindow(ScanWindow): def __init__(self, parent=None, title="Mca Window", specfit=None, backend=None, plugins=True, newplot=False, roi=True, fit=True, **kw): From b6f3be9eeb83f70d8d708f67832ee4c4eed593fb Mon Sep 17 00:00:00 2001 From: Joshua Lohmolder Date: Mon, 5 Aug 2024 15:01:41 -0400 Subject: [PATCH 17/26] Added search by functionality to Tiled Browser. --- PyMca5/PyMcaGui/io/QTiledWidget.py | 37 ++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/PyMca5/PyMcaGui/io/QTiledWidget.py b/PyMca5/PyMcaGui/io/QTiledWidget.py index 5d2403f00..6e275c901 100644 --- a/PyMca5/PyMcaGui/io/QTiledWidget.py +++ b/PyMca5/PyMcaGui/io/QTiledWidget.py @@ -81,10 +81,11 @@ def __init__(self, parent=None): self.connection_widget.setLayout(connection_layout) # Search By elements - searchBy_tuple = ('plan', 'sample', 'scan_id', 'uid') + searchBy_tuple = ('Plan Name', 'Plan Type', 'Project', 'Scan ID', 'uid') self.search_dropdown = QComboBox() self.search_dropdown.addItems(searchBy_tuple) + self.search_dropdown.currentTextChanged.connect(self._set_search_by) self.search_label = QLabel("Search By") self.search_entry = QLineEdit() self.search_entry.setPlaceholderText("Enter Search") @@ -244,6 +245,8 @@ def __init__(self, parent=None): self._on_rows_per_page_changed ) + # Default values + self.searchBy_selection = 'uid' self.data = None def _on_connect_clicked(self): @@ -468,9 +471,22 @@ def _rebuild_table(self): icon = self.style().standardIcon( QStyle.SP_TitleBarContextHelpButton ) - self.catalog_table.setItem( - row_index, 0, QTableWidgetItem(icon, key) - ) + if 'raw' not in self.node_path: + self.catalog_table.setItem( + row_index, 0, QTableWidgetItem(icon, key) + ) + else: + if self.searchBy_selection != 'uid': + searchBy_key = self._get_search_by() + metadata_path = value.item["attributes"]["metadata"]["start"][searchBy_key] + key = str(metadata_path) + self.catalog_table.setItem( + row_index, 0, QTableWidgetItem(icon, key) + ) + else: + self.catalog_table.setItem( + row_index, 0, QTableWidgetItem(icon, key) + ) # remove extra rows for row in range(self._rows_per_page - len(items)): @@ -495,6 +511,19 @@ def _rebuild(self): self._set_current_location_label() self.data_channels_table.clear_table() + def _set_search_by(self, selection=None): + """ + Converts user selection in the Search DropDown to value that can be + referenced in the metadata. + """ + if selection is not None: + self.searchBy_selection = selection.lower().replace(' ', '_') + return self.searchBy_selection + + def _get_search_by(self): + """"Retrieves user selected search by method in form that can be searched in metadata.""" + return self.searchBy_selection + def _on_prev_page_clicked(self): if self._current_page != 0: self._current_page -= 1 From 48d437f75ada3a65329923ef81ee6c6ebe2dbba3 Mon Sep 17 00:00:00 2001 From: Joshua Lohmolder Date: Tue, 6 Aug 2024 10:47:39 -0400 Subject: [PATCH 18/26] Added search entry functionality to Tiled Browser. --- PyMca5/PyMcaGui/io/QTiledWidget.py | 66 +++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/PyMca5/PyMcaGui/io/QTiledWidget.py b/PyMca5/PyMcaGui/io/QTiledWidget.py index 6e275c901..20ff96949 100644 --- a/PyMca5/PyMcaGui/io/QTiledWidget.py +++ b/PyMca5/PyMcaGui/io/QTiledWidget.py @@ -81,7 +81,7 @@ def __init__(self, parent=None): self.connection_widget.setLayout(connection_layout) # Search By elements - searchBy_tuple = ('Plan Name', 'Plan Type', 'Project', 'Scan ID', 'uid') + searchBy_tuple = ('Plan Name', 'Plan Type', 'Scan ID', 'uid') self.search_dropdown = QComboBox() self.search_dropdown.addItems(searchBy_tuple) @@ -89,6 +89,7 @@ def __init__(self, parent=None): self.search_label = QLabel("Search By") self.search_entry = QLineEdit() self.search_entry.setPlaceholderText("Enter Search") + self.search_entry.textChanged.connect(self._search_text_changed) self.search_drop_widget = QWidget() self.search_widget = QWidget() @@ -248,6 +249,7 @@ def __init__(self, parent=None): # Default values self.searchBy_selection = 'uid' self.data = None + self.previous_search_text = '' def _on_connect_clicked(self): url = self.url_entry.displayText().strip() @@ -292,7 +294,10 @@ def get_current_node(self): @functools.lru_cache(maxsize=1) def get_node(self, node_path): if node_path: + # if 'raw' not in node_path: return self.root[node_path] + # else: + # print(node_path) return self.root def enter_node(self, node_id): @@ -446,10 +451,6 @@ def _rebuild_table(self): 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()[ @@ -457,9 +458,8 @@ def _rebuild_table(self): ] # 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 - ): + row_index = start + for key, value in items: family = value.item["attributes"]["structure_family"] if family == StructureFamily.container: icon = self.style().standardIcon(QStyle.SP_DirHomeIcon) @@ -472,24 +472,30 @@ def _rebuild_table(self): QStyle.SP_TitleBarContextHelpButton ) if 'raw' not in self.node_path: - self.catalog_table.setItem( - row_index, 0, QTableWidgetItem(icon, key) - ) + self.catalog_table.insertRow(row_index) + self.catalog_table.setItem(row_index, 0, QTableWidgetItem(icon, key)) + row_index += 1 + else: if self.searchBy_selection != 'uid': searchBy_key = self._get_search_by() metadata_path = value.item["attributes"]["metadata"]["start"][searchBy_key] key = str(metadata_path) - self.catalog_table.setItem( - row_index, 0, QTableWidgetItem(icon, key) - ) + + user_entry = self.search_entry.text() + if user_entry: + if self._filter_table(key): + self.catalog_table.insertRow(row_index) + self.catalog_table.setItem(row_index, 0, QTableWidgetItem(icon, key)) + row_index += 1 + else: - self.catalog_table.setItem( - row_index, 0, QTableWidgetItem(icon, key) - ) + self.catalog_table.insertRow(row_index) + self.catalog_table.setItem(row_index, 0, QTableWidgetItem(icon, key)) + row_index += 1 # remove extra rows - for row in range(self._rows_per_page - len(items)): + while row_index < self.catalog_table.rowCount(): self.catalog_table.removeRow(self.catalog_table.rowCount() - 1) headers = [ @@ -505,6 +511,12 @@ def _rebuild_table(self): self._clear_metadata() self.catalog_table.blockSignals(prev_block) + def _add_line_catalog_table(self, row_index, icon, key): + """Add line to catalog table.""" + self.catalog_table.insertRow(row_index) + self.catalog_table.setItem(row_index, 0, QTableWidgetItem(icon, key)) + row_index += 1 + def _rebuild(self): self._rebuild_table() self._rebuild_current_path_layout() @@ -518,12 +530,26 @@ def _set_search_by(self, selection=None): """ if selection is not None: self.searchBy_selection = selection.lower().replace(' ', '_') + self._rebuild() return self.searchBy_selection - + def _get_search_by(self): """"Retrieves user selected search by method in form that can be searched in metadata.""" return self.searchBy_selection - + + def _filter_table(self, scan): + """This filters out scans that do not match the text in the search bar.""" + user_entry = str(self.search_entry.text()) + value = user_entry in scan + return value + + def _search_text_changed(self): + """Updates Catalog Table if in 'Raw' directory and search entry is changed.""" + current_text = self.search_entry.text() + if current_text != self.previous_search_text: + if 'raw' in self.node_path: + self._rebuild_table() + def _on_prev_page_clicked(self): if self._current_page != 0: self._current_page -= 1 From e9f67ccdaf7377f0948dc68e4f4fb6984ec888bc Mon Sep 17 00:00:00 2001 From: Joshua Lohmolder Date: Tue, 6 Aug 2024 16:36:50 -0400 Subject: [PATCH 19/26] Can select items in Catalog Table that have text that is not uid now. --- PyMca5/PyMcaGui/io/QTiledWidget.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/PyMca5/PyMcaGui/io/QTiledWidget.py b/PyMca5/PyMcaGui/io/QTiledWidget.py index 20ff96949..90bbccd63 100644 --- a/PyMca5/PyMcaGui/io/QTiledWidget.py +++ b/PyMca5/PyMcaGui/io/QTiledWidget.py @@ -250,6 +250,7 @@ def __init__(self, parent=None): self.searchBy_selection = 'uid' self.data = None self.previous_search_text = '' + self.key_to_uid = {} def _on_connect_clicked(self): url = self.url_entry.displayText().strip() @@ -349,16 +350,27 @@ def _on_item_double_click(self, item): if item is self.catalog_breadcrumbs: self.exit_node() return - self.open_node(item.text()) + + if 'raw' not in self.node_path: + self.open_node(item.text()) + else: + uid = str(self.key_to_uid.get(item.text())) + self.open_node(uid) 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,) + if 'raw' not in self.node_path: + node_path = self.node_path + (name,) + + else: + uid = str(self.key_to_uid.get(name)) + node_path = self.node_path + (uid,) + node = self.get_node(node_path) attrs = node.item["attributes"] @@ -480,7 +492,9 @@ def _rebuild_table(self): if self.searchBy_selection != 'uid': searchBy_key = self._get_search_by() metadata_path = value.item["attributes"]["metadata"]["start"][searchBy_key] + uid = value.item["attributes"]["metadata"]["start"]["uid"] key = str(metadata_path) + self.key_to_uid[key] = uid user_entry = self.search_entry.text() if user_entry: From f70e2003497ed70b8521e09c63e76b87bdc653f6 Mon Sep 17 00:00:00 2001 From: Joshua Lohmolder Date: Wed, 7 Aug 2024 11:03:59 -0400 Subject: [PATCH 20/26] Search bar filters results for entire Catalog table now. --- PyMca5/PyMcaGui/io/QTiledWidget.py | 87 +++++++++++++++++++----------- 1 file changed, 55 insertions(+), 32 deletions(-) diff --git a/PyMca5/PyMcaGui/io/QTiledWidget.py b/PyMca5/PyMcaGui/io/QTiledWidget.py index 90bbccd63..3a16828d7 100644 --- a/PyMca5/PyMcaGui/io/QTiledWidget.py +++ b/PyMca5/PyMcaGui/io/QTiledWidget.py @@ -85,6 +85,7 @@ def __init__(self, parent=None): self.search_dropdown = QComboBox() self.search_dropdown.addItems(searchBy_tuple) + self.search_dropdown.setCurrentIndex(-1) self.search_dropdown.currentTextChanged.connect(self._set_search_by) self.search_label = QLabel("Search By") self.search_entry = QLineEdit() @@ -351,9 +352,10 @@ def _on_item_double_click(self, item): self.exit_node() return - if 'raw' not in self.node_path: + try: self.open_node(item.text()) - else: + + except KeyError: uid = str(self.key_to_uid.get(item.text())) self.open_node(uid) @@ -364,15 +366,15 @@ def _on_item_selected(self): return name = item.text() - if 'raw' not in self.node_path: + try: node_path = self.node_path + (name,) + node = self.get_node(node_path) - else: + except KeyError: uid = str(self.key_to_uid.get(name)) node_path = self.node_path + (uid,) + node = self.get_node(node_path) - node = self.get_node(node_path) - attrs = node.item["attributes"] family = attrs["structure_family"] metadata = json.dumps(attrs["metadata"], indent=2, default=json_decode) @@ -463,11 +465,51 @@ def _rebuild_table(self): self.catalog_table.insertRow(0) self.catalog_table.setItem(0, 0, self.catalog_breadcrumbs) + # All key value pairs for current node + all_items = self.get_current_node().items() + + # Changed keys: + changed_keys_values = [] + + # Change key metadata if applicable + for key, value in all_items: + if self.searchBy_selection != 'uid' and 'raw' in self.node_path: + searchBy_key = self._get_search_by() + metadata_path = value.item["attributes"]["metadata"]["start"][searchBy_key] + uid = value.item["attributes"]["metadata"]["start"]["uid"] + key = str(metadata_path) + self.key_to_uid[key] = uid + + user_entry = self.search_entry.text() + # If there is an entry in the search bar + if user_entry: + if self._filter_row_in_table(key): + changed_keys_values.append((key, value)) + else: + pass + # If there is no entry in the search bar + else: + changed_keys_values.append((key, value)) + + # If there is no Search By selection + else: + user_entry = self.search_entry.text() + # If there is an entry in the search bar + if user_entry: + if self._filter_row_in_table(key): + changed_keys_values.append((key, value)) + else: + pass + # If there is no entry in the search bar + else: + changed_keys_values.append((key, value)) + node_offset = self._rows_per_page * self._current_page # Fetch a page of keys. - items = self.get_current_node().items()[ + items = changed_keys_values[ 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 row_index = start @@ -483,30 +525,10 @@ def _rebuild_table(self): icon = self.style().standardIcon( QStyle.SP_TitleBarContextHelpButton ) - if 'raw' not in self.node_path: - self.catalog_table.insertRow(row_index) - self.catalog_table.setItem(row_index, 0, QTableWidgetItem(icon, key)) - row_index += 1 - else: - if self.searchBy_selection != 'uid': - searchBy_key = self._get_search_by() - metadata_path = value.item["attributes"]["metadata"]["start"][searchBy_key] - uid = value.item["attributes"]["metadata"]["start"]["uid"] - key = str(metadata_path) - self.key_to_uid[key] = uid - - user_entry = self.search_entry.text() - if user_entry: - if self._filter_table(key): - self.catalog_table.insertRow(row_index) - self.catalog_table.setItem(row_index, 0, QTableWidgetItem(icon, key)) - row_index += 1 - - else: - self.catalog_table.insertRow(row_index) - self.catalog_table.setItem(row_index, 0, QTableWidgetItem(icon, key)) - row_index += 1 + self.catalog_table.insertRow(row_index) + self.catalog_table.setItem(row_index, 0, QTableWidgetItem(icon, key)) + row_index += 1 # remove extra rows while row_index < self.catalog_table.rowCount(): @@ -551,7 +573,7 @@ def _get_search_by(self): """"Retrieves user selected search by method in form that can be searched in metadata.""" return self.searchBy_selection - def _filter_table(self, scan): + def _filter_row_in_table(self, scan): """This filters out scans that do not match the text in the search bar.""" user_entry = str(self.search_entry.text()) value = user_entry in scan @@ -561,7 +583,8 @@ def _search_text_changed(self): """Updates Catalog Table if in 'Raw' directory and search entry is changed.""" current_text = self.search_entry.text() if current_text != self.previous_search_text: - if 'raw' in self.node_path: + if 'raw' in self.node_path: # TODO: (Maybe) add second conditional. + # Don't want to rebuild table if scan is selected self._rebuild_table() def _on_prev_page_clicked(self): From 8c7d9aab519a7cc437dfa965fe5a704e94b46f4f Mon Sep 17 00:00:00 2001 From: Joshua Lohmolder Date: Thu, 8 Aug 2024 10:56:05 -0400 Subject: [PATCH 21/26] Different scans can be selected, search by can be changed, and search text entered after a scan has been selected. --- PyMca5/PyMcaGui/io/QTiledWidget.py | 69 ++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 18 deletions(-) diff --git a/PyMca5/PyMcaGui/io/QTiledWidget.py b/PyMca5/PyMcaGui/io/QTiledWidget.py index 3a16828d7..22cc28565 100644 --- a/PyMca5/PyMcaGui/io/QTiledWidget.py +++ b/PyMca5/PyMcaGui/io/QTiledWidget.py @@ -85,7 +85,6 @@ def __init__(self, parent=None): self.search_dropdown = QComboBox() self.search_dropdown.addItems(searchBy_tuple) - self.search_dropdown.setCurrentIndex(-1) self.search_dropdown.currentTextChanged.connect(self._set_search_by) self.search_label = QLabel("Search By") self.search_entry = QLineEdit() @@ -296,10 +295,8 @@ def get_current_node(self): @functools.lru_cache(maxsize=1) def get_node(self, node_path): if node_path: - # if 'raw' not in node_path: return self.root[node_path] - # else: - # print(node_path) + return self.root def enter_node(self, node_id): @@ -319,6 +316,10 @@ def exit_node(self): self._rebuild() def open_node(self, node_id): + # This allows another scan to be selected after one is already clicked on. + if 'raw' in self.node_path and 'raw' != self.node_path[-1]: + self.node_path = self.node_path[:-1] + node = self.get_current_node()[node_id] family = node.item["attributes"]["structure_family"] if isinstance(node, DummyClient): @@ -355,25 +356,37 @@ def _on_item_double_click(self, item): try: self.open_node(item.text()) - except KeyError: + except KeyError: uid = str(self.key_to_uid.get(item.text())) self.open_node(uid) - - def _on_item_selected(self): + + 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() + try: node_path = self.node_path + (name,) node = self.get_node(node_path) except KeyError: - uid = str(self.key_to_uid.get(name)) - node_path = self.node_path + (uid,) - node = self.get_node(node_path) + try: + uid = str(self.key_to_uid.get(name)) + node_path = self.node_path + (uid,) + node = self.get_node(node_path) + + except KeyError: + try: + node_path = self._replace_last_node(name) + node = self.get_node(node_path) + + except KeyError: + uid = str(self.key_to_uid.get(name)) + node_path = self._replace_last_node(uid) + node = self.get_node(node_path) attrs = node.item["attributes"] family = attrs["structure_family"] @@ -495,7 +508,7 @@ def _rebuild_table(self): else: user_entry = self.search_entry.text() # If there is an entry in the search bar - if user_entry: + if user_entry and 'raw' in self.node_path: if self._filter_row_in_table(key): changed_keys_values.append((key, value)) else: @@ -504,6 +517,9 @@ def _rebuild_table(self): else: changed_keys_values.append((key, value)) + self.nrows_catalog_table = len(changed_keys_values) + self._set_current_location_label() + node_offset = self._rows_per_page * self._current_page # Fetch a page of keys. items = changed_keys_values[ @@ -565,9 +581,14 @@ def _set_search_by(self, selection=None): referenced in the metadata. """ if selection is not None: - self.searchBy_selection = selection.lower().replace(' ', '_') + if 'raw' in self.node_path and 'raw' != self.node_path[-1]: + self.searchBy_selection = selection.lower().replace(' ', '_') + self.node_path = self.node_path[:-1] + else: + self.searchBy_selection = selection.lower().replace(' ', '_') + self.node_path = self.node_path self._rebuild() - return self.searchBy_selection + return self.searchBy_selection, self.node_path def _get_search_by(self): """"Retrieves user selected search by method in form that can be searched in metadata.""" @@ -583,9 +604,18 @@ def _search_text_changed(self): """Updates Catalog Table if in 'Raw' directory and search entry is changed.""" current_text = self.search_entry.text() if current_text != self.previous_search_text: - if 'raw' in self.node_path: # TODO: (Maybe) add second conditional. - # Don't want to rebuild table if scan is selected - self._rebuild_table() + if 'raw' in self.node_path: + if 'raw' != self.node_path[-1]: + self.node_path = self.node_path[:-1] + self._rebuild_table() + else: + self._rebuild_table() + + def _replace_last_node(self, new_node_id): + """Replaces the last node in the current path with new selected node.""" + if self.node_path: + self.node_path = self.node_path[:-1] + (new_node_id,) + return self.node_path def _on_prev_page_clicked(self): if self._current_page != 0: @@ -615,12 +645,15 @@ def _on_last_page_clicked(self): break def _set_current_location_label(self): + if not self.node_path: + self.nrows_catalog_table = self.nrows_catalog_table + 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()), + self.nrows_catalog_table, ) - current_location_text = f"{starting_index}-{ending_index} of {len(self.get_current_node())}" + current_location_text = f"{starting_index}-{ending_index} of {self.nrows_catalog_table}" self.current_location_label.setText(current_location_text) def _addClicked(self, emit=True): From c1e3861b3a7f716ccc809f75820b07f6cc0bf57b Mon Sep 17 00:00:00 2001 From: Joshua Lohmolder Date: Thu, 8 Aug 2024 13:49:12 -0400 Subject: [PATCH 22/26] Can change rows per page after scan is selected now. --- PyMca5/PyMcaGui/io/QTiledWidget.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/PyMca5/PyMcaGui/io/QTiledWidget.py b/PyMca5/PyMcaGui/io/QTiledWidget.py index 22cc28565..b93bb9b31 100644 --- a/PyMca5/PyMcaGui/io/QTiledWidget.py +++ b/PyMca5/PyMcaGui/io/QTiledWidget.py @@ -343,6 +343,10 @@ def _on_load(self): self.open_node(item.text()) def _on_rows_per_page_changed(self, value): + # If scan already selected + if 'raw' in self.node_path and 'raw' != self.node_path[-1]: + self.node_path = self.node_path[:-1] + self._rows_per_page = int(value) self._current_page = 0 self._rebuild_table() From 970a204d041692d380ed1acbdf26d8fe6bafdca0 Mon Sep 17 00:00:00 2001 From: Joshua Lohmolder Date: Fri, 16 Aug 2024 14:06:21 -0400 Subject: [PATCH 23/26] Recent changes to Tiled Browser, TiledDataSource, and QDataSource. TiledDataSource needs to be completed to plot scans. --- PyMca5/PyMcaCore/TiledDataSource.py | 146 +++++++++++++-------------- PyMca5/PyMcaGui/io/QTiledWidget.py | 12 +-- PyMca5/PyMcaGui/pymca/QDataSource.py | 3 + 3 files changed, 76 insertions(+), 85 deletions(-) diff --git a/PyMca5/PyMcaCore/TiledDataSource.py b/PyMca5/PyMcaCore/TiledDataSource.py index b79a417de..a247ff28f 100644 --- a/PyMca5/PyMcaCore/TiledDataSource.py +++ b/PyMca5/PyMcaCore/TiledDataSource.py @@ -1,16 +1,13 @@ -import logging import numpy as np -import os import time import sys from PyMca5.PyMcaCore import DataObject -from PyMca5.PyMcaIO import TiledFile +from PyMca5.PyMcaGui.io.TiledDataChannelTable import TiledDataChannelTable +from PyMca5.PyMcaGui.io.QTiledWidget import TiledBrowser SOURCE_TYPE = 'Tiled' -_logger = logging.getLogger(__name__) - class TiledDataSource(object): """ Creates instance of a Tiled Data Source class. This is neccesary @@ -31,89 +28,82 @@ def __init__(self, nameInput): self.sourceName = nameList self.source_type = SOURCE_TYPE self.__sourceNameList = self.sourceName - self._sourceObjectList = [] self.refresh() def refresh(self): - self._sourceObjectList = [] - for name in self.__sourceNameList: - self._sourceObjectList.append(TiledFile.TiledFile(name)) - self.__lastKeyInfo = {} - - def getSourceInfo(self): - """ - Returns a dictionary with the key "KeyList" (list of all available keys - in this source). Each element in "KeyList" has the form 'n1.n2' where - n1 is the source number and n2 entry number in file both starting at 1. - """ - return self.__getSourceInfo() - - def __getSourceInfo(self): - SourceInfo = {} - SourceInfo["SourceType"] = SOURCE_TYPE - SourceInfo["KeyList"] = [] - i = 0 - for sourceObject in self._sourceObjectList: - i += 1 - nEntries = len(sourceObject["/"].keys()) - for n in range(nEntries): - SourceInfo["KeyList"].append("%d.%d" % (i,n+1)) - SourceInfo["Size"] = len(SourceInfo["KeyList"]) - return SourceInfo - - def getKeyInfo(self, key): - if key in self.getSourceInfo()['KeyList']: - return self.__getKeyInfo(key) - else: - #should we raise a KeyError? - _logger.debug("Error key not in list ") - return {} - - def __getKeyInfo(self, key): - try: - index, entry = key.split(".") - index = int(index) - 1 - entry = int(entry) - 1 - except Exception: - #should we rise an error? - _logger.debug("Error trying to interpret key = %s", key) - return {} + pass + + + def _set_key(self): + """Sets key once a scan has been selected in Tiled Browser.""" - sourceObject = self._sourceObjectList[index] + selection = TiledBrowser.set_data_source_key() + key = { + "scan": selection.metadata['start']['uid'], + "scan_id": selection.metadata['start']['scan_id'], + "streams": list(selection), + "selection": selection, + } + + return key + + def _set_data_channel_selection(self): + """Retrieve Data Channel Selections from Tiled Data Channel Table.""" - info = { + channel_sel = TiledDataChannelTable.getChannelSelection() + self.chan_sel = { + 'x': channel_sel['x'], + 'y': channel_sel['y'], + 'm': channel_sel['m'], + 'Channel List': channel_sel['Data Channel List'], + } + + def _get_key_info(self, selection): + """Retrives key info.""" + + key = self._set_key() + key_info = { "SourceType": SOURCE_TYPE, - "SourceName": self.sourceName[index], - "Key": key, - "FileName": sourceObject.name, + "selection": selection, + "key": key, } - return info + return key_info - def getDataObject(self, key, selection=None): - data = DataObject.DataObject() - index, entry = key.split(".") - index = int(index) - 1 - entry = int(entry) - 1 - - data.info = self.__getKeyInfo(key) - data.info['selection'] = selection - - sourceObject = self._sourceObjectList[index] - data.data = sourceObject.getData() - - def isUpdated(self, sourceName, key): - #sourceName is redundant? - index, entry = key.split(".") - index = int(index)-1 - lastmodified = os.path.getmtime(self.__sourceNameList[index]) - if lastmodified != self.__lastKeyInfo[key]: - self.__lastKeyInfo[key] = lastmodified - return True - else: - return False + def get_data_object(self, key, selection=None): + """Generate a dataObject that will be used to plot scan data.""" + + dataObject = DataObject.DataObject() + dataObject.info = self._get_key_info(selection) + dataObject.data = key['selection'] -source_types = { SOURCE_TYPE: TiledDataSource} + chan_sel = self.chan_sel + + # What data.info attributes to add? + dataObject.info['selection'] = selection + + # data = [key['selection']][datachannel][data] + # If data channel selected in x axis go to data and time + dataObject.data.x = dataObject.data.chan_sel['x']['data']['time'] + # If data channel selected in y axis plot everything + dataObject.data.y = dataObject.data.chan_sel['y']['data'] + # If data selected in m divide y by m and plot + dataObject.data.m = dataObject.data.chan_sel['m']['data'] + + return dataObject + + def isUpdated(self,key): + pass + + +def _is_Tiled_Source(filename): + try: + if hasattr(filename, self.node_path): + return True + except Exception: + return False + +source_types = {SOURCE_TYPE: TiledDataSource} def DataSource(name="", source_type=SOURCE_TYPE): try: diff --git a/PyMca5/PyMcaGui/io/QTiledWidget.py b/PyMca5/PyMcaGui/io/QTiledWidget.py index b93bb9b31..9e39ff7ee 100644 --- a/PyMca5/PyMcaGui/io/QTiledWidget.py +++ b/PyMca5/PyMcaGui/io/QTiledWidget.py @@ -268,7 +268,7 @@ def _on_connect_clicked(self): self.connection_label.setText(f"Connected to {url}") self.set_root(root) - def setDataSource(self, filedata): + def setData(self, filedata): self.data = filedata self.refreshData() @@ -277,6 +277,10 @@ def refreshData(self): def clearData(self): self.data = None + + def set_data_source_key(self): + if 'raw' in self.node_path and 'raw' != self.node_path[-1]: + return self.root[self.node_path] def set_root(self, root): self.root = root @@ -567,12 +571,6 @@ def _rebuild_table(self): self._clear_metadata() self.catalog_table.blockSignals(prev_block) - def _add_line_catalog_table(self, row_index, icon, key): - """Add line to catalog table.""" - self.catalog_table.insertRow(row_index) - self.catalog_table.setItem(row_index, 0, QTableWidgetItem(icon, key)) - row_index += 1 - def _rebuild(self): self._rebuild_table() self._rebuild_current_path_layout() diff --git a/PyMca5/PyMcaGui/pymca/QDataSource.py b/PyMca5/PyMcaGui/pymca/QDataSource.py index 1c4cb1eb7..d054a5f69 100644 --- a/PyMca5/PyMcaGui/pymca/QDataSource.py +++ b/PyMca5/PyMcaGui/pymca/QDataSource.py @@ -89,6 +89,9 @@ def getSourceType(sourceName0): # wrapped as SpecFile return SpecFileDataSource.SOURCE_TYPE + if TiledDataSource._is_Tiled_Source(sourceName): + return TiledDataSource.SOURCE_TYPE + if sps is not None: if sourceName in sps.getspeclist(): return QSpsDataSource.SOURCE_TYPE From f9004277140ed651122fc1ddaddd977a552f1466 Mon Sep 17 00:00:00 2001 From: Abigail Giles Date: Fri, 30 Aug 2024 15:50:06 -0400 Subject: [PATCH 24/26] WIP using TiledDataSource --- PyMca5/PyMcaCore/TiledDataSource.py | 7 +++-- PyMca5/PyMcaGui/io/QSourceSelector.py | 7 ++++- PyMca5/PyMcaGui/io/QTiledWidget.py | 42 ++++++++++++++++++++++++++- PyMca5/PyMcaGui/pymca/QDataSource.py | 5 ++++ PyMca5/PyMcaGui/pymca/QDispatcher.py | 3 ++ 5 files changed, 59 insertions(+), 5 deletions(-) diff --git a/PyMca5/PyMcaCore/TiledDataSource.py b/PyMca5/PyMcaCore/TiledDataSource.py index a247ff28f..f041c614b 100644 --- a/PyMca5/PyMcaCore/TiledDataSource.py +++ b/PyMca5/PyMcaCore/TiledDataSource.py @@ -21,12 +21,13 @@ class TiledDataSource(object): """ def __init__(self, nameInput): + print("-------- TiledDataSource init") if isinstance(nameInput, list): nameList = nameInput else: nameList = [nameInput] self.sourceName = nameList - self.source_type = SOURCE_TYPE + self.sourceType = SOURCE_TYPE self.__sourceNameList = self.sourceName self.refresh() @@ -49,7 +50,7 @@ def _set_key(self): def _set_data_channel_selection(self): """Retrieve Data Channel Selections from Tiled Data Channel Table.""" - + print("-------- TiledDataSource _set_data_channel_selection") channel_sel = TiledDataChannelTable.getChannelSelection() self.chan_sel = { 'x': channel_sel['x'], @@ -72,7 +73,7 @@ def _get_key_info(self, selection): def get_data_object(self, key, selection=None): """Generate a dataObject that will be used to plot scan data.""" - + print("-------- TiledDataSource get_data_object") dataObject = DataObject.DataObject() dataObject.info = self._get_key_info(selection) dataObject.data = key['selection'] diff --git a/PyMca5/PyMcaGui/io/QSourceSelector.py b/PyMca5/PyMcaGui/io/QSourceSelector.py index d5d9a8b93..3e7a4d726 100644 --- a/PyMca5/PyMcaGui/io/QSourceSelector.py +++ b/PyMca5/PyMcaGui/io/QSourceSelector.py @@ -342,7 +342,12 @@ def openSpec(self): def tiledConnection(self): """When 'bluesky' icon clicked it opens the TiledBrowser in the Tiled Tab""" - ddict = {"event": "Open Tiled Tab"} + # ddict = {"event": "Open Tiled Tab"} + url = "https://tiled-demo.blueskyproject.io/api" + ddict = { + "event": "NewSourceSelected", + "sourcelist": url, + } self.sigSourceSelectorSignal.emit(ddict) # Potentially add a authorization window when clicked diff --git a/PyMca5/PyMcaGui/io/QTiledWidget.py b/PyMca5/PyMcaGui/io/QTiledWidget.py index 9e39ff7ee..5320723f6 100644 --- a/PyMca5/PyMcaGui/io/QTiledWidget.py +++ b/PyMca5/PyMcaGui/io/QTiledWidget.py @@ -26,7 +26,7 @@ QWidget, ) from PyMca5.PyMcaGui import PyMcaQt as qt -from PyMca5.PyMcaGui.io import TiledDataChannelTable +from PyMca5.PyMcaGui.io import TiledDataChannelTable, QSourceSelector from tiled.client import from_uri from tiled.client.array import DaskArrayClient @@ -267,6 +267,46 @@ def _on_connect_clicked(self): else: self.connection_label.setText(f"Connected to {url}") self.set_root(root) + # sigSourceSelectorSignal.emit( + # { + # "event": "NewSourceSelected", + # "sourcelist": url, + # } + # ) + + def setDataSource(self, data): + self.data = data + # self.data.sigUpdated.connect(self._update) + + dataObject = self._getDataObject() + # self.graphWidget.setImageData(dataObject.data) + self.lastDataObject = dataObject + + def _update(self, ddict): + # targetwidgetid = ddict.get('targetwidgetid', None) + # if targetwidgetid not in [None, id(self)]: + # return + # dataObject = self._getDataObject(ddict['Key'], + # selection=None) + # if dataObject is not None: + # self.graphWidget.setImageData(dataObject.data) + # self.lastDataObject = dataObject + ... + + def _getDataObject(self, key=None, selection=None): + if key is None: + # key = self.info['Key'] + print('deal with later') + dataObject = self.data.getDataObject(key, + selection=None, + poll=False) + # if dataObject is not None: + # dataObject.info['legend'] = self.info['Key'] + # dataObject.info['imageselection'] = False + # dataObject.info['scanselection'] = False + # dataObject.info['targetwidgetid'] = id(self) + # self.data.addToPoller(dataObject) + return dataObject def setData(self, filedata): self.data = filedata diff --git a/PyMca5/PyMcaGui/pymca/QDataSource.py b/PyMca5/PyMcaGui/pymca/QDataSource.py index d054a5f69..d9a54de04 100644 --- a/PyMca5/PyMcaGui/pymca/QDataSource.py +++ b/PyMca5/PyMcaGui/pymca/QDataSource.py @@ -161,6 +161,11 @@ def getSourceType(sourceName0): except Exception: pass return SpecFileDataSource.SOURCE_TYPE + elif sourceName.startswith("tiled:") or \ + sourceName.startswith(r"http:/") or \ + sourceName.startswith(r"https:/"): + # only chance is to use silx via an h5py-like API + return TiledDataSource.SOURCE_TYPE else: return QSpsDataSource.SOURCE_TYPE diff --git a/PyMca5/PyMcaGui/pymca/QDispatcher.py b/PyMca5/PyMcaGui/pymca/QDispatcher.py index 752f62483..5d826a8e7 100644 --- a/PyMca5/PyMcaGui/pymca/QDispatcher.py +++ b/PyMca5/PyMcaGui/pymca/QDispatcher.py @@ -75,6 +75,7 @@ def __init__(self, parent=None, pluginsIcon=False): #for the time being just files for src_widget in QDataSource.source_widgets.keys(): + print(f"{QDataSource.source_widgets = }") self.selectorWidget[src_widget] = QDataSource.source_widgets[src_widget]() self.tabWidget.addTab(self.selectorWidget[src_widget], src_widget) self.selectorWidget[src_widget].sigAddSelection.connect( \ @@ -253,6 +254,8 @@ def _sourceSelectorSlot(self, ddict): source = QDataSource.QDataSource(ddict["sourcelist"]) self.sourceList.append(source) sourceType = source.sourceType + print(f"{sourceType = }") + print(f"{source = }") self.selectorWidget[sourceType].setDataSource(source) self.tabWidget.setCurrentWidget(self.selectorWidget[sourceType]) #if sourceType == "SPS": From dbc295d94012178f775823f502637aff1c401421 Mon Sep 17 00:00:00 2001 From: Abigail Giles Date: Tue, 3 Sep 2024 14:51:32 -0400 Subject: [PATCH 25/26] Rename TiledDataSource to QTiledDataSource --- .../{TiledDataSource.py => QTiledDataSource.py} | 17 ++++++++++------- PyMca5/PyMcaGui/pymca/QDataSource.py | 12 ++++++------ 2 files changed, 16 insertions(+), 13 deletions(-) rename PyMca5/PyMcaCore/{TiledDataSource.py => QTiledDataSource.py} (90%) diff --git a/PyMca5/PyMcaCore/TiledDataSource.py b/PyMca5/PyMcaCore/QTiledDataSource.py similarity index 90% rename from PyMca5/PyMcaCore/TiledDataSource.py rename to PyMca5/PyMcaCore/QTiledDataSource.py index f041c614b..78270b7a4 100644 --- a/PyMca5/PyMcaCore/TiledDataSource.py +++ b/PyMca5/PyMcaCore/QTiledDataSource.py @@ -5,10 +5,12 @@ from PyMca5.PyMcaCore import DataObject from PyMca5.PyMcaGui.io.TiledDataChannelTable import TiledDataChannelTable from PyMca5.PyMcaGui.io.QTiledWidget import TiledBrowser +from PyMca5.PyMcaGui.pymca import QSource + SOURCE_TYPE = 'Tiled' -class TiledDataSource(object): +class QTiledDataSource(QSource.QSource): """ Creates instance of a Tiled Data Source class. This is neccesary to create a Tiled Tab in the QDispatcher, which houses the Tiled @@ -21,7 +23,8 @@ class TiledDataSource(object): """ def __init__(self, nameInput): - print("-------- TiledDataSource init") + print("-------- QTiledDataSource init") + super().__init__() if isinstance(nameInput, list): nameList = nameInput else: @@ -50,7 +53,7 @@ def _set_key(self): def _set_data_channel_selection(self): """Retrieve Data Channel Selections from Tiled Data Channel Table.""" - print("-------- TiledDataSource _set_data_channel_selection") + print("-------- QTiledDataSource _set_data_channel_selection") channel_sel = TiledDataChannelTable.getChannelSelection() self.chan_sel = { 'x': channel_sel['x'], @@ -73,7 +76,7 @@ def _get_key_info(self, selection): def get_data_object(self, key, selection=None): """Generate a dataObject that will be used to plot scan data.""" - print("-------- TiledDataSource get_data_object") + print("-------- QTiledDataSource get_data_object") dataObject = DataObject.DataObject() dataObject.info = self._get_key_info(selection) dataObject.data = key['selection'] @@ -104,7 +107,7 @@ def _is_Tiled_Source(filename): except Exception: return False -source_types = {SOURCE_TYPE: TiledDataSource} +source_types = {SOURCE_TYPE: QTiledDataSource} def DataSource(name="", source_type=SOURCE_TYPE): try: @@ -121,10 +124,10 @@ def DataSource(name="", source_type=SOURCE_TYPE): sourcename = sys.argv[1] key = sys.argv[2] except Exception: - print("Usage: TiledDataSource ") + print("Usage: QTiledDataSource ") sys.exit() #one can use this: - obj = TiledDataSource(sourcename) + obj = QTiledDataSource(sourcename) #or this: obj = DataSource(sourcename) #data = obj.getData(key,selection={'pos':(10,10),'size':(40,40)}) diff --git a/PyMca5/PyMcaGui/pymca/QDataSource.py b/PyMca5/PyMcaGui/pymca/QDataSource.py index d9a54de04..000eb5388 100644 --- a/PyMca5/PyMcaGui/pymca/QDataSource.py +++ b/PyMca5/PyMcaGui/pymca/QDataSource.py @@ -38,7 +38,7 @@ from PyMca5.PyMcaCore import SpecFileDataSource from PyMca5.PyMcaCore import EdfFileDataSource -from PyMca5.PyMcaCore import TiledDataSource +from PyMca5.PyMcaCore import QTiledDataSource from PyMca5.PyMcaIO import BlissSpecFile from PyMca5.PyMcaGui.io import QEdfFileWidget from PyMca5.PyMcaGui.io import QSpecFileWidget @@ -58,12 +58,12 @@ source_types = { SpecFileDataSource.SOURCE_TYPE: SpecFileDataSource.SpecFileDataSource, EdfFileDataSource.SOURCE_TYPE: EdfFileDataSource.EdfFileDataSource, QSpsDataSource.SOURCE_TYPE: QSpsDataSource.QSpsDataSource, - TiledDataSource.SOURCE_TYPE: TiledDataSource.TiledDataSource} + QTiledDataSource.SOURCE_TYPE: QTiledDataSource.QTiledDataSource} source_widgets = { SpecFileDataSource.SOURCE_TYPE: QSpecFileWidget.QSpecFileWidget, EdfFileDataSource.SOURCE_TYPE: QEdfFileWidget.QEdfFileWidget, QSpsDataSource.SOURCE_TYPE: QSpsWidget.QSpsWidget, - TiledDataSource.SOURCE_TYPE: QTiledWidget.TiledBrowser} + QTiledDataSource.SOURCE_TYPE: QTiledWidget.TiledBrowser} NEXUS = True try: @@ -89,8 +89,8 @@ def getSourceType(sourceName0): # wrapped as SpecFile return SpecFileDataSource.SOURCE_TYPE - if TiledDataSource._is_Tiled_Source(sourceName): - return TiledDataSource.SOURCE_TYPE + if QTiledDataSource._is_Tiled_Source(sourceName): + return QTiledDataSource.SOURCE_TYPE if sps is not None: if sourceName in sps.getspeclist(): @@ -165,7 +165,7 @@ def getSourceType(sourceName0): sourceName.startswith(r"http:/") or \ sourceName.startswith(r"https:/"): # only chance is to use silx via an h5py-like API - return TiledDataSource.SOURCE_TYPE + return QTiledDataSource.SOURCE_TYPE else: return QSpsDataSource.SOURCE_TYPE From fe433b815713810106f055d17a615b4732ea2b41 Mon Sep 17 00:00:00 2001 From: Abigail Giles Date: Thu, 5 Sep 2024 12:44:11 -0400 Subject: [PATCH 26/26] Plotting not working but no errors --- PyMca5/PyMcaCore/QTiledDataSource.py | 17 +++++++++---- PyMca5/PyMcaGui/io/QTiledWidget.py | 36 ++++++++++++++++------------ PyMca5/PyMcaGui/pymca/QDispatcher.py | 3 --- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/PyMca5/PyMcaCore/QTiledDataSource.py b/PyMca5/PyMcaCore/QTiledDataSource.py index 78270b7a4..8177d196d 100644 --- a/PyMca5/PyMcaCore/QTiledDataSource.py +++ b/PyMca5/PyMcaCore/QTiledDataSource.py @@ -36,12 +36,21 @@ def __init__(self, nameInput): def refresh(self): pass - - def _set_key(self): + def getDataObject(self, key_list, selection=None, poll=False): + if type(key_list) not in [type([])]: + nolist = True + key_list = [key_list] + else: + output = [] + nolist = False + data = self.get_data_object(key_list, selection=selection) + + return data + + def _set_key(self, selection=None): """Sets key once a scan has been selected in Tiled Browser.""" - selection = TiledBrowser.set_data_source_key() key = { "scan": selection.metadata['start']['uid'], "scan_id": selection.metadata['start']['scan_id'], @@ -65,7 +74,7 @@ def _set_data_channel_selection(self): def _get_key_info(self, selection): """Retrives key info.""" - key = self._set_key() + key = self._set_key(selection=selection) key_info = { "SourceType": SOURCE_TYPE, "selection": selection, diff --git a/PyMca5/PyMcaGui/io/QTiledWidget.py b/PyMca5/PyMcaGui/io/QTiledWidget.py index 5320723f6..e3ea2aafe 100644 --- a/PyMca5/PyMcaGui/io/QTiledWidget.py +++ b/PyMca5/PyMcaGui/io/QTiledWidget.py @@ -252,6 +252,8 @@ def __init__(self, parent=None): self.previous_search_text = '' self.key_to_uid = {} + self.selection = None + def _on_connect_clicked(self): url = self.url_entry.displayText().strip() # url = "https://tiled-demo.blueskyproject.io/api" @@ -277,10 +279,12 @@ def _on_connect_clicked(self): def setDataSource(self, data): self.data = data # self.data.sigUpdated.connect(self._update) + selection = self.set_data_source_key() - dataObject = self._getDataObject() - # self.graphWidget.setImageData(dataObject.data) - self.lastDataObject = dataObject + if selection is not None: + dataObject = self._getDataObject(selection=selection) + # self.graphWidget.setImageData(dataObject.data) + self.lastDataObject = dataObject def _update(self, ddict): # targetwidgetid = ddict.get('targetwidgetid', None) @@ -308,8 +312,8 @@ def _getDataObject(self, key=None, selection=None): # self.data.addToPoller(dataObject) return dataObject - def setData(self, filedata): - self.data = filedata + def setData(self, node): + self.data = node self.refreshData() def refreshData(self): @@ -320,7 +324,8 @@ def clearData(self): def set_data_source_key(self): if 'raw' in self.node_path and 'raw' != self.node_path[-1]: - return self.root[self.node_path] + self.selection = self.root[self.node_path] + return self.selection def set_root(self, root): self.root = root @@ -344,15 +349,16 @@ def get_node(self, node_path): return self.root def enter_node(self, node_id): - if 'raw' in self.node_path: - self.node_path += (node_id,) + print(f"{self.node_path = }") + print(f"{node_id = }") + self.node_path += (node_id,) + self._current_page = 0 + self._rebuild() + # avoid populating data channels table if not in a scan + if 'raw' in self.node_path and self.node_path[-1] != 'raw': rawDataChannels = tuple(self.get_current_node().items()) dataChannels = [channel[0] for channel in rawDataChannels] self.data_channels_table.build_table(dataChannels) - else: - self.node_path += (node_id,) - self._current_page = 0 - self._rebuild() def exit_node(self): self.node_path = self.node_path[:-1] @@ -388,8 +394,8 @@ def _on_load(self): def _on_rows_per_page_changed(self, value): # If scan already selected - if 'raw' in self.node_path and 'raw' != self.node_path[-1]: - self.node_path = self.node_path[:-1] + # if 'raw' in self.node_path and 'raw' != self.node_path[-1]: + # self.node_path = self.node_path[:-1] self._rows_per_page = int(value) self._current_page = 0 @@ -707,7 +713,7 @@ def _addClicked(self, emit=True): if len(channel_sel['y']): # TODO: find was to give self.data a SourceName method. sel = { - 'SourceName': self.data.SourceName, + 'SourceName': self.data.sourceName, 'SourceType': self.data.sourceType, 'selection': {'x': channel_sel['x'], 'y': channel_sel['y'], diff --git a/PyMca5/PyMcaGui/pymca/QDispatcher.py b/PyMca5/PyMcaGui/pymca/QDispatcher.py index 5d826a8e7..752f62483 100644 --- a/PyMca5/PyMcaGui/pymca/QDispatcher.py +++ b/PyMca5/PyMcaGui/pymca/QDispatcher.py @@ -75,7 +75,6 @@ def __init__(self, parent=None, pluginsIcon=False): #for the time being just files for src_widget in QDataSource.source_widgets.keys(): - print(f"{QDataSource.source_widgets = }") self.selectorWidget[src_widget] = QDataSource.source_widgets[src_widget]() self.tabWidget.addTab(self.selectorWidget[src_widget], src_widget) self.selectorWidget[src_widget].sigAddSelection.connect( \ @@ -254,8 +253,6 @@ def _sourceSelectorSlot(self, ddict): source = QDataSource.QDataSource(ddict["sourcelist"]) self.sourceList.append(source) sourceType = source.sourceType - print(f"{sourceType = }") - print(f"{source = }") self.selectorWidget[sourceType].setDataSource(source) self.tabWidget.setCurrentWidget(self.selectorWidget[sourceType]) #if sourceType == "SPS":