From 83b071551aa024ac8f06d6a0e2dd02a902e6b284 Mon Sep 17 00:00:00 2001 From: luiz Date: Wed, 12 Oct 2022 18:47:09 +0200 Subject: [PATCH 1/8] add fsspec as stream mode --- nwbwidgets/panel.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/nwbwidgets/panel.py b/nwbwidgets/panel.py index bcbe53b8..d1ba8455 100644 --- a/nwbwidgets/panel.py +++ b/nwbwidgets/panel.py @@ -4,17 +4,20 @@ from pynwb import NWBHDF5IO from nwbwidgets import nwb2widget from dandi.dandiapi import DandiAPIClient +import h5py +import fsspec +from fsspec.implementations.cached import CachingFileSystem -# def panel(): - class Panel(widgets.VBox): - def __init__(self, children=None, **kwargs): + def __init__(self, children=None, stream_mode="fsspec", **kwargs): if children is None: children = list() super().__init__(children, **kwargs) + self.stream_mode = stream_mode + self.source_options_radio = widgets.RadioButtons(options=['dandi', 'local dir', 'local file'], value='dandi') self.source_options_label = widgets.Label('Source:') self.source_options = widgets.VBox( @@ -92,9 +95,25 @@ def stream_dandiset_file(args): with DandiAPIClient() as client: asset = client.get_dandiset(dandiset_id=self.source_path_text.value, version_id="draft").get_asset_by_path(self.source_file_dandi_dropdown.value) s3_url = asset.get_content_url(follow_redirects=1, strip_query=True) + + if self.stream_mode == "ros3": io = NWBHDF5IO(s3_url, mode='r', load_namespaces=True, driver='ros3') nwb = io.read() self.widgets_panel.children = [nwb2widget(nwb)] + + elif self.stream_mode == "fsspec": + # Create a virtual filesystem based on the http protocol and use caching to save accessed data to RAM. + fs = CachingFileSystem( + fs=fsspec.filesystem("http"), + cache_storage="nwb-cache", # Local folder for the cache + ) + + with fs.open(s3_url, "rb") as f: + with h5py.File(f) as file: + with NWBHDF5IO(file=file, load_namespaces=True) as io: + nwbfile = io.read() + self.widgets_panel.children = [nwb2widget(nwbfile)] + elif self.source_path_label.value == "Path to local dir:": full_file_path = str(Path(self.source_path_text.value) / self.source_file_dandi_dropdown.value) io = NWBHDF5IO(full_file_path, mode='r', load_namespaces=True) From dd0243979c61990fc1337466dabae0ef9bfe868d Mon Sep 17 00:00:00 2001 From: luiz Date: Mon, 17 Oct 2022 09:22:47 +0200 Subject: [PATCH 2/8] cache path option --- nwbwidgets/panel.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nwbwidgets/panel.py b/nwbwidgets/panel.py index d1ba8455..9d7a9c6d 100644 --- a/nwbwidgets/panel.py +++ b/nwbwidgets/panel.py @@ -11,12 +11,14 @@ class Panel(widgets.VBox): - def __init__(self, children=None, stream_mode="fsspec", **kwargs): + def __init__(self, children:list=None, stream_mode:str="fsspec", cache_path:str=None, **kwargs): if children is None: children = list() super().__init__(children, **kwargs) self.stream_mode = stream_mode + if cache_path is None: + cache_path = "nwb-cache" self.source_options_radio = widgets.RadioButtons(options=['dandi', 'local dir', 'local file'], value='dandi') self.source_options_label = widgets.Label('Source:') @@ -105,7 +107,7 @@ def stream_dandiset_file(args): # Create a virtual filesystem based on the http protocol and use caching to save accessed data to RAM. fs = CachingFileSystem( fs=fsspec.filesystem("http"), - cache_storage="nwb-cache", # Local folder for the cache + cache_storage=cache_path, # Local folder for the cache ) with fs.open(s3_url, "rb") as f: From 36d31d0c5f1a461081a1b9239631d589e58afaa8 Mon Sep 17 00:00:00 2001 From: luiz Date: Mon, 17 Oct 2022 09:39:49 +0200 Subject: [PATCH 3/8] clean code --- nwbwidgets/panel.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/nwbwidgets/panel.py b/nwbwidgets/panel.py index 9d7a9c6d..4f2b7232 100644 --- a/nwbwidgets/panel.py +++ b/nwbwidgets/panel.py @@ -100,8 +100,6 @@ def stream_dandiset_file(args): if self.stream_mode == "ros3": io = NWBHDF5IO(s3_url, mode='r', load_namespaces=True, driver='ros3') - nwb = io.read() - self.widgets_panel.children = [nwb2widget(nwb)] elif self.stream_mode == "fsspec": # Create a virtual filesystem based on the http protocol and use caching to save accessed data to RAM. @@ -109,12 +107,12 @@ def stream_dandiset_file(args): fs=fsspec.filesystem("http"), cache_storage=cache_path, # Local folder for the cache ) + f = fs.open(s3_url, "rb") + file = h5py.File(f) + io = NWBHDF5IO(file=file, load_namespaces=True) - with fs.open(s3_url, "rb") as f: - with h5py.File(f) as file: - with NWBHDF5IO(file=file, load_namespaces=True) as io: - nwbfile = io.read() - self.widgets_panel.children = [nwb2widget(nwbfile)] + nwbfile = io.read() + self.widgets_panel.children = [nwb2widget(nwbfile)] elif self.source_path_label.value == "Path to local dir:": full_file_path = str(Path(self.source_path_text.value) / self.source_file_dandi_dropdown.value) From ffdcc1c12c85126a95ae3c45d59c404866c54c27 Mon Sep 17 00:00:00 2001 From: luiz Date: Mon, 17 Oct 2022 09:42:37 +0200 Subject: [PATCH 4/8] req --- nwbwidgets/panel.py | 5 +++-- requirements.txt | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/nwbwidgets/panel.py b/nwbwidgets/panel.py index 4f2b7232..90416fba 100644 --- a/nwbwidgets/panel.py +++ b/nwbwidgets/panel.py @@ -5,8 +5,6 @@ from nwbwidgets import nwb2widget from dandi.dandiapi import DandiAPIClient import h5py -import fsspec -from fsspec.implementations.cached import CachingFileSystem class Panel(widgets.VBox): @@ -102,6 +100,9 @@ def stream_dandiset_file(args): io = NWBHDF5IO(s3_url, mode='r', load_namespaces=True, driver='ros3') elif self.stream_mode == "fsspec": + import fsspec + from fsspec.implementations.cached import CachingFileSystem + # Create a virtual filesystem based on the http protocol and use caching to save accessed data to RAM. fs = CachingFileSystem( fs=fsspec.filesystem("http"), diff --git a/requirements.txt b/requirements.txt index e75f2f28..c35ec1a7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,3 +17,4 @@ ndx-grayscalevolume trimesh dandi importlib-metadata <5.0 +fsspec \ No newline at end of file From fea98bd206314a26c63d9cda28a013bf02da89a2 Mon Sep 17 00:00:00 2001 From: luiz Date: Tue, 18 Oct 2022 17:54:44 +0200 Subject: [PATCH 5/8] qt app --- qt/README.md | 15 ++++ qt/__init__.py | 0 qt/application.py | 171 +++++++++++++++++++++++++++++++++++++++ qt/requirements.txt | 3 + qt/static/icon_dandi.png | Bin 0 -> 9450 bytes qt/static/icon_dandi.svg | 1 + qt/utils/__init__.py | 0 qt/utils/dandi.py | 33 ++++++++ 8 files changed, 223 insertions(+) create mode 100644 qt/README.md create mode 100644 qt/__init__.py create mode 100644 qt/application.py create mode 100644 qt/requirements.txt create mode 100644 qt/static/icon_dandi.png create mode 100644 qt/static/icon_dandi.svg create mode 100644 qt/utils/__init__.py create mode 100644 qt/utils/dandi.py diff --git a/qt/README.md b/qt/README.md new file mode 100644 index 00000000..0d3cb750 --- /dev/null +++ b/qt/README.md @@ -0,0 +1,15 @@ +## Running locally from source + +Install the dependencies and run the `application.py` script: +```bash +$ pip install -r requirements.txt +$ python application.py +``` + +## Packaging the GUI as executable + +Further reference and options: https://pyinstaller.org/en/stable/index.html + +```bash +$ pyinstaller application.py +``` \ No newline at end of file diff --git a/qt/__init__.py b/qt/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/qt/application.py b/qt/application.py new file mode 100644 index 00000000..ab4df9b9 --- /dev/null +++ b/qt/application.py @@ -0,0 +1,171 @@ +from PySide6.QtWidgets import ( + QApplication, + QMainWindow, + QPushButton, + QWidget, + QVBoxLayout, + QHBoxLayout, + QComboBox, + QStyle, + QTextBrowser +) +from PySide6.QtCore import Qt +from qtvoila import QtVoila +import sys +import webbrowser + +from utils.dandi import ( + get_all_dandisets_metadata, + get_dandiset_metadata, + list_dandiset_files, + get_file_url +) + + +class MyApp(QMainWindow): + def __init__(self, parent=None): + super().__init__(parent) + self.resize(800, 800) + self.setWindowTitle('Desktop DANDI Explorer') + + try: + self.all_dandisets_metadata = get_all_dandisets_metadata() + except BaseException as e: + self.all_dandisets_metadata = list() + print("Failed to fetch DANDI archive datasets: ", e) + + # Voila widget + self.voila_widget = QtVoila( + parent=self, + strip_sources=True, + ) + + # Select source + self.source_choice = QComboBox() + self.source_choice.currentTextChanged.connect(self.change_data_source) + # self.source_choice.addItem(QIcon(':/static/icon_dandi.svg'), "DANDI") + self.source_choice.addItem("DANDI archive") + self.source_choice.addItem("Local dir") + self.source_choice.addItem("Local file") + self.source_choice.model().item(1).setEnabled(False) + self.source_choice.model().item(2).setEnabled(False) + + # Select dandi set + self.dandiset_choice = QComboBox() + for m in self.all_dandisets_metadata: + item_name = m.id.split(":")[1].split("/")[0] + " - " + m.name + self.dandiset_choice.addItem(item_name) + self.dandiset_choice.view().setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) + + self.accept_dandiset_choice = QPushButton() + icon_1 = self.style().standardIcon(QStyle.SP_ArrowDown) + self.accept_dandiset_choice.setIcon(icon_1) + self.accept_dandiset_choice.setToolTip("Read DANDI set") + self.accept_dandiset_choice.clicked.connect(self.list_dandiset_files) + + self.open_dandiset_choice = QPushButton() + icon_2 = self.style().standardIcon(QStyle.SP_ComputerIcon) + self.open_dandiset_choice.setIcon(icon_2) + self.open_dandiset_choice.setToolTip("Open in DANDI Archive") + self.open_dandiset_choice.clicked.connect(self.open_webbrowser) + + self.hbox1 = QHBoxLayout() + self.hbox1.addWidget(self.source_choice, stretch=0) + self.hbox1.addWidget(self.dandiset_choice, stretch=1) + self.hbox1.addWidget(self.accept_dandiset_choice, stretch=0) + self.hbox1.addWidget(self.open_dandiset_choice, stretch=0) + self.hbox1_w = QWidget() + self.hbox1_w.setLayout(self.hbox1) + + # Summary info + self.info_summary = QTextBrowser() + self.info_summary.setOpenExternalLinks(True) + self.info_summary.setStyleSheet("font-size: 14px; background: rgba(0,0,0,0%);") + self.info_summary.setFixedHeight(100) + + # Select file + self.file_choice = QComboBox() + self.file_choice.view().setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) + self.accept_file_choice = QPushButton() + icon_3 = self.style().standardIcon(QStyle.SP_FileDialogContentsView) + self.accept_file_choice.setIcon(icon_3) + self.accept_file_choice.setToolTip("Visualize NWB file") + self.accept_file_choice.clicked.connect(self.pass_code_to_voila_widget) + + self.hbox2 = QHBoxLayout() + self.hbox2.addWidget(self.file_choice, stretch=1) + self.hbox2.addWidget(self.accept_file_choice, stretch=0) + self.hbox2_w = QWidget() + self.hbox2_w.setLayout(self.hbox2) + + layout = QVBoxLayout() + layout.addWidget(self.hbox1_w, stretch=0) + layout.addWidget(self.info_summary, stretch=0) + layout.addWidget(self.hbox2_w, stretch=0) + layout.addWidget(self.voila_widget, stretch=1) + + self.main_widget = QWidget(self) + self.main_widget.setLayout(layout) + self.setCentralWidget(self.main_widget) + self.show() + + + def change_data_source(self, value): + if value == "DANDI archive": + print("CHANGED: ", value) + elif value == "Local dir": + print("CHANGED: ", value) + elif value == "Local file": + print("CHANGED: ", value) + + + def open_webbrowser(self): + dandiset_id = self.dandiset_choice.currentText().split("-")[0].strip() + metadata = get_dandiset_metadata(dandiset_id=dandiset_id) + webbrowser.open(metadata.url) + + + def list_dandiset_files(self): + self.file_choice.clear() + dandiset_id = self.dandiset_choice.currentText().split("-")[0].strip() + self.info_summary.clear() + metadata = get_dandiset_metadata(dandiset_id=dandiset_id) + self.info_summary.append(metadata.description) + all_files = list_dandiset_files(dandiset_id=dandiset_id) + for f in all_files: + self.file_choice.addItem(f) + + + def pass_code_to_voila_widget(self): + self.voila_widget.external_notebook = None + self.voila_widget.clear() + file_url = get_file_url( + dandiset_id=self.dandiset_choice.currentText().split("-")[0].strip(), + file_path=self.file_choice.currentText().strip() + ) + code1 = f"""import fsspec +import pynwb +import h5py +from fsspec.implementations.cached import CachingFileSystem +from nwbwidgets import nwb2widget + +fs = CachingFileSystem( + fs=fsspec.filesystem("http"), + cache_storage="nwb-cache", # Local folder for the cache +) + +# next, open the file +f = fs.open('{file_url}', "rb") +file = h5py.File(f) +io = pynwb.NWBHDF5IO(file=file, load_namespaces=True) +nwbfile = io.read() +nwb2widget(nwbfile)""" + self.voila_widget.add_notebook_cell(code=code1, cell_type='code') + # Run Voila + self.voila_widget.run_voila() + + +if __name__ == '__main__': + app = QApplication(sys.argv) + my_app = MyApp() + sys.exit(app.exec()) diff --git a/qt/requirements.txt b/qt/requirements.txt new file mode 100644 index 00000000..75d52218 --- /dev/null +++ b/qt/requirements.txt @@ -0,0 +1,3 @@ +qtvoila +PySide6 +nwbwidgets \ No newline at end of file diff --git a/qt/static/icon_dandi.png b/qt/static/icon_dandi.png new file mode 100644 index 0000000000000000000000000000000000000000..2f699c6bfcab0792f88a6f92cf4a0f1552dfb407 GIT binary patch literal 9450 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 literal 0 HcmV?d00001 diff --git a/qt/static/icon_dandi.svg b/qt/static/icon_dandi.svg new file mode 100644 index 00000000..daad0ead --- /dev/null +++ b/qt/static/icon_dandi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/qt/utils/__init__.py b/qt/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/qt/utils/dandi.py b/qt/utils/dandi.py new file mode 100644 index 00000000..1d3fc9a0 --- /dev/null +++ b/qt/utils/dandi.py @@ -0,0 +1,33 @@ +from dandi.dandiapi import DandiAPIClient + + +def get_all_dandisets_metadata(): + with DandiAPIClient() as client: + all_metadata = list() + for ii, m in enumerate(client.get_dandisets()): + if ii > 150 and ii < 160: + try: + all_metadata.append(m.get_metadata()) + except: + pass + else: + pass + return all_metadata + + +def get_dandiset_metadata(dandiset_id: str): + with DandiAPIClient() as client: + dandiset = client.get_dandiset(dandiset_id=dandiset_id, version_id="draft") + return dandiset.get_metadata() + + +def list_dandiset_files(dandiset_id: str): + with DandiAPIClient() as client: + dandiset = client.get_dandiset(dandiset_id=dandiset_id, version_id="draft") + return [i.dict().get("path") for i in dandiset.get_assets()] + + +def get_file_url(dandiset_id:str, file_path: str): + with DandiAPIClient() as client: + asset = client.get_dandiset(dandiset_id, 'draft').get_asset_by_path(file_path) + return asset.get_content_url(follow_redirects=1, strip_query=True) \ No newline at end of file From b7a2c32b2eb5469363d347b1893cfbf9b51cc683 Mon Sep 17 00:00:00 2001 From: luiz Date: Thu, 20 Oct 2022 21:03:52 +0200 Subject: [PATCH 6/8] local dir and file browser --- qt/application.py | 220 +++++++++++++++++++++++++++++++++------------- 1 file changed, 161 insertions(+), 59 deletions(-) diff --git a/qt/application.py b/qt/application.py index ab4df9b9..26a56e65 100644 --- a/qt/application.py +++ b/qt/application.py @@ -7,12 +7,15 @@ QHBoxLayout, QComboBox, QStyle, - QTextBrowser + QTextBrowser, + QLineEdit, + QFileDialog ) from PySide6.QtCore import Qt from qtvoila import QtVoila import sys import webbrowser +from pathlib import Path from utils.dandi import ( get_all_dandisets_metadata, @@ -44,38 +47,127 @@ def __init__(self, parent=None): self.source_choice = QComboBox() self.source_choice.currentTextChanged.connect(self.change_data_source) # self.source_choice.addItem(QIcon(':/static/icon_dandi.svg'), "DANDI") - self.source_choice.addItem("DANDI archive") self.source_choice.addItem("Local dir") self.source_choice.addItem("Local file") - self.source_choice.model().item(1).setEnabled(False) - self.source_choice.model().item(2).setEnabled(False) + self.source_choice.addItem("DANDI archive") + + # Main Layout + self.layout = QVBoxLayout() + self.layout.addWidget(self.source_choice, stretch=0) + self.layout.addWidget(self.voila_widget, stretch=1) + + self.create_folder_layout() + + self.main_widget = QWidget(self) + self.main_widget.setLayout(self.layout) + self.setCentralWidget(self.main_widget) + self.show() + + + def change_data_source(self, value): + self.delete_item_from_layout() + if value == "DANDI archive": + self.create_dandi_layout() + elif value == "Local dir": + self.create_folder_layout() + elif value == "Local file": + self.create_file_layout() + + + def delete_item_from_layout(self): + # Ref: https://stackoverflow.com/a/9899475/11483674 + child = self.layout.takeAt(1) + child.widget().deleteLater() + + + def browser_local_folder(self): + folder_path = QFileDialog.getExistingDirectory(parent=self, caption='Open folder', dir=str(Path.home())) + if folder_path: + for f in Path(folder_path).glob("*.nwb"): + self.all_folder_files.addItem(str(f)) + + + def browser_local_file(self): + filename, filter = QFileDialog.getOpenFileName(parent=self, caption='Open file', dir=str(Path.home()), filter='NWB Files (*.nwb)') + if filename: + self.chosen_file.setText(filename) + + + def create_folder_layout(self): + browser_folder_button = QPushButton() + icon = self.style().standardIcon(QStyle.SP_DialogOpenButton) + browser_folder_button.setIcon(icon) + browser_folder_button.setToolTip("Browser local dir") + browser_folder_button.clicked.connect(self.browser_local_folder) + + self.all_folder_files = QComboBox() + + accept_folder_file_button = QPushButton() + icon_2 = self.style().standardIcon(QStyle.SP_FileDialogContentsView) + accept_folder_file_button.setIcon(icon_2) + accept_folder_file_button.setToolTip("Visualize NWB file") + accept_folder_file_button.clicked.connect(self.pass_code_to_voila_widget) + + hbox = QHBoxLayout() + hbox.addWidget(browser_folder_button, stretch=0) + hbox.addWidget(self.all_folder_files, stretch=1) + hbox.addWidget(accept_folder_file_button, stretch=0) + w = QWidget() + w.setLayout(hbox) + + self.layout.insertWidget(1, w) + + + def create_file_layout(self): + browser_file_button = QPushButton() + icon = self.style().standardIcon(QStyle.SP_DialogOpenButton) + browser_file_button.setIcon(icon) + browser_file_button.setToolTip("Browser local dir") + browser_file_button.clicked.connect(self.browser_local_file) - # Select dandi set + self.chosen_file = QLineEdit() + + accept_file_button = QPushButton() + icon_2 = self.style().standardIcon(QStyle.SP_FileDialogContentsView) + accept_file_button.setIcon(icon_2) + accept_file_button.setToolTip("Visualize NWB file") + accept_file_button.clicked.connect(self.pass_code_to_voila_widget) + + hbox = QHBoxLayout() + hbox.addWidget(browser_file_button, stretch=0) + hbox.addWidget(self.chosen_file, stretch=1) + hbox.addWidget(accept_file_button, stretch=0) + w = QWidget() + w.setLayout(hbox) + + self.layout.insertWidget(1, w) + + + def create_dandi_layout(self): self.dandiset_choice = QComboBox() for m in self.all_dandisets_metadata: item_name = m.id.split(":")[1].split("/")[0] + " - " + m.name self.dandiset_choice.addItem(item_name) self.dandiset_choice.view().setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) - self.accept_dandiset_choice = QPushButton() + accept_dandiset_choice = QPushButton() icon_1 = self.style().standardIcon(QStyle.SP_ArrowDown) - self.accept_dandiset_choice.setIcon(icon_1) - self.accept_dandiset_choice.setToolTip("Read DANDI set") - self.accept_dandiset_choice.clicked.connect(self.list_dandiset_files) + accept_dandiset_choice.setIcon(icon_1) + accept_dandiset_choice.setToolTip("Read DANDI set") + accept_dandiset_choice.clicked.connect(self.list_dandiset_files) - self.open_dandiset_choice = QPushButton() + open_dandiset_choice = QPushButton() icon_2 = self.style().standardIcon(QStyle.SP_ComputerIcon) - self.open_dandiset_choice.setIcon(icon_2) - self.open_dandiset_choice.setToolTip("Open in DANDI Archive") - self.open_dandiset_choice.clicked.connect(self.open_webbrowser) - - self.hbox1 = QHBoxLayout() - self.hbox1.addWidget(self.source_choice, stretch=0) - self.hbox1.addWidget(self.dandiset_choice, stretch=1) - self.hbox1.addWidget(self.accept_dandiset_choice, stretch=0) - self.hbox1.addWidget(self.open_dandiset_choice, stretch=0) - self.hbox1_w = QWidget() - self.hbox1_w.setLayout(self.hbox1) + open_dandiset_choice.setIcon(icon_2) + open_dandiset_choice.setToolTip("Open in DANDI Archive") + open_dandiset_choice.clicked.connect(self.open_webbrowser) + + hbox1 = QHBoxLayout() + hbox1.addWidget(self.dandiset_choice, stretch=1) + hbox1.addWidget(accept_dandiset_choice, stretch=0) + hbox1.addWidget(open_dandiset_choice, stretch=0) + hbox1_w = QWidget() + hbox1_w.setLayout(hbox1) # Summary info self.info_summary = QTextBrowser() @@ -84,39 +176,28 @@ def __init__(self, parent=None): self.info_summary.setFixedHeight(100) # Select file - self.file_choice = QComboBox() - self.file_choice.view().setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) - self.accept_file_choice = QPushButton() + self.dandi_file_choice = QComboBox() + self.dandi_file_choice.view().setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) + accept_file_choice = QPushButton() icon_3 = self.style().standardIcon(QStyle.SP_FileDialogContentsView) - self.accept_file_choice.setIcon(icon_3) - self.accept_file_choice.setToolTip("Visualize NWB file") - self.accept_file_choice.clicked.connect(self.pass_code_to_voila_widget) - - self.hbox2 = QHBoxLayout() - self.hbox2.addWidget(self.file_choice, stretch=1) - self.hbox2.addWidget(self.accept_file_choice, stretch=0) - self.hbox2_w = QWidget() - self.hbox2_w.setLayout(self.hbox2) - - layout = QVBoxLayout() - layout.addWidget(self.hbox1_w, stretch=0) - layout.addWidget(self.info_summary, stretch=0) - layout.addWidget(self.hbox2_w, stretch=0) - layout.addWidget(self.voila_widget, stretch=1) + accept_file_choice.setIcon(icon_3) + accept_file_choice.setToolTip("Visualize NWB file") + accept_file_choice.clicked.connect(self.pass_code_to_voila_widget) - self.main_widget = QWidget(self) - self.main_widget.setLayout(layout) - self.setCentralWidget(self.main_widget) - self.show() + hbox2 = QHBoxLayout() + hbox2.addWidget(self.dandi_file_choice, stretch=1) + hbox2.addWidget(accept_file_choice, stretch=0) + hbox2_w = QWidget() + hbox2_w.setLayout(hbox2) - - def change_data_source(self, value): - if value == "DANDI archive": - print("CHANGED: ", value) - elif value == "Local dir": - print("CHANGED: ", value) - elif value == "Local file": - print("CHANGED: ", value) + dandi_source_layout = QVBoxLayout() + dandi_source_layout.addWidget(hbox1_w, stretch=0) + dandi_source_layout.addWidget(self.info_summary, stretch=0) + dandi_source_layout.addWidget(hbox2_w, stretch=0) + w = QWidget() + w.setLayout(dandi_source_layout) + + self.layout.insertWidget(1, w) def open_webbrowser(self): @@ -126,24 +207,26 @@ def open_webbrowser(self): def list_dandiset_files(self): - self.file_choice.clear() + self.dandi_file_choice.clear() dandiset_id = self.dandiset_choice.currentText().split("-")[0].strip() self.info_summary.clear() metadata = get_dandiset_metadata(dandiset_id=dandiset_id) self.info_summary.append(metadata.description) all_files = list_dandiset_files(dandiset_id=dandiset_id) for f in all_files: - self.file_choice.addItem(f) + self.dandi_file_choice.addItem(f) def pass_code_to_voila_widget(self): self.voila_widget.external_notebook = None self.voila_widget.clear() - file_url = get_file_url( - dandiset_id=self.dandiset_choice.currentText().split("-")[0].strip(), - file_path=self.file_choice.currentText().strip() - ) - code1 = f"""import fsspec + + if self.source_choice.currentText() == "DANDI archive": + file_url = get_file_url( + dandiset_id=self.dandiset_choice.currentText().split("-")[0].strip(), + file_path=self.dandi_file_choice.currentText().strip() + ) + code = f"""import fsspec import pynwb import h5py from fsspec.implementations.cached import CachingFileSystem @@ -160,7 +243,26 @@ def pass_code_to_voila_widget(self): io = pynwb.NWBHDF5IO(file=file, load_namespaces=True) nwbfile = io.read() nwb2widget(nwbfile)""" - self.voila_widget.add_notebook_cell(code=code1, cell_type='code') + + elif self.source_choice.currentText() == "Local dir": + file_path = self.all_folder_files.currentText() + code = f"""import pynwb +from nwbwidgets import nwb2widget + +io = pynwb.NWBHDF5IO('{file_path}', load_namespaces=True) +nwbfile = io.read() +nwb2widget(nwbfile)""" + + elif self.source_choice.currentText() == "Local file": + file_path = self.chosen_file.text() + code = f"""import pynwb +from nwbwidgets import nwb2widget + +io = pynwb.NWBHDF5IO('{file_path}', load_namespaces=True) +nwbfile = io.read() +nwb2widget(nwbfile)""" + + self.voila_widget.add_notebook_cell(code=code, cell_type='code') # Run Voila self.voila_widget.run_voila() From 041ccf129899e4cf8fb01aff9ddef89e100392d2 Mon Sep 17 00:00:00 2001 From: Ben Dichter Date: Wed, 26 Oct 2022 09:26:48 -0400 Subject: [PATCH 7/8] Update requirements.txt --- qt/requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qt/requirements.txt b/qt/requirements.txt index 75d52218..d40b28ae 100644 --- a/qt/requirements.txt +++ b/qt/requirements.txt @@ -1,3 +1,4 @@ qtvoila PySide6 -nwbwidgets \ No newline at end of file +nwbwidgets +dandi From 024d818725e234289b49cafc37dddd4d22b1a26e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 30 Jun 2023 11:53:26 +0000 Subject: [PATCH 8/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- qt/README.md | 2 +- qt/application.py | 64 ++++++++++++++++++---------------------- qt/static/icon_dandi.svg | 2 +- qt/utils/dandi.py | 6 ++-- 4 files changed, 33 insertions(+), 41 deletions(-) diff --git a/qt/README.md b/qt/README.md index 0d3cb750..dc2ffee6 100644 --- a/qt/README.md +++ b/qt/README.md @@ -12,4 +12,4 @@ Further reference and options: https://pyinstaller.org/en/stable/index.html ```bash $ pyinstaller application.py -``` \ No newline at end of file +``` diff --git a/qt/application.py b/qt/application.py index 26a56e65..a0bc7c23 100644 --- a/qt/application.py +++ b/qt/application.py @@ -1,27 +1,27 @@ +import sys +import webbrowser +from pathlib import Path + +from PySide6.QtCore import Qt from PySide6.QtWidgets import ( - QApplication, - QMainWindow, - QPushButton, - QWidget, - QVBoxLayout, - QHBoxLayout, - QComboBox, + QApplication, + QComboBox, + QFileDialog, + QHBoxLayout, + QLineEdit, + QMainWindow, + QPushButton, QStyle, QTextBrowser, - QLineEdit, - QFileDialog + QVBoxLayout, + QWidget, ) -from PySide6.QtCore import Qt from qtvoila import QtVoila -import sys -import webbrowser -from pathlib import Path - from utils.dandi import ( - get_all_dandisets_metadata, + get_all_dandisets_metadata, get_dandiset_metadata, - list_dandiset_files, - get_file_url + get_file_url, + list_dandiset_files, ) @@ -29,7 +29,7 @@ class MyApp(QMainWindow): def __init__(self, parent=None): super().__init__(parent) self.resize(800, 800) - self.setWindowTitle('Desktop DANDI Explorer') + self.setWindowTitle("Desktop DANDI Explorer") try: self.all_dandisets_metadata = get_all_dandisets_metadata() @@ -63,7 +63,6 @@ def __init__(self, parent=None): self.setCentralWidget(self.main_widget) self.show() - def change_data_source(self, value): self.delete_item_from_layout() if value == "DANDI archive": @@ -72,27 +71,25 @@ def change_data_source(self, value): self.create_folder_layout() elif value == "Local file": self.create_file_layout() - def delete_item_from_layout(self): # Ref: https://stackoverflow.com/a/9899475/11483674 child = self.layout.takeAt(1) child.widget().deleteLater() - def browser_local_folder(self): - folder_path = QFileDialog.getExistingDirectory(parent=self, caption='Open folder', dir=str(Path.home())) + folder_path = QFileDialog.getExistingDirectory(parent=self, caption="Open folder", dir=str(Path.home())) if folder_path: for f in Path(folder_path).glob("*.nwb"): - self.all_folder_files.addItem(str(f)) - + self.all_folder_files.addItem(str(f)) def browser_local_file(self): - filename, filter = QFileDialog.getOpenFileName(parent=self, caption='Open file', dir=str(Path.home()), filter='NWB Files (*.nwb)') + filename, filter = QFileDialog.getOpenFileName( + parent=self, caption="Open file", dir=str(Path.home()), filter="NWB Files (*.nwb)" + ) if filename: self.chosen_file.setText(filename) - def create_folder_layout(self): browser_folder_button = QPushButton() icon = self.style().standardIcon(QStyle.SP_DialogOpenButton) @@ -106,7 +103,7 @@ def create_folder_layout(self): icon_2 = self.style().standardIcon(QStyle.SP_FileDialogContentsView) accept_folder_file_button.setIcon(icon_2) accept_folder_file_button.setToolTip("Visualize NWB file") - accept_folder_file_button.clicked.connect(self.pass_code_to_voila_widget) + accept_folder_file_button.clicked.connect(self.pass_code_to_voila_widget) hbox = QHBoxLayout() hbox.addWidget(browser_folder_button, stretch=0) @@ -117,7 +114,6 @@ def create_folder_layout(self): self.layout.insertWidget(1, w) - def create_file_layout(self): browser_file_button = QPushButton() icon = self.style().standardIcon(QStyle.SP_DialogOpenButton) @@ -142,7 +138,6 @@ def create_file_layout(self): self.layout.insertWidget(1, w) - def create_dandi_layout(self): self.dandiset_choice = QComboBox() for m in self.all_dandisets_metadata: @@ -198,13 +193,11 @@ def create_dandi_layout(self): w.setLayout(dandi_source_layout) self.layout.insertWidget(1, w) - def open_webbrowser(self): dandiset_id = self.dandiset_choice.currentText().split("-")[0].strip() metadata = get_dandiset_metadata(dandiset_id=dandiset_id) webbrowser.open(metadata.url) - def list_dandiset_files(self): self.dandi_file_choice.clear() @@ -216,15 +209,14 @@ def list_dandiset_files(self): for f in all_files: self.dandi_file_choice.addItem(f) - def pass_code_to_voila_widget(self): self.voila_widget.external_notebook = None self.voila_widget.clear() if self.source_choice.currentText() == "DANDI archive": file_url = get_file_url( - dandiset_id=self.dandiset_choice.currentText().split("-")[0].strip(), - file_path=self.dandi_file_choice.currentText().strip() + dandiset_id=self.dandiset_choice.currentText().split("-")[0].strip(), + file_path=self.dandi_file_choice.currentText().strip(), ) code = f"""import fsspec import pynwb @@ -262,12 +254,12 @@ def pass_code_to_voila_widget(self): nwbfile = io.read() nwb2widget(nwbfile)""" - self.voila_widget.add_notebook_cell(code=code, cell_type='code') + self.voila_widget.add_notebook_cell(code=code, cell_type="code") # Run Voila self.voila_widget.run_voila() -if __name__ == '__main__': +if __name__ == "__main__": app = QApplication(sys.argv) my_app = MyApp() sys.exit(app.exec()) diff --git a/qt/static/icon_dandi.svg b/qt/static/icon_dandi.svg index daad0ead..73b76431 100644 --- a/qt/static/icon_dandi.svg +++ b/qt/static/icon_dandi.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/qt/utils/dandi.py b/qt/utils/dandi.py index 1d3fc9a0..2dc7ff97 100644 --- a/qt/utils/dandi.py +++ b/qt/utils/dandi.py @@ -27,7 +27,7 @@ def list_dandiset_files(dandiset_id: str): return [i.dict().get("path") for i in dandiset.get_assets()] -def get_file_url(dandiset_id:str, file_path: str): +def get_file_url(dandiset_id: str, file_path: str): with DandiAPIClient() as client: - asset = client.get_dandiset(dandiset_id, 'draft').get_asset_by_path(file_path) - return asset.get_content_url(follow_redirects=1, strip_query=True) \ No newline at end of file + asset = client.get_dandiset(dandiset_id, "draft").get_asset_by_path(file_path) + return asset.get_content_url(follow_redirects=1, strip_query=True)