mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-15 20:57:56 +08:00
* add multi-layout support to back-end * remove obsolete code * temporary code to apply heuristic choice of default layout * fix tests * update python tests * more py lint * PR review changes * more PR lint * PR lint
211 lines
6.3 KiB
Python
211 lines
6.3 KiB
Python
import errno
|
|
import logging
|
|
from os import devnull
|
|
from os.path import splitext, basename, getsize
|
|
import sys
|
|
import warnings
|
|
import webbrowser
|
|
|
|
import click
|
|
import psutil
|
|
|
|
from server.app.app import Server
|
|
from server.app.util.errors import ScanpyFileError
|
|
from server.app.util.utils import custom_format_warning
|
|
from server.utils.utils import find_available_port
|
|
|
|
|
|
# anything bigger than this will generate a special message
|
|
BIG_FILE_SIZE_THRESHOLD = 100 * 2**20 # 100MB
|
|
|
|
|
|
@click.command()
|
|
@click.argument("data", metavar="<data file>", type=click.Path(exists=True, file_okay=True, dir_okay=False))
|
|
@click.option(
|
|
"--layout",
|
|
"-l",
|
|
default=[],
|
|
multiple=True,
|
|
show_default=True,
|
|
help="Layout name, eg, 'umap'."
|
|
)
|
|
@click.option(
|
|
"--diffexp",
|
|
"-d",
|
|
type=click.Choice(["ttest"]),
|
|
default="ttest",
|
|
show_default=True,
|
|
help="Method for differential expression.",
|
|
)
|
|
@click.option("--title", "-t", help="Title to display (if omitted will use file name).", metavar="")
|
|
@click.option(
|
|
"--verbose",
|
|
"-v",
|
|
is_flag=True,
|
|
default=False,
|
|
show_default=True,
|
|
help="Provide verbose output, including warnings and all server requests.",
|
|
)
|
|
@click.option("--debug", is_flag=True, default=False, show_default=True, help="Run in debug mode.")
|
|
@click.option(
|
|
"--open",
|
|
"-o",
|
|
"open_browser",
|
|
is_flag=True,
|
|
default=False,
|
|
show_default=True,
|
|
help="Open the web browser after launch.",
|
|
)
|
|
@click.option("--port", "-p", help="Port to run server on, if not specified cellxgene will find an available port.",
|
|
metavar="", show_default=True)
|
|
@click.option("--obs-names", default=None, metavar="", help="Name of annotation field to use for observations.")
|
|
@click.option("--var-names", default=None, metavar="", help="Name of annotation to use for variables.")
|
|
@click.option("--host", default="127.0.0.1", help="Host IP address")
|
|
@click.option(
|
|
"--max-category-items",
|
|
default=100,
|
|
metavar="",
|
|
show_default=True,
|
|
help="Limits the number of categorical annotation items displayed.",
|
|
)
|
|
@click.option(
|
|
"--diffexp-lfc-cutoff",
|
|
default=0.01,
|
|
show_default=True,
|
|
help="Relative expression cutoff used when selecting top N differentially expressed genes",
|
|
)
|
|
@click.option(
|
|
"--scripts",
|
|
default=[],
|
|
multiple=True,
|
|
help="Additional script files to include in html page",
|
|
show_default=True,
|
|
)
|
|
def launch(
|
|
data,
|
|
layout,
|
|
diffexp,
|
|
title,
|
|
verbose,
|
|
debug,
|
|
obs_names,
|
|
var_names,
|
|
open_browser,
|
|
port,
|
|
host,
|
|
max_category_items,
|
|
diffexp_lfc_cutoff,
|
|
scripts,
|
|
):
|
|
"""Launch the cellxgene data viewer.
|
|
This web app lets you explore single-cell expression data.
|
|
Data must be in a format that cellxgene expects, read the
|
|
"getting started" guide.
|
|
|
|
Examples:
|
|
|
|
> cellxgene launch example_dataset/pbmc3k.h5ad --title pbmc3k
|
|
|
|
> cellxgene launch <your data file> --title <your title>"""
|
|
|
|
# Startup message
|
|
click.echo("[cellxgene] Starting the CLI...")
|
|
|
|
# Argument checking
|
|
name, extension = splitext(data)
|
|
if extension != ".h5ad":
|
|
raise click.FileError(basename(data), hint="file type must be .h5ad")
|
|
|
|
if debug:
|
|
verbose = True
|
|
open_browser = False
|
|
else:
|
|
warnings.formatwarning = custom_format_warning
|
|
|
|
if scripts:
|
|
click.echo(r"""
|
|
/ / /\ \ \__ _ _ __ _ __ (_)_ __ __ _
|
|
\ \/ \/ / _` | '__| '_ \| | '_ \ / _` |
|
|
\ /\ / (_| | | | | | | | | | | (_| |
|
|
\/ \/ \__,_|_| |_| |_|_|_| |_|\__, |
|
|
|___/
|
|
The --scripts flag is intended for developers to include google analytics etc. You could be opening yourself to a
|
|
security risk by including the --scripts flag. Make sure you trust the scripts that you are including.
|
|
""")
|
|
scripts_pretty = ", ".join(scripts)
|
|
click.confirm(f"Are you sure you want to inject these scripts: {scripts_pretty}?", abort=True)
|
|
|
|
if not verbose:
|
|
sys.tracebacklimit = 0
|
|
|
|
if not title:
|
|
file_parts = splitext(basename(data))
|
|
title = file_parts[0]
|
|
|
|
if not port:
|
|
port = find_available_port(host)
|
|
|
|
# Setup app
|
|
cellxgene_url = f"http://{host}:{port}"
|
|
|
|
# Import Flask app
|
|
server = Server()
|
|
|
|
server.create_app()
|
|
server.app.config.update(SCRIPTS=scripts)
|
|
|
|
if not verbose:
|
|
log = logging.getLogger("werkzeug")
|
|
log.setLevel(logging.ERROR)
|
|
|
|
file_size = getsize(data)
|
|
|
|
# if a big file, let the user know it may take a while to load.
|
|
if file_size > BIG_FILE_SIZE_THRESHOLD:
|
|
click.echo(f"[cellxgene] Loading data from {basename(data)}, this may take awhile...")
|
|
else:
|
|
click.echo(f"[cellxgene] Loading data from {basename(data)}.")
|
|
# if file is larger than main memory, let the user know performance may suffer
|
|
if file_size > .95 * psutil.virtual_memory().total:
|
|
click.echo(f"[cellxgene] Warning: data file is larger than RAM - application may be very slow.")
|
|
|
|
# Fix for anaconda python. matplotlib typically expects python to be installed as a framework TKAgg is usually
|
|
# available and fixes this issue. See https://matplotlib.org/faq/virtualenv_faq.html
|
|
import matplotlib as mpl
|
|
|
|
mpl.use("TkAgg")
|
|
from server.app.scanpy_engine.scanpy_engine import ScanpyEngine
|
|
|
|
args = {
|
|
"layout": layout,
|
|
"diffexp": diffexp,
|
|
"max_category_items": max_category_items,
|
|
"diffexp_lfc_cutoff": diffexp_lfc_cutoff,
|
|
"obs_names": obs_names,
|
|
"var_names": var_names,
|
|
}
|
|
|
|
try:
|
|
server.attach_data(ScanpyEngine(data, args), title=title)
|
|
except ScanpyFileError as e:
|
|
raise click.ClickException(f"{e}")
|
|
|
|
if open_browser:
|
|
click.echo(f"[cellxgene] Launching! Opening your browser to {cellxgene_url} now.")
|
|
webbrowser.open(cellxgene_url)
|
|
else:
|
|
click.echo(f"[cellxgene] Launching! Please go to {cellxgene_url} in your browser.")
|
|
|
|
click.echo("[cellxgene] Type CTRL-C at any time to exit.")
|
|
|
|
if not verbose:
|
|
f = open(devnull, "w")
|
|
sys.stdout = f
|
|
|
|
try:
|
|
server.app.run(host=host, debug=debug, port=port, threaded=True)
|
|
except OSError as e:
|
|
if e.errno == errno.EADDRINUSE:
|
|
raise click.ClickException("Port is in use, please specify an open port using the --port flag.") from e
|
|
raise
|