mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-23 22:48:12 +08:00
remove options widget + launch on file selection (#839)
* remove options widget + launch on file selection * extend drop area width
This commit is contained in:
+16
-55
@@ -14,11 +14,8 @@ from PySide2.QtWidgets import *
|
||||
|
||||
import server.gui.cellxgene_rc
|
||||
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.utils import WINDOWS, LINUX, MAC, FileLoadSignals, Emitter, WorkerSignals, FileChanged
|
||||
from server.utils.errors import OptionsError
|
||||
from server.utils.utils import find_available_port
|
||||
|
||||
if WINDOWS or LINUX:
|
||||
@@ -30,6 +27,7 @@ if WINDOWS or LINUX:
|
||||
# TODO remember this or calculate it?
|
||||
WIDTH = 1300
|
||||
HEIGHT = 800
|
||||
MAX_CONTENT_WIDTH = 700
|
||||
GUI_PORT = find_available_port("localhost")
|
||||
BROWSER_INDEX = 0
|
||||
LOAD_INDEX = 1
|
||||
@@ -149,9 +147,8 @@ class LoadWidget(QFrame):
|
||||
def __init__(self, parent):
|
||||
super(LoadWidget, self).__init__(parent=parent)
|
||||
# Init layout
|
||||
self.MAX_CONTENT_WIDTH = 500
|
||||
load_ui_layout = QVBoxLayout()
|
||||
h_margin = (WIDTH - self.MAX_CONTENT_WIDTH) // 2
|
||||
h_margin = (WIDTH - MAX_CONTENT_WIDTH) // 2
|
||||
if h_margin < 10:
|
||||
h_margin = 10
|
||||
load_ui_layout.setContentsMargins(h_margin, 20, h_margin, 20)
|
||||
@@ -178,17 +175,14 @@ class LoadWidget(QFrame):
|
||||
self.file_area = FileArea()
|
||||
self.file_name.signals.changed.connect(self.updatePath)
|
||||
|
||||
self.launch_widget = QPushButton("Launch cellxgene")
|
||||
self.launch_widget.setEnabled(False)
|
||||
self.launch_widget.clicked.connect(self.onLoad)
|
||||
self.launch_widget = QLabel("Select a file to launch cellxgene")
|
||||
# self.launch_widget.setEnabled(False)
|
||||
# self.launch_widget.clicked.connect(self.onLoad)
|
||||
|
||||
self.progress = QProgressBar()
|
||||
self.progress.setTextVisible(False)
|
||||
|
||||
self.advanced_options = CLIOptionsArea(self)
|
||||
|
||||
file_layout.addWidget(self.file_area)
|
||||
file_layout.addWidget(self.advanced_options)
|
||||
self.loading_layout = QStackedLayout()
|
||||
self.loading_layout.addWidget(self.launch_widget)
|
||||
self.loading_layout.addWidget(self.progress)
|
||||
@@ -198,7 +192,7 @@ class LoadWidget(QFrame):
|
||||
# Error section
|
||||
self.error_label = QLabel("")
|
||||
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)
|
||||
|
||||
# Options Form
|
||||
@@ -225,14 +219,13 @@ class LoadWidget(QFrame):
|
||||
self.file_area.label.setText("File: " + file_name)
|
||||
else:
|
||||
self.file_area.label.setText("")
|
||||
self.launch_widget.setEnabled(bool(file_name))
|
||||
# self.launch_widget.setEnabled(bool(file_name))
|
||||
|
||||
def reset(self):
|
||||
self.loading_layout.setCurrentIndex(0)
|
||||
self.timer.stop()
|
||||
self.error_label.setText("")
|
||||
self.file_name.updateValue(None)
|
||||
self.advanced_options.reset()
|
||||
|
||||
def updateProgress(self):
|
||||
curr_val = self.progress.value()
|
||||
@@ -240,23 +233,15 @@ class LoadWidget(QFrame):
|
||||
self.progress.setValue(next_val)
|
||||
|
||||
def resetProgress(self):
|
||||
print("here i am")
|
||||
self.progress.setValue(0)
|
||||
self.loading_layout.setCurrentIndex(0)
|
||||
self.timer.stop()
|
||||
|
||||
def createScanpyEngine(self, file_name):
|
||||
try:
|
||||
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]
|
||||
title = splitext(basename(file_name))[0]
|
||||
self.window().setupServer()
|
||||
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.engine_error.connect(self.onServerError)
|
||||
self.window().load_emitter.signals.server_error.connect(self.onServerError)
|
||||
@@ -299,37 +284,11 @@ class LoadWidget(QFrame):
|
||||
self.resetProgress()
|
||||
self.window().stacked_layout.setCurrentIndex(LOAD_INDEX)
|
||||
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()
|
||||
|
||||
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):
|
||||
def __init__(self):
|
||||
super(FilePath, self).__init__()
|
||||
@@ -346,17 +305,17 @@ class FileArea(QFrame):
|
||||
super(FileArea, self).__init__()
|
||||
self.setFrameShape(QFrame.Box)
|
||||
self.setMinimumHeight(100)
|
||||
self.setFixedWidth(500)
|
||||
self.setFixedWidth(MAX_CONTENT_WIDTH)
|
||||
self.setAcceptDrops(True)
|
||||
self.instructions = QLabel(self)
|
||||
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)
|
||||
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.clicked.connect(self.fileBrowse)
|
||||
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):
|
||||
options = QFileDialog.Options()
|
||||
@@ -365,6 +324,7 @@ class FileArea(QFrame):
|
||||
"Open H5AD File", "", "H5AD Files (*.h5ad)", options=options)
|
||||
if file_name:
|
||||
self.parent().file_name.updateValue(file_name)
|
||||
self.parent().onLoad()
|
||||
|
||||
def dragEnterEvent(self, e):
|
||||
if e.mimeData().hasUrls:
|
||||
@@ -391,6 +351,7 @@ class FileArea(QFrame):
|
||||
for url in e.mimeData().urls():
|
||||
file_name = str(url.toLocalFile())
|
||||
self.parent().file_name.updateValue(file_name)
|
||||
self.parent().onLoad()
|
||||
else:
|
||||
e.ignore()
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -1,7 +0,0 @@
|
||||
class OptionsError(Exception):
|
||||
"""
|
||||
Raised when user specified cli options specified fail parsing
|
||||
"""
|
||||
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
Reference in New Issue
Block a user