From 939fec8b8cf29121694715a416f34216c77f8e02 Mon Sep 17 00:00:00 2001 From: Caleb Weinreb Date: Mon, 22 Apr 2024 14:22:06 -0400 Subject: [PATCH] Added functions to save and load selections + explanation in help menu --- snub/gui/help/loading_data.md | 2 +- snub/gui/help/selections.md | 4 ++ snub/gui/main.py | 87 +++++++++++++++++++++++++++++++---- 3 files changed, 84 insertions(+), 9 deletions(-) diff --git a/snub/gui/help/loading_data.md b/snub/gui/help/loading_data.md index 0c157ee..42beaf1 100644 --- a/snub/gui/help/loading_data.md +++ b/snub/gui/help/loading_data.md @@ -4,4 +4,4 @@ To open a SNUB project, go to File > Open Project, navigate to the project direc ## Reload a project -If a project is already open and its files have been modified outside of SNUB, the project can be reloaded in the SNUB GUI by going to File > Reload Project. \ No newline at end of file +If a project is already open and its files have been modified outside of SNUB, the project can be reloaded in the SNUB GUI by going to File > Reload Project. diff --git a/snub/gui/help/selections.md b/snub/gui/help/selections.md index 8fdd76d..a71f986 100644 --- a/snub/gui/help/selections.md +++ b/snub/gui/help/selections.md @@ -6,6 +6,10 @@ Selections can be used to show relationships between data-views (such intervals * Use command/control+drag to deselect points and intervals. * Use Edit > Deselect All to deselect everything +### Saving and loading selections + +The start/end times of all currently selected intervals can be saved to an external file using File > Save Selections. The file is in csv format with two columns containing the start and end times respectively. Selections can also be loaded using File > Load Selections. Note that when you load selections from a file, all current selections are cleared. + ## Rank variables by enrichment ### Scatter plot diff --git a/snub/gui/main.py b/snub/gui/main.py index 7be13c9..2720451 100644 --- a/snub/gui/main.py +++ b/snub/gui/main.py @@ -2,6 +2,7 @@ from PyQt5.QtWidgets import * from PyQt5.QtGui import * import sys, os, json +import numpy as np from functools import partial from snub.gui.utils import IntervalIndex, CheckBox from snub.gui.stacks import PanelStack, TrackStack @@ -339,12 +340,15 @@ def __init__(self, args): reload_data.setShortcut("Ctrl+R") reload_data.triggered.connect(self.reload_data) + save_selection = QAction("Save Selection", self) + save_selection.triggered.connect(self.save_selection) + + load_selection = QAction("Load Selection", self) + load_selection.triggered.connect(self.load_selection) + deselect_all = QAction("&Deselect All", self) deselect_all.triggered.connect(self.deselect_all) - save_layout = QAction("&Layout", self) - save_layout.triggered.connect(self.save_layout) - self.set_layout_to_rows = QAction("&Rows", self) self.set_layout_to_cols = QAction("&Columns", self) self.set_layout_to_rows.setCheckable(True) @@ -362,6 +366,8 @@ def __init__(self, args): fileMenu = mainMenu.addMenu("&File") fileMenu.addAction(open_project) fileMenu.addAction(reload_data) + fileMenu.addAction(save_selection) + fileMenu.addAction(load_selection) editMenu = mainMenu.addMenu("&Edit") editMenu.addAction(deselect_all) @@ -401,8 +407,6 @@ def open(self, *args, project_directories=None): error_directories = [] for project_dir in project_directories: if len(project_dir) > 0: - # if project_dir.endswith('config.json'): - # project_dir = os.path.dirname(project_dir) if os.path.exists(os.path.join(project_dir, "config.json")): self.load_project(project_dir) else: @@ -426,9 +430,6 @@ def reload_data(self): self.close_tab(current_index) self.load_project(project_dir) - def save_layout(self): - print("save") - def load_project(self, project_directory): name = project_directory.strip(os.path.sep).split(os.path.sep)[-1] project_tab = ProjectTab(project_directory) @@ -458,6 +459,76 @@ def getExistingDirectories(self): str(), ] + def load_selection(self): + current_index = self.tabs.currentIndex() + if current_index == -1: + QMessageBox.warning(self, "Error: No project is open") + return # no tab + + # get path of file to load + options = QFileDialog.Options() + options |= QFileDialog.DontUseNativeDialog + file_name, _ = QFileDialog.getOpenFileName( + self, + "Load Selection", + "", + "All Files (*);;CSV Files (*.csv)", + options=options, + ) + + print(file_name) + + if file_name: + try: + intervals = np.loadtxt(file_name, delimiter=",", skiprows=1) + if len(intervals) > 0: + is_selected = np.ones(len(intervals)) > 0 + self.tabs.currentWidget().update_selected_intervals( + intervals, is_selected + ) + except Exception as e: + QMessageBox.warning( + self, "Error", f"Failed to load selection: {str(e)}" + ) + + def save_selection(self): + current_index = self.tabs.currentIndex() + if current_index == -1: + QMessageBox.warning(self, "Error: No project is open") + return # no tab + + selected_intervals = self.tabs.currentWidget().selected_intervals.intervals + if len(selected_intervals) == 0: + QMessageBox.warning(self, "Error: Nothing is selected") + return # no tab + + # get path of file to save + options = QFileDialog.Options() + options |= QFileDialog.DontUseNativeDialog + file_name, _ = QFileDialog.getSaveFileName( + self, "Save Selection", "", "CSV Files (*.csv)", options=options + ) + + if file_name: + + if not file_name.lower().endswith(".csv"): + file_name += ".csv" + + try: + # generate csv text + text = ["start,end"] + for s, e in selected_intervals: + text.append(f"{s},{e}") + text = "\n".join(text) + "\n" + + # write to file + with open(file_name, "w") as file: + file.write(text) + except Exception as e: + QMessageBox.warning( + self, "Error", f"Failed to save selection: {str(e)}" + ) + def run(): app = QApplication(sys.argv)