-
Notifications
You must be signed in to change notification settings - Fork 24
Qt gui #227
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
luiztauffer
wants to merge
13
commits into
master
Choose a base branch
from
qt-gui
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Qt gui #227
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
83b0715
add fsspec as stream mode
luiztauffer dd02439
cache path option
luiztauffer 36d31d0
clean code
luiztauffer ffdcc1c
req
luiztauffer fea98bd
qt app
luiztauffer eecb17d
Merge branch 'master' into qt-gui
luiztauffer b7a2c32
local dir and file browser
luiztauffer b05b61f
Merge branch 'master' into qt-gui
luiztauffer 041ccf1
Update requirements.txt
bendichter a99c847
Merge branch 'master' into qt-gui
luiztauffer 030fc18
Merge branch 'master' into qt-gui
luiztauffer 4f973b2
Merge branch 'master' into qt-gui
luiztauffer 024d818
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
luiztauffer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ``` | ||
|
|
||
| ## Packaging the GUI as executable | ||
|
|
||
| Further reference and options: https://pyinstaller.org/en/stable/index.html | ||
|
|
||
| ```bash | ||
| $ pyinstaller application.py | ||
| ``` | ||
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,265 @@ | ||
| import sys | ||
| import webbrowser | ||
| from pathlib import Path | ||
|
|
||
| from PySide6.QtCore import Qt | ||
| from PySide6.QtWidgets import ( | ||
| QApplication, | ||
| QComboBox, | ||
| QFileDialog, | ||
| QHBoxLayout, | ||
| QLineEdit, | ||
| QMainWindow, | ||
| QPushButton, | ||
| QStyle, | ||
| QTextBrowser, | ||
| QVBoxLayout, | ||
| QWidget, | ||
| ) | ||
| from qtvoila import QtVoila | ||
| from utils.dandi import ( | ||
| get_all_dandisets_metadata, | ||
| get_dandiset_metadata, | ||
| get_file_url, | ||
| list_dandiset_files, | ||
| ) | ||
|
|
||
|
|
||
| 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("Local dir") | ||
| self.source_choice.addItem("Local file") | ||
| 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) | ||
|
|
||
| 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) | ||
|
|
||
| accept_dandiset_choice = QPushButton() | ||
| icon_1 = self.style().standardIcon(QStyle.SP_ArrowDown) | ||
| accept_dandiset_choice.setIcon(icon_1) | ||
| accept_dandiset_choice.setToolTip("Read DANDI set") | ||
| accept_dandiset_choice.clicked.connect(self.list_dandiset_files) | ||
|
|
||
| open_dandiset_choice = QPushButton() | ||
| icon_2 = self.style().standardIcon(QStyle.SP_ComputerIcon) | ||
| 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() | ||
| 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.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) | ||
| accept_file_choice.setIcon(icon_3) | ||
| accept_file_choice.setToolTip("Visualize NWB file") | ||
| accept_file_choice.clicked.connect(self.pass_code_to_voila_widget) | ||
|
|
||
| hbox2 = QHBoxLayout() | ||
| hbox2.addWidget(self.dandi_file_choice, stretch=1) | ||
| hbox2.addWidget(accept_file_choice, stretch=0) | ||
| hbox2_w = QWidget() | ||
| hbox2_w.setLayout(hbox2) | ||
|
|
||
| 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): | ||
| 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() | ||
| 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.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(), | ||
| ) | ||
| code = 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)""" | ||
|
|
||
| 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() | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| app = QApplication(sys.argv) | ||
| my_app = MyApp() | ||
| sys.exit(app.exec()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| qtvoila | ||
| PySide6 | ||
| nwbwidgets | ||
| dandi |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.