remove options widget + launch on file selection (#839)

* remove options widget + launch on file selection

* extend drop area width
This commit is contained in:
Charlotte Weaver
2019-07-09 10:46:13 -07:00
committed by GitHub
parent acdc810f82
commit 722a91f1d2
4 changed files with 16 additions and 121 deletions
+16 -55
View File
@@ -14,11 +14,8 @@ from PySide2.QtWidgets import *
import server.gui.cellxgene_rc import server.gui.cellxgene_rc
from server.gui.browser import CefWidget, CefApplication from server.gui.browser import CefWidget, CefApplication
from server.gui.options_parser import parse_opt_string
from server.cli.launch import parse_engine_args
from server.gui.workers import Worker, SiteReadyWorker from server.gui.workers import Worker, SiteReadyWorker
from server.gui.utils import WINDOWS, LINUX, MAC, FileLoadSignals, Emitter, WorkerSignals, FileChanged from server.gui.utils import WINDOWS, LINUX, MAC, FileLoadSignals, Emitter, WorkerSignals, FileChanged
from server.utils.errors import OptionsError
from server.utils.utils import find_available_port from server.utils.utils import find_available_port
if WINDOWS or LINUX: if WINDOWS or LINUX:
@@ -30,6 +27,7 @@ if WINDOWS or LINUX:
# TODO remember this or calculate it? # TODO remember this or calculate it?
WIDTH = 1300 WIDTH = 1300
HEIGHT = 800 HEIGHT = 800
MAX_CONTENT_WIDTH = 700
GUI_PORT = find_available_port("localhost") GUI_PORT = find_available_port("localhost")
BROWSER_INDEX = 0 BROWSER_INDEX = 0
LOAD_INDEX = 1 LOAD_INDEX = 1
@@ -149,9 +147,8 @@ class LoadWidget(QFrame):
def __init__(self, parent): def __init__(self, parent):
super(LoadWidget, self).__init__(parent=parent) super(LoadWidget, self).__init__(parent=parent)
# Init layout # Init layout
self.MAX_CONTENT_WIDTH = 500
load_ui_layout = QVBoxLayout() load_ui_layout = QVBoxLayout()
h_margin = (WIDTH - self.MAX_CONTENT_WIDTH) // 2 h_margin = (WIDTH - MAX_CONTENT_WIDTH) // 2
if h_margin < 10: if h_margin < 10:
h_margin = 10 h_margin = 10
load_ui_layout.setContentsMargins(h_margin, 20, h_margin, 20) load_ui_layout.setContentsMargins(h_margin, 20, h_margin, 20)
@@ -178,17 +175,14 @@ class LoadWidget(QFrame):
self.file_area = FileArea() self.file_area = FileArea()
self.file_name.signals.changed.connect(self.updatePath) self.file_name.signals.changed.connect(self.updatePath)
self.launch_widget = QPushButton("Launch cellxgene") self.launch_widget = QLabel("Select a file to launch cellxgene")
self.launch_widget.setEnabled(False) # self.launch_widget.setEnabled(False)
self.launch_widget.clicked.connect(self.onLoad) # self.launch_widget.clicked.connect(self.onLoad)
self.progress = QProgressBar() self.progress = QProgressBar()
self.progress.setTextVisible(False) self.progress.setTextVisible(False)
self.advanced_options = CLIOptionsArea(self)
file_layout.addWidget(self.file_area) file_layout.addWidget(self.file_area)
file_layout.addWidget(self.advanced_options)
self.loading_layout = QStackedLayout() self.loading_layout = QStackedLayout()
self.loading_layout.addWidget(self.launch_widget) self.loading_layout.addWidget(self.launch_widget)
self.loading_layout.addWidget(self.progress) self.loading_layout.addWidget(self.progress)
@@ -198,7 +192,7 @@ class LoadWidget(QFrame):
# Error section # Error section
self.error_label = QLabel("") self.error_label = QLabel("")
self.error_label.setWordWrap(True) self.error_label.setWordWrap(True)
self.error_label.setFixedWidth(self.MAX_CONTENT_WIDTH) self.error_label.setFixedWidth(MAX_CONTENT_WIDTH)
message_layout.addWidget(self.error_label) message_layout.addWidget(self.error_label)
# Options Form # Options Form
@@ -225,14 +219,13 @@ class LoadWidget(QFrame):
self.file_area.label.setText("File: " + file_name) self.file_area.label.setText("File: " + file_name)
else: else:
self.file_area.label.setText("") self.file_area.label.setText("")
self.launch_widget.setEnabled(bool(file_name)) # self.launch_widget.setEnabled(bool(file_name))
def reset(self): def reset(self):
self.loading_layout.setCurrentIndex(0) self.loading_layout.setCurrentIndex(0)
self.timer.stop() self.timer.stop()
self.error_label.setText("") self.error_label.setText("")
self.file_name.updateValue(None) self.file_name.updateValue(None)
self.advanced_options.reset()
def updateProgress(self): def updateProgress(self):
curr_val = self.progress.value() curr_val = self.progress.value()
@@ -240,23 +233,15 @@ class LoadWidget(QFrame):
self.progress.setValue(next_val) self.progress.setValue(next_val)
def resetProgress(self): def resetProgress(self):
print("here i am")
self.progress.setValue(0) self.progress.setValue(0)
self.loading_layout.setCurrentIndex(0) self.loading_layout.setCurrentIndex(0)
self.timer.stop() self.timer.stop()
def createScanpyEngine(self, file_name): def createScanpyEngine(self, file_name):
try: title = splitext(basename(file_name))[0]
self.advanced_options.parse()
except OptionsError as e:
self.signals.error.emit(f"Options Error: {e}")
return
title = self.advanced_options.title
if not title:
title = splitext(basename(file_name))[0]
self.window().setupServer() self.window().setupServer()
worker = Worker(self.window().parent_conn, self.window().child_conn, file_name, host="127.0.0.1", worker = Worker(self.window().parent_conn, self.window().child_conn, file_name, host="127.0.0.1",
port=GUI_PORT, title=title, engine_options=self.advanced_options.engine_options) port=GUI_PORT, title=title, engine_options={})
self.window().load_emitter.signals.ready.connect(self.onDataReady) self.window().load_emitter.signals.ready.connect(self.onDataReady)
self.window().load_emitter.signals.engine_error.connect(self.onServerError) self.window().load_emitter.signals.engine_error.connect(self.onServerError)
self.window().load_emitter.signals.server_error.connect(self.onServerError) self.window().load_emitter.signals.server_error.connect(self.onServerError)
@@ -299,37 +284,11 @@ class LoadWidget(QFrame):
self.resetProgress() self.resetProgress()
self.window().stacked_layout.setCurrentIndex(LOAD_INDEX) self.window().stacked_layout.setCurrentIndex(LOAD_INDEX)
self.error_label.setText(f"Error: {err}") self.error_label.setText(f"Error: {err}")
self.error_label.resize(self.MAX_CONTENT_WIDTH, self.error_label.height()) self.error_label.resize(MAX_CONTENT_WIDTH, self.error_label.height())
self.window().repaint() self.window().repaint()
onServerError = partialmethod(onError, server_error=True) onServerError = partialmethod(onError, server_error=True)
class CLIOptionsArea(QFrame):
def __init__(self, parent):
super(CLIOptionsArea, self).__init__(parent)
self.engine_options = {}
self.setFixedWidth(500)
self.title = None
self.form_layout = QFormLayout()
self.form_layout.setContentsMargins(0,0,0,0)
self.cli_label = QLabel("CLI Options:")
self.cli_label.setToolTip("Additional options can be passed as cli parameters. See cellxgene --help for more info")
self.cli_widget = QLineEdit()
self.form_layout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
self.form_layout.addRow(self.cli_label, self.cli_widget)
self.setLayout(self.form_layout)
def reset(self):
self.cli_widget.setText("")
self.engine_options = {}
self.title = None
def parse(self):
opts = parse_opt_string(self.cli_widget.text())
self.title = opts.pop("title", None)
self.engine_options = parse_engine_args(**opts)
class FilePath(QObject): class FilePath(QObject):
def __init__(self): def __init__(self):
super(FilePath, self).__init__() super(FilePath, self).__init__()
@@ -346,17 +305,17 @@ class FileArea(QFrame):
super(FileArea, self).__init__() super(FileArea, self).__init__()
self.setFrameShape(QFrame.Box) self.setFrameShape(QFrame.Box)
self.setMinimumHeight(100) self.setMinimumHeight(100)
self.setFixedWidth(500) self.setFixedWidth(MAX_CONTENT_WIDTH)
self.setAcceptDrops(True) self.setAcceptDrops(True)
self.instructions = QLabel(self) self.instructions = QLabel(self)
self.instructions.setText("Drag & Drop a h5ad file to load or open") self.instructions.setText("Drag & Drop a h5ad file to load or open")
self.instructions.setGeometry(10, 10, 500, self.instructions.height()) self.instructions.setGeometry(10, 10, MAX_CONTENT_WIDTH, self.instructions.height())
self.loadButton = QPushButton("Open...", parent=self) self.loadButton = QPushButton("Open...", parent=self)
x_pos = (500 - self.loadButton.width()) / 2 x_pos = (MAX_CONTENT_WIDTH - self.loadButton.width()) / 2
self.loadButton.setGeometry(x_pos, 50, self.loadButton.width(), self.loadButton.height()) self.loadButton.setGeometry(x_pos, 50, self.loadButton.width(), self.loadButton.height())
self.loadButton.clicked.connect(self.fileBrowse) self.loadButton.clicked.connect(self.fileBrowse)
self.label = QLabel(self) self.label = QLabel(self)
self.label.setGeometry(10, 75, 500, self.label.height()) self.label.setGeometry(10, 75, MAX_CONTENT_WIDTH, self.label.height())
def fileBrowse(self): def fileBrowse(self):
options = QFileDialog.Options() options = QFileDialog.Options()
@@ -365,6 +324,7 @@ class FileArea(QFrame):
"Open H5AD File", "", "H5AD Files (*.h5ad)", options=options) "Open H5AD File", "", "H5AD Files (*.h5ad)", options=options)
if file_name: if file_name:
self.parent().file_name.updateValue(file_name) self.parent().file_name.updateValue(file_name)
self.parent().onLoad()
def dragEnterEvent(self, e): def dragEnterEvent(self, e):
if e.mimeData().hasUrls: if e.mimeData().hasUrls:
@@ -391,6 +351,7 @@ class FileArea(QFrame):
for url in e.mimeData().urls(): for url in e.mimeData().urls():
file_name = str(url.toLocalFile()) file_name = str(url.toLocalFile())
self.parent().file_name.updateValue(file_name) self.parent().file_name.updateValue(file_name)
self.parent().onLoad()
else: else:
e.ignore() e.ignore()
-27
View File
@@ -1,27 +0,0 @@
import shlex
import click
from server.cli.launch import common_args
from server.utils.errors import OptionsError
@click.command()
@common_args
def cli(**kwargs):
pass
def parse_opt_string(opts):
context = click.Context(cli)
parser = click.OptionParser(context)
for command in context.command.params:
command.add_to_parser(parser, context)
try:
opts, args, param_order = parser.parse_args(shlex.split(opts))
for param in cli.params:
value, args = param.handle_parse_result(context, opts, args)
except click.ClickException as ce:
raise OptionsError(ce.message) from ce
return dict(context.params)
-32
View File
@@ -1,32 +0,0 @@
import unittest
from server.gui.options_parser import parse_opt_string
from server.utils.errors import OptionsError
default_opts = {'title': None, 'layout': (), 'obs_names': None, 'var_names': None, 'max_category_items': 1000,
'diffexp_lfc_cutoff': 0.01}
class UtilsTest(unittest.TestCase):
def test_empty_opts(self):
result = parse_opt_string("")
self.assertEqual(result, default_opts)
def test_one_opts(self):
result = parse_opt_string("--title abcde")
expected_opts = dict(default_opts)
expected_opts["title"] = "abcde"
self.assertEqual(result, expected_opts)
def test_malformed_opts(self):
with self.assertRaises(OptionsError):
parse_opt_string("--ewwwww lkafsjkl")
def test_complex_opts(self):
result = parse_opt_string(
"--title abcde --obs-names zzzzz --var-names 'xxx yyy' --max-category-items 234907 "
"--diffexp-lfc-cutoff 0.00003847382")
expected_opts = {'title': 'abcde', 'layout': (), 'obs_names': 'zzzzz', 'var_names': 'xxx yyy',
'max_category_items': 234907, 'diffexp_lfc_cutoff': 3.847382e-05}
self.assertEqual(result, expected_opts)
-7
View File
@@ -1,7 +0,0 @@
class OptionsError(Exception):
"""
Raised when user specified cli options specified fail parsing
"""
def __init__(self, message):
self.message = message