Launch validation (#414)

* WIP

* Cleanup

* Validation

* typo

* Max category limit 100 -> 500
This commit is contained in:
Charlotte Weaver
2018-11-07 13:01:13 -08:00
committed by GitHub
parent a9189017c7
commit 7d5c054b90
10 changed files with 204 additions and 141 deletions
+3 -3
View File
@@ -14,9 +14,9 @@ class CXGDriver(metaclass=ABCMeta):
def __init__(self, data, args): def __init__(self, data, args):
self.data = self._load_data(data) self.data = self._load_data(data)
self.layout_method = args['layout'] self.layout_method = args["layout"]
self.diffexp_method = args['diffexp'] self.diffexp_method = args["diffexp"]
self.max_category_items = args['max_category_items'] self.max_category_items = args["max_category_items"]
self.cluster = None self.cluster = None
@property @property
+2 -1
View File
@@ -10,7 +10,8 @@ from werkzeug.datastructures import ImmutableMultiDict
from server.app.util.constants import Axis, DiffExpMode from server.app.util.constants import Axis, DiffExpMode
from server.app.util.filter import parse_filter, QueryStringError from server.app.util.filter import parse_filter, QueryStringError
from server.app.util.models import FilterModel from server.app.util.models import FilterModel
from server.app.util.utils import FilterError, InteractiveError, MimeTypeError, PrepareError, get_mime_type from server.app.util.utils import get_mime_type
from server.app.util.errors import MimeTypeError, FilterError, InteractiveError, PrepareError
""" """
Sort order for routes Sort order for routes
+52 -18
View File
@@ -2,12 +2,13 @@ import warnings
import numpy as np import numpy as np
from pandas import DataFrame from pandas import DataFrame
from pandas.core.dtypes.dtypes import CategoricalDtype
import scanpy.api as sc import scanpy.api as sc
from scipy import stats, sparse from scipy import stats, sparse
from server.app.driver.driver import CXGDriver from server.app.driver.driver import CXGDriver
from server.app.util.constants import Axis, DEFAULT_TOP_N, DiffExpMode from server.app.util.constants import Axis, DEFAULT_TOP_N, DiffExpMode
from server.app.util.utils import FilterError, InteractiveError, PrepareError from server.app.util.errors import FilterError, InteractiveError, PrepareError, ScanpyFileError
""" """
Sort order for methods Sort order for methods
@@ -23,9 +24,10 @@ class ScanpyEngine(CXGDriver):
def __init__(self, data, args): def __init__(self, data, args):
super().__init__(data, args) super().__init__(data, args)
self._alias_annotation_names(Axis.OBS, args['obs_names']) self._alias_annotation_names(Axis.OBS, args["obs_names"])
self._alias_annotation_names(Axis.VAR, args['var_names']) self._alias_annotation_names(Axis.VAR, args["var_names"])
self._validate_data_types() self._validate_data_types()
self._validate_data_calculations()
self.cell_count = self.data.shape[0] self.cell_count = self.data.shape[0]
self.gene_count = self.data.shape[1] self.gene_count = self.data.shape[1]
self.layout_options = ["umap", "tsne"] self.layout_options = ["umap", "tsne"]
@@ -39,27 +41,28 @@ class ScanpyEngine(CXGDriver):
As a *critical* side-effect, ensure the indices are simple number ranges As a *critical* side-effect, ensure the indices are simple number ranges
(accomplished by calling pandas.DataFrame.reset_index()) (accomplished by calling pandas.DataFrame.reset_index())
""" """
if name == 'name': if name == "name":
# a noop, so skip it # a noop, so skip it
return return
ax_name = str(axis) ax_name = str(axis)
df_axis = getattr(self.data, ax_name) df_axis = getattr(self.data, ax_name)
if name is None: if name is None:
# reset index to simple range; alias 'name' to point at the # reset index to simple range; alias "name" to point at the
# previously specified index. # previously specified index.
df_axis = df_axis.reset_index().rename(columns={'index': 'name'}) df_axis.reset_index(inplace=True)
df_axis.rename(inplace=True, columns={"index": "name"})
elif name in df_axis.columns: elif name in df_axis.columns:
if name not in df_axis.columns: if name not in df_axis.columns:
raise KeyError(f"Annotation name {name}, specified in --{ax_name}-name does not exist.") raise KeyError(f"Annotation name {name}, specified in --{ax_name}-name does not exist.")
if not df_axis[name].is_unique: if not df_axis[name].is_unique:
raise KeyError(f"Values in -{ax_name}-name must be unique. " raise KeyError(f"Values in -{ax_name}-name must be unique. "
"Please prepare data to contain unique values.") "Please prepare data to contain unique values.")
# reset index to simple range; alias user-specified annotation to 'name' # reset index to simple range; alias user-specified annotation to "name"
df_axis = df_axis.reset_index(drop=True).rename(columns={name: 'name'}) df_axis.reset_index(drop=True, inplace=True)
df_axis.rename(inplace=True, columns={name: "name"})
else: else:
raise KeyError(f"Annotation name {name}, specified in --{ax_name}_name does not exist.") raise KeyError(f"Annotation name {name}, specified in --{ax_name}_name does not exist.")
setattr(self.data, ax_name, df_axis)
def _create_schema(self): def _create_schema(self):
self.schema = { self.schema = {
@@ -78,9 +81,9 @@ class ScanpyEngine(CXGDriver):
for ann in curr_axis: for ann in curr_axis:
ann_schema = {"name": ann} ann_schema = {"name": ann}
data_kind = curr_axis[ann].dtype.kind data_kind = curr_axis[ann].dtype.kind
if data_kind == 'f': if data_kind == "f":
ann_schema["type"] = "float32" ann_schema["type"] = "float32"
elif data_kind in ['i', 'u']: elif data_kind in ["i", "u"]:
ann_schema["type"] = "int32" ann_schema["type"] = "int32"
elif data_kind == "?": elif data_kind == "?":
ann_schema["type"] = "boolean" ann_schema["type"] = "boolean"
@@ -98,7 +101,18 @@ class ScanpyEngine(CXGDriver):
# Based on benchmarking, cache=True has no impact on perf. # Based on benchmarking, cache=True has no impact on perf.
# Note: as of current scanpy/anndata release, setting backed='r' will # Note: as of current scanpy/anndata release, setting backed='r' will
# result in an error. https://github.com/theislab/anndata/issues/79 # result in an error. https://github.com/theislab/anndata/issues/79
return sc.read(data, cache=False) try:
result = sc.read(data, cache=True)
except ValueError:
raise ScanpyFileError("File must be in the .h5ad format. Please read "
"https://github.com/theislab/scanpy_usage/blob/master/170505_seurat/info_h5ad.md to "
"learn more about this format. You may be able to convert your file into this format "
"using `cellxgene prepare`, please run `cellxgene prepare --help` for more "
"information.")
except Exception as e:
raise ScanpyFileError(f"Error while loading file: {e}, File must be in the .h5ad format, please check "
f"that your input and try again.")
return result
@staticmethod @staticmethod
def _top_sort(values, sort_order, top_n=None): def _top_sort(values, sort_order, top_n=None):
@@ -137,14 +151,34 @@ class ScanpyEngine(CXGDriver):
curr_axis = getattr(self.data, str(ax)) curr_axis = getattr(self.data, str(ax))
for ann in curr_axis: for ann in curr_axis:
datatype = curr_axis[ann].dtype datatype = curr_axis[ann].dtype
downcast_map = {'int64': 'int32', downcast_map = {"int64": "int32",
'uint32': 'int32', "uint32": "int32",
'uint64': 'int32', "uint64": "int32",
'float64': 'float32', "float64": "float32",
} }
if datatype in downcast_map: if datatype in downcast_map:
warnings.warn(f"Scanpy annotation {ax}:{ann} is in unsupported format: {datatype}. " warnings.warn(f"Scanpy annotation {ax}:{ann} is in unsupported format: {datatype}. "
f"Data will be downcast to {downcast_map[datatype]}.") f"Data will be downcast to {downcast_map[datatype]}.")
if isinstance(datatype, CategoricalDtype):
category_num = len(curr_axis[ann].dtype.categories)
if category_num > 500 and category_num > self.max_category_items:
warnings.warn(
f"{str(ax).title()} annotation '{ann}' has {category_num} categories, this may be "
f"cumbersome or slow to display. We recommend setting the "
f"--max-category-items option to 500, this will hide categorical "
f"annotations with more than 500 categories in the UI")
def _validate_data_calculations(self):
layout_key = f"X_{self.layout_method}"
try:
assert layout_key in self.data.obsm_keys()
except AssertionError:
raise PrepareError(
f"Cannot find a field with coordinates for the {self.layout_method} layout requested. A different"
f" layout may have been computed. The requested layout must be pre-calculated and saved "
f"back in the h5ad file. You can run "
f"`cellxgene prepare --layout {self.layout_method} <datafile>` "
f"to solve this problem. ")
def filter_dataframe(self, filter, include_uns=False): def filter_dataframe(self, filter, include_uns=False):
""" """
@@ -232,8 +266,8 @@ class ScanpyEngine(CXGDriver):
https://docs.scipy.org/doc/scipy/reference/sparse.html https://docs.scipy.org/doc/scipy/reference/sparse.html
""" """
prefer_row_access = sparse.isspmatrix_csr(data._X) or \ prefer_row_access = sparse.isspmatrix_csr(data._X) or sparse.isspmatrix_lil(data._X) \
sparse.isspmatrix_lil(data._X) or sparse.isspmatrix_bsr(data._X) or sparse.isspmatrix_bsr(data._X)
if prefer_row_access: if prefer_row_access:
# Row-major slicing # Row-major slicing
if obs_selector is not None: if obs_selector is not None:
+43
View File
@@ -0,0 +1,43 @@
class FilterError(Exception):
"""
Raised when filter is malformed
"""
def __init__(self, message):
self.message = message
class InteractiveError(Exception):
"""
Raised when computation would exceed interactive time
"""
def __init__(self, message):
self.message = message
class MimeTypeError(Exception):
"""
Raised when incompatible MIME type selected
"""
def __init__(self, message):
self.message = message
class PrepareError(Exception):
"""
Raised when data is misprepared
"""
def __init__(self, message):
self.message = message
class ScanpyFileError(Exception):
"""
Raised when file loaded into scanpy is misformatted
"""
def __init__(self, message):
self.message = message
+2 -24
View File
@@ -3,6 +3,8 @@ from argparse import ArgumentTypeError
from numpy import float32, integer from numpy import float32, integer
from server.app.util.errors import MimeTypeError
class Float32JSONEncoder(json.JSONEncoder): class Float32JSONEncoder(json.JSONEncoder):
def default(self, obj): def default(self, obj):
@@ -13,30 +15,6 @@ class Float32JSONEncoder(json.JSONEncoder):
return json.JSONEncoder.default(self, obj) return json.JSONEncoder.default(self, obj)
class MimeTypeError(Exception):
def __init__(self, message):
self.message = message
class FilterError(Exception):
def __init__(self, message):
self.message = message
class InteractiveError(Exception):
def __init__(self, message):
self.message = message
class PrepareError(Exception):
def __init__(self, message):
self.message = message
def get_mime_type(default="application/json", acceptable_types=["application/json", "text/csv"], query_param=None, def get_mime_type(default="application/json", acceptable_types=["application/json", "text/csv"], query_param=None,
header=None): header=None):
mime_type = default mime_type = default
+2 -2
View File
@@ -4,8 +4,8 @@ from .launch import launch
from .prepare import prepare from .prepare import prepare
@click.group(name='cellxgene', context_settings=dict(max_content_width=85)) @click.group(name="cellxgene", context_settings=dict(max_content_width=85))
@click.version_option(version='0.0.1', prog_name='cellxgene', message='[%(prog)s] Version %(version)s') @click.version_option(version="0.0.1", prog_name="cellxgene", message="[%(prog)s] Version %(version)s")
def cli(): def cli():
pass pass
+42 -33
View File
@@ -1,31 +1,32 @@
import sys import sys
import click import click
import logging import logging
from os.path import splitext, basename
import webbrowser import webbrowser
from os.path import splitext, basename from server.app.util.errors import ScanpyFileError
@click.command() @click.command()
@click.argument('data', metavar='<data file>', type=click.Path(exists=True, file_okay=True, dir_okay=False)) @click.argument("data", metavar="<data file>", type=click.Path(exists=True, file_okay=True, dir_okay=False))
@click.option('--layout', '-l', type=click.Choice(['umap', 'tsne']), default='umap', show_default=True, @click.option("--layout", "-l", type=click.Choice(["umap", "tsne"]), default="umap", show_default=True,
help='Method for layout.') help="Method for layout.")
@click.option('--diffexp', '-d', type=click.Choice(['ttest']), default='ttest', show_default=True, @click.option("--diffexp", "-d", type=click.Choice(["ttest"]), default="ttest", show_default=True,
help='Method for differential expression.') help="Method for differential expression.")
@click.option('--title', '-t', help='Title to display (if omitted will use file name).', metavar='') @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, @click.option("--verbose", "-v", is_flag=True, default=False, show_default=True,
help='Provide verbose output, including warnings and all server requests.') help="Provide verbose output, including warnings and all server requests.")
@click.option('--debug', '-d', is_flag=True, default=False, show_default=True, @click.option("--debug", "-d", is_flag=True, default=False, show_default=True,
help='Run in debug mode.') help="Run in debug mode.")
@click.option('--open', '-o', 'open_browser', is_flag=True, default=False, show_default=True, @click.option("--open", "-o", "open_browser", is_flag=True, default=False, show_default=True,
help='Open the web browser after launch.') help="Open the web browser after launch.")
@click.option('--port', '-p', help="Port to run server on.", metavar='', default=5005, show_default=True) @click.option("--port", "-p", help="Port to run server on.", metavar="", default=5005, show_default=True)
@click.option('--obs-names', default=None, metavar='', help='Name of annotation field to use for observations.') @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("--var-names", default=None, metavar="", help="Name of annotation to use for variables.")
@click.option('--listen-all', is_flag=True, default=False, show_default=True, @click.option("--listen-all", is_flag=True, default=False, show_default=True,
help='Bind to all interfaces (this makes the server accessible beyond this computer).') help="Bind to all interfaces (this makes the server accessible beyond this computer).")
@click.option('--max-category-items', default=100, metavar='', show_default=True, @click.option("--max-category-items", default=100, metavar="", show_default=True,
help='Limits the number of categorical annotation items displayed.') help="Limits the number of categorical annotation items displayed.")
def launch(data, layout, diffexp, title, verbose, debug, obs_names, var_names, def launch(data, layout, diffexp, title, verbose, debug, obs_names, var_names,
open_browser, port, listen_all, max_category_items): open_browser, port, listen_all, max_category_items):
"""Launch the cellxgene data viewer. """Launch the cellxgene data viewer.
@@ -40,15 +41,15 @@ def launch(data, layout, diffexp, title, verbose, debug, obs_names, var_names,
> cellxgene launch <your data file> --title <your title>""" > cellxgene launch <your data file> --title <your title>"""
# Startup message # Startup message
click.echo('[cellxgene] Starting the CLI...') click.echo("[cellxgene] Starting the CLI...")
# Import Flask app # Import Flask app
from server.app.app import app from server.app.app import app
# Argument checking # Argument checking
name, extension = splitext(data) name, extension = splitext(data)
if extension != '.h5ad': if extension != ".h5ad":
raise click.FileError(basename(data), hint='file type must be .h5ad') raise click.FileError(basename(data), hint="file type must be .h5ad")
if debug: if debug:
verbose = True verbose = True
@@ -62,9 +63,9 @@ def launch(data, layout, diffexp, title, verbose, debug, obs_names, var_names,
title = file_parts[0] title = file_parts[0]
if listen_all: if listen_all:
host = '0.0.0.0' host = "0.0.0.0"
else: else:
host = '127.0.0.1' host = "127.0.0.1"
# Setup app # Setup app
cellxgene_url = f"http://{host}:{port}" cellxgene_url = f"http://{host}:{port}"
@@ -76,24 +77,32 @@ def launch(data, layout, diffexp, title, verbose, debug, obs_names, var_names,
) )
if not verbose: if not verbose:
log = logging.getLogger('werkzeug') log = logging.getLogger("werkzeug")
log.setLevel(logging.ERROR) log.setLevel(logging.ERROR)
click.echo(f'[cellxgene] Loading data from {basename(data)}, this may take awhile...') click.echo(f"[cellxgene] Loading data from {basename(data)}, this may take awhile...")
from server.app.scanpy_engine.scanpy_engine import ScanpyEngine from server.app.scanpy_engine.scanpy_engine import ScanpyEngine
args = {'layout': layout, 'diffexp': diffexp, 'max_category_items': max_category_items, args = {
'obs_names': obs_names, 'var_names': var_names} "layout": layout,
"diffexp": diffexp,
"max_category_items": max_category_items,
"obs_names": obs_names,
"var_names": var_names
}
app.data = ScanpyEngine(data, args) try:
app.data = ScanpyEngine(data, args)
except ScanpyFileError as e:
raise click.ClickException(f"{e}")
if open_browser: if open_browser:
click.echo(f'[cellxgene] Launching! Opening your browser to {cellxgene_url} now.') click.echo(f"[cellxgene] Launching! Opening your browser to {cellxgene_url} now.")
webbrowser.open(cellxgene_url) webbrowser.open(cellxgene_url)
else: else:
click.echo(f'[cellxgene] Launching! Please go to {cellxgene_url} in your browser.') click.echo(f"[cellxgene] Launching! Please go to {cellxgene_url} in your browser.")
click.echo('[cellxgene] Type CTRL-C at any time to exit.') click.echo("[cellxgene] Type CTRL-C at any time to exit.")
app.run(host=host, debug=debug, port=port, threaded=True) app.run(host=host, debug=debug, port=port, threaded=True)
+56 -56
View File
@@ -1,26 +1,26 @@
import click from os.path import expanduser, isdir, isfile, sep, splitext
from numpy import unique, ndarray import click
from numpy import ndarray, unique
from scipy.sparse.csc import csc_matrix from scipy.sparse.csc import csc_matrix
from os.path import isfile, isdir, splitext, expanduser, sep
@click.command() @click.command()
@click.argument('data', nargs=1, metavar='<dataset: file or path to data>', required=True) @click.argument("data", nargs=1, metavar="<dataset: file or path to data>", required=True)
@click.option('--layout', '-l', default=['umap', 'tsne'], multiple=True, type=click.Choice(['umap', 'tsne']), @click.option("--layout", "-l", default=["umap", "tsne"], multiple=True, type=click.Choice(["umap", "tsne"]),
help='Layout algorithm', show_default=True) help="Layout algorithm", show_default=True)
@click.option('--recipe', '-r', default='none', type=click.Choice(['none', 'seurat', 'zheng17']), @click.option("--recipe", "-r", default="none", type=click.Choice(["none", "seurat", "zheng17"]),
help='Preprocessing to run.', show_default=True) help="Preprocessing to run.", show_default=True)
@click.option('--output', '-o', default='', help='Save a new file to filename.', metavar='<filename>') @click.option("--output", "-o", default="", help="Save a new file to filename.", metavar="<filename>")
@click.option('--plotting', '-p', default=False, is_flag=True, help='Whether to generate plots.', show_default=True) @click.option("--plotting", "-p", default=False, is_flag=True, help="Whether to generate plots.", show_default=True)
@click.option('--sparse', default=False, is_flag=True, help='Whether to force sparsity.', show_default=True) @click.option("--sparse", default=False, is_flag=True, help="Whether to force sparsity.", show_default=True)
@click.option('--overwrite', default=False, is_flag=True, help='Allow file overwriting.', show_default=True) @click.option("--overwrite", default=False, is_flag=True, help="Allow file overwriting.", show_default=True)
@click.option('--set-obs-names', default='', help='Named field to set as index for obs.', metavar='<name>') @click.option("--set-obs-names", default="", help="Named field to set as index for obs.", metavar="<name>")
@click.option('--set-var-names', default='', help='Named field to set as index for var.', metavar='<name>') @click.option("--set-var-names", default="", help="Named field to set as index for var.", metavar="<name>")
@click.option('--make-obs-names-unique', default=True, is_flag=True, @click.option("--make-obs-names-unique", default=True, is_flag=True,
help='Ensure obs index is unique.', show_default=True) help="Ensure obs index is unique.", show_default=True)
@click.option('--make-var-names-unique', default=True, is_flag=True, @click.option("--make-var-names-unique", default=True, is_flag=True,
help='Ensure var index is unique.', show_default=True) help="Ensure var index is unique.", show_default=True)
def prepare(data, layout, recipe, output, plotting, sparse, overwrite, def prepare(data, layout, recipe, output, plotting, sparse, overwrite,
set_obs_names, set_var_names, make_obs_names_unique, make_var_names_unique): set_obs_names, set_var_names, make_obs_names_unique, make_var_names_unique):
"""Preprocesses data for use with cellxgene. """Preprocesses data for use with cellxgene.
@@ -33,9 +33,9 @@ def prepare(data, layout, recipe, output, plotting, sparse, overwrite,
annotations, ensuring sparsity, and plotting results.""" annotations, ensuring sparsity, and plotting results."""
# collect slow imports here to make CLI startup more responsive # collect slow imports here to make CLI startup more responsive
click.echo('[cellxgene] Starting CLI...') click.echo("[cellxgene] Starting CLI...")
import matplotlib import matplotlib
matplotlib.use('Agg') matplotlib.use("Agg")
import scanpy.api as sc import scanpy.api as sc
# scanpy settings # scanpy settings
@@ -43,45 +43,45 @@ def prepare(data, layout, recipe, output, plotting, sparse, overwrite,
sc.settings.autosave = True sc.settings.autosave = True
# check args # check args
if sparse and not recipe == 'none': if sparse and not recipe == "none":
raise click.UsageError('Cannot use a recipe when forcing sparsity') raise click.UsageError("Cannot use a recipe when forcing sparsity")
output = expanduser(output) output = expanduser(output)
if isfile(output) and not overwrite: if isfile(output) and not overwrite:
raise click.UsageError(f'Cannot overwrite existing file {output}, try using the flag --overwrite') raise click.UsageError(f"Cannot overwrite existing file {output}, try using the flag --overwrite")
def load_data(data): def load_data(data):
if isfile(data): if isfile(data):
name, extension = splitext(data) name, extension = splitext(data)
if extension == '.h5ad': if extension == ".h5ad":
adata = sc.read_h5ad(data) adata = sc.read_h5ad(data)
elif extension == '.loom': elif extension == ".loom":
adata = sc.read_loom(data) adata = sc.read_loom(data)
else: else:
raise click.FileError(data, hint='does not have a valid extension [.h5ad | .loom]') raise click.FileError(data, hint="does not have a valid extension [.h5ad | .loom]")
elif isdir(data): elif isdir(data):
if not data.endswith(sep): if not data.endswith(sep):
data += sep data += sep
adata = sc.read_10x_mtx(data) adata = sc.read_10x_mtx(data)
else: else:
raise click.FileError(data, hint='not a valid file or path') raise click.FileError(data, hint="not a valid file or path")
if not set_obs_names == '': if not set_obs_names == "":
if set_obs_names not in adata.obs_keys(): if set_obs_names not in adata.obs_keys():
raise click.UsageError(f'obs {set_obs_names} not found, options are: {adata.obs_keys()}') raise click.UsageError(f"obs {set_obs_names} not found, options are: {adata.obs_keys()}")
adata.obs_names = adata.obs[set_obs_names] adata.obs_names = adata.obs[set_obs_names]
if not set_var_names == '': if not set_var_names == "":
if set_var_names not in adata.var_keys(): if set_var_names not in adata.var_keys():
raise click.UsageError(f'var {set_var_names} not found, options are: {adata.var_keys()}') raise click.UsageError(f"var {set_var_names} not found, options are: {adata.var_keys()}")
adata.var_names = adata.var[set_var_names] adata.var_names = adata.var[set_var_names]
if make_obs_names_unique: if make_obs_names_unique:
adata.obs_names_make_unique() adata.obs_names_make_unique()
if make_var_names_unique: if make_var_names_unique:
adata.var_names_make_unique() adata.var_names_make_unique()
if not adata._obs.index.is_unique: if not adata._obs.index.is_unique:
click.echo('Warning: obs index is not unique') click.echo("Warning: obs index is not unique")
if not adata._var.index.is_unique: if not adata._var.index.is_unique:
click.echo('Warning: var index is not unique') click.echo("Warning: var index is not unique")
return adata return adata
@@ -90,9 +90,9 @@ def prepare(data, layout, recipe, output, plotting, sparse, overwrite,
adata.X = csc_matrix(adata.X) adata.X = csc_matrix(adata.X)
def run_recipe(adata): def run_recipe(adata):
if recipe == 'seurat': if recipe == "seurat":
sc.pp.recipe_seurat(adata) sc.pp.recipe_seurat(adata)
elif recipe == 'zheng17': elif recipe == "zheng17":
sc.pp.recipe_zheng17(adata) sc.pp.recipe_zheng17(adata)
else: else:
sc.pp.filter_cells(adata, min_genes=5) sc.pp.filter_cells(adata, min_genes=5)
@@ -104,9 +104,9 @@ def prepare(data, layout, recipe, output, plotting, sparse, overwrite,
def run_pca(adata): def run_pca(adata):
if sparse: if sparse:
sc.pp.pca(adata, svd_solver='arpack', zero_center=False) sc.pp.pca(adata, svd_solver="arpack", zero_center=False)
else: else:
sc.pp.pca(adata, svd_solver='arpack') sc.pp.pca(adata, svd_solver="arpack")
def run_neighbors(adata): def run_neighbors(adata):
sc.pp.neighbors(adata) sc.pp.neighbors(adata)
@@ -115,46 +115,46 @@ def prepare(data, layout, recipe, output, plotting, sparse, overwrite,
sc.tl.louvain(adata) sc.tl.louvain(adata)
def run_layout(adata): def run_layout(adata):
if len(unique(adata.obs['louvain'].values)) < 10: if len(unique(adata.obs["louvain"].values)) < 10:
palette = 'tab10' palette = "tab10"
else: else:
palette = 'tab20' palette = "tab20"
if 'umap' in layout: if "umap" in layout:
sc.tl.umap(adata) sc.tl.umap(adata)
if plotting: if plotting:
sc.pl.umap(adata, color='louvain', palette=palette, save='_louvain') sc.pl.umap(adata, color="louvain", palette=palette, save="_louvain")
if 'tsne' in layout: if "tsne" in layout:
sc.tl.tsne(adata) sc.tl.tsne(adata)
if plotting: if plotting:
sc.pl.tsne(adata, color='louvain', palette=palette, save='_louvain') sc.pl.tsne(adata, color="louvain", palette=palette, save="_louvain")
def show_step(item): def show_step(item):
names = { names = {
'make_sparse': 'Ensuring sparsity', "make_sparse": "Ensuring sparsity",
'run_recipe': 'Running preprocessing recipe "%s"' % recipe, "run_recipe": f"Running preprocessing recipe \"{recipe}\"",
'run_pca': 'Running PCA', "run_pca": "Running PCA",
'run_neighbors': 'Calculating neighbors', "run_neighbors": "Calculating neighbors",
'run_louvain': 'Calculating clusters', "run_louvain": "Calculating clusters",
'run_layout': 'Computing layout' "run_layout": "Computing layout"
} }
if item is not None: if item is not None:
return names[item.__name__] return names[item.__name__]
steps = [make_sparse, run_recipe, run_pca, run_neighbors, run_louvain, run_layout] steps = [make_sparse, run_recipe, run_pca, run_neighbors, run_louvain, run_layout]
click.echo(f'[cellxgene] Loading data from {data}, please wait...') click.echo(f"[cellxgene] Loading data from {data}, please wait...")
adata = load_data(data) adata = load_data(data)
click.echo('[cellxgene] Beginning preprocessing...') click.echo("[cellxgene] Beginning preprocessing...")
with click.progressbar(steps, label='[cellxgene] Progress', show_eta=False, item_show_func=show_step) as bar: with click.progressbar(steps, label="[cellxgene] Progress", show_eta=False, item_show_func=show_step) as bar:
for step in bar: for step in bar:
step(adata) step(adata)
# saving # saving
if not output == '': if not output == "":
click.echo(f'[cellxgene] Saving results to {output}...') click.echo(f"[cellxgene] Saving results to {output}...")
adata.write(output) adata.write(output)
click.echo('[cellxgene] Success!') click.echo("[cellxgene] Success!")
-3
View File
@@ -1,3 +0,0 @@
from app.app import app
app.run(host="0.0.0.0", debug=True, port=5005)
+2 -1
View File
@@ -34,7 +34,8 @@ class UtilTest(unittest.TestCase):
@pytest.mark.filterwarnings("ignore:Scanpy data matrix") @pytest.mark.filterwarnings("ignore:Scanpy data matrix")
def test_data_type(self): def test_data_type(self):
self.data.data.X = self.data.data.X.astype("float64") self.data.data.X = self.data.data.X.astype("float64")
self.assertWarns(UserWarning, self.data._validate_data_types()) with self.assertWarns(UserWarning):
self.data._validate_data_types()
def test_filter_idx(self): def test_filter_idx(self):
filter_ = { filter_ = {