mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-24 01:58:12 +08:00
Launch validation (#414)
* WIP * Cleanup * Validation * typo * Max category limit 100 -> 500
This commit is contained in:
@@ -14,9 +14,9 @@ class CXGDriver(metaclass=ABCMeta):
|
||||
|
||||
def __init__(self, data, args):
|
||||
self.data = self._load_data(data)
|
||||
self.layout_method = args['layout']
|
||||
self.diffexp_method = args['diffexp']
|
||||
self.max_category_items = args['max_category_items']
|
||||
self.layout_method = args["layout"]
|
||||
self.diffexp_method = args["diffexp"]
|
||||
self.max_category_items = args["max_category_items"]
|
||||
self.cluster = None
|
||||
|
||||
@property
|
||||
|
||||
@@ -10,7 +10,8 @@ from werkzeug.datastructures import ImmutableMultiDict
|
||||
from server.app.util.constants import Axis, DiffExpMode
|
||||
from server.app.util.filter import parse_filter, QueryStringError
|
||||
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
|
||||
|
||||
@@ -2,12 +2,13 @@ import warnings
|
||||
|
||||
import numpy as np
|
||||
from pandas import DataFrame
|
||||
from pandas.core.dtypes.dtypes import CategoricalDtype
|
||||
import scanpy.api as sc
|
||||
from scipy import stats, sparse
|
||||
|
||||
from server.app.driver.driver import CXGDriver
|
||||
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
|
||||
@@ -23,9 +24,10 @@ class ScanpyEngine(CXGDriver):
|
||||
|
||||
def __init__(self, data, args):
|
||||
super().__init__(data, args)
|
||||
self._alias_annotation_names(Axis.OBS, args['obs_names'])
|
||||
self._alias_annotation_names(Axis.VAR, args['var_names'])
|
||||
self._alias_annotation_names(Axis.OBS, args["obs_names"])
|
||||
self._alias_annotation_names(Axis.VAR, args["var_names"])
|
||||
self._validate_data_types()
|
||||
self._validate_data_calculations()
|
||||
self.cell_count = self.data.shape[0]
|
||||
self.gene_count = self.data.shape[1]
|
||||
self.layout_options = ["umap", "tsne"]
|
||||
@@ -39,27 +41,28 @@ class ScanpyEngine(CXGDriver):
|
||||
As a *critical* side-effect, ensure the indices are simple number ranges
|
||||
(accomplished by calling pandas.DataFrame.reset_index())
|
||||
"""
|
||||
if name == 'name':
|
||||
if name == "name":
|
||||
# a noop, so skip it
|
||||
return
|
||||
|
||||
ax_name = str(axis)
|
||||
df_axis = getattr(self.data, ax_name)
|
||||
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.
|
||||
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:
|
||||
if name not in df_axis.columns:
|
||||
raise KeyError(f"Annotation name {name}, specified in --{ax_name}-name does not exist.")
|
||||
if not df_axis[name].is_unique:
|
||||
raise KeyError(f"Values in -{ax_name}-name must be unique. "
|
||||
"Please prepare data to contain unique values.")
|
||||
# reset index to simple range; alias user-specified annotation to 'name'
|
||||
df_axis = df_axis.reset_index(drop=True).rename(columns={name: 'name'})
|
||||
# reset index to simple range; alias user-specified annotation to "name"
|
||||
df_axis.reset_index(drop=True, inplace=True)
|
||||
df_axis.rename(inplace=True, columns={name: "name"})
|
||||
else:
|
||||
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):
|
||||
self.schema = {
|
||||
@@ -78,9 +81,9 @@ class ScanpyEngine(CXGDriver):
|
||||
for ann in curr_axis:
|
||||
ann_schema = {"name": ann}
|
||||
data_kind = curr_axis[ann].dtype.kind
|
||||
if data_kind == 'f':
|
||||
if data_kind == "f":
|
||||
ann_schema["type"] = "float32"
|
||||
elif data_kind in ['i', 'u']:
|
||||
elif data_kind in ["i", "u"]:
|
||||
ann_schema["type"] = "int32"
|
||||
elif data_kind == "?":
|
||||
ann_schema["type"] = "boolean"
|
||||
@@ -98,7 +101,18 @@ class ScanpyEngine(CXGDriver):
|
||||
# Based on benchmarking, cache=True has no impact on perf.
|
||||
# Note: as of current scanpy/anndata release, setting backed='r' will
|
||||
# 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
|
||||
def _top_sort(values, sort_order, top_n=None):
|
||||
@@ -137,14 +151,34 @@ class ScanpyEngine(CXGDriver):
|
||||
curr_axis = getattr(self.data, str(ax))
|
||||
for ann in curr_axis:
|
||||
datatype = curr_axis[ann].dtype
|
||||
downcast_map = {'int64': 'int32',
|
||||
'uint32': 'int32',
|
||||
'uint64': 'int32',
|
||||
'float64': 'float32',
|
||||
downcast_map = {"int64": "int32",
|
||||
"uint32": "int32",
|
||||
"uint64": "int32",
|
||||
"float64": "float32",
|
||||
}
|
||||
if datatype in downcast_map:
|
||||
warnings.warn(f"Scanpy annotation {ax}:{ann} is in unsupported format: {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):
|
||||
"""
|
||||
@@ -232,8 +266,8 @@ class ScanpyEngine(CXGDriver):
|
||||
|
||||
https://docs.scipy.org/doc/scipy/reference/sparse.html
|
||||
"""
|
||||
prefer_row_access = sparse.isspmatrix_csr(data._X) or \
|
||||
sparse.isspmatrix_lil(data._X) or sparse.isspmatrix_bsr(data._X)
|
||||
prefer_row_access = sparse.isspmatrix_csr(data._X) or sparse.isspmatrix_lil(data._X) \
|
||||
or sparse.isspmatrix_bsr(data._X)
|
||||
if prefer_row_access:
|
||||
# Row-major slicing
|
||||
if obs_selector is not None:
|
||||
|
||||
@@ -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
|
||||
@@ -3,6 +3,8 @@ from argparse import ArgumentTypeError
|
||||
|
||||
from numpy import float32, integer
|
||||
|
||||
from server.app.util.errors import MimeTypeError
|
||||
|
||||
|
||||
class Float32JSONEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
@@ -13,30 +15,6 @@ class Float32JSONEncoder(json.JSONEncoder):
|
||||
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,
|
||||
header=None):
|
||||
mime_type = default
|
||||
|
||||
+2
-2
@@ -4,8 +4,8 @@ from .launch import launch
|
||||
from .prepare import prepare
|
||||
|
||||
|
||||
@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.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")
|
||||
def cli():
|
||||
pass
|
||||
|
||||
|
||||
+42
-33
@@ -1,31 +1,32 @@
|
||||
import sys
|
||||
import click
|
||||
import logging
|
||||
from os.path import splitext, basename
|
||||
import webbrowser
|
||||
|
||||
from os.path import splitext, basename
|
||||
from server.app.util.errors import ScanpyFileError
|
||||
|
||||
|
||||
@click.command()
|
||||
@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,
|
||||
help='Method for layout.')
|
||||
@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', '-d', 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.", metavar='', default=5005, 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('--listen-all', is_flag=True, default=False, show_default=True,
|
||||
help='Bind to all interfaces (this makes the server accessible beyond this computer).')
|
||||
@click.option('--max-category-items', default=100, metavar='', show_default=True,
|
||||
help='Limits the number of categorical annotation items displayed.')
|
||||
@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,
|
||||
help="Method for layout.")
|
||||
@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", "-d", 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.", metavar="", default=5005, 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("--listen-all", is_flag=True, default=False, show_default=True,
|
||||
help="Bind to all interfaces (this makes the server accessible beyond this computer).")
|
||||
@click.option("--max-category-items", default=100, metavar="", show_default=True,
|
||||
help="Limits the number of categorical annotation items displayed.")
|
||||
def launch(data, layout, diffexp, title, verbose, debug, obs_names, var_names,
|
||||
open_browser, port, listen_all, max_category_items):
|
||||
"""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>"""
|
||||
|
||||
# Startup message
|
||||
click.echo('[cellxgene] Starting the CLI...')
|
||||
click.echo("[cellxgene] Starting the CLI...")
|
||||
|
||||
# Import Flask app
|
||||
from server.app.app import app
|
||||
|
||||
# Argument checking
|
||||
name, extension = splitext(data)
|
||||
if extension != '.h5ad':
|
||||
raise click.FileError(basename(data), hint='file type must be .h5ad')
|
||||
if extension != ".h5ad":
|
||||
raise click.FileError(basename(data), hint="file type must be .h5ad")
|
||||
|
||||
if debug:
|
||||
verbose = True
|
||||
@@ -62,9 +63,9 @@ def launch(data, layout, diffexp, title, verbose, debug, obs_names, var_names,
|
||||
title = file_parts[0]
|
||||
|
||||
if listen_all:
|
||||
host = '0.0.0.0'
|
||||
host = "0.0.0.0"
|
||||
else:
|
||||
host = '127.0.0.1'
|
||||
host = "127.0.0.1"
|
||||
|
||||
# Setup app
|
||||
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:
|
||||
log = logging.getLogger('werkzeug')
|
||||
log = logging.getLogger("werkzeug")
|
||||
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
|
||||
|
||||
args = {'layout': layout, 'diffexp': diffexp, 'max_category_items': max_category_items,
|
||||
'obs_names': obs_names, 'var_names': var_names}
|
||||
args = {
|
||||
"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:
|
||||
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)
|
||||
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)
|
||||
|
||||
+56
-56
@@ -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 os.path import isfile, isdir, splitext, expanduser, sep
|
||||
|
||||
|
||||
@click.command()
|
||||
@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']),
|
||||
help='Layout algorithm', show_default=True)
|
||||
@click.option('--recipe', '-r', default='none', type=click.Choice(['none', 'seurat', 'zheng17']),
|
||||
help='Preprocessing to run.', show_default=True)
|
||||
@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('--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('--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('--make-obs-names-unique', default=True, is_flag=True,
|
||||
help='Ensure obs index is unique.', show_default=True)
|
||||
@click.option('--make-var-names-unique', default=True, is_flag=True,
|
||||
help='Ensure var index is unique.', show_default=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"]),
|
||||
help="Layout algorithm", show_default=True)
|
||||
@click.option("--recipe", "-r", default="none", type=click.Choice(["none", "seurat", "zheng17"]),
|
||||
help="Preprocessing to run.", show_default=True)
|
||||
@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("--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("--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("--make-obs-names-unique", default=True, is_flag=True,
|
||||
help="Ensure obs index is unique.", show_default=True)
|
||||
@click.option("--make-var-names-unique", default=True, is_flag=True,
|
||||
help="Ensure var index is unique.", show_default=True)
|
||||
def prepare(data, layout, recipe, output, plotting, sparse, overwrite,
|
||||
set_obs_names, set_var_names, make_obs_names_unique, make_var_names_unique):
|
||||
"""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."""
|
||||
|
||||
# collect slow imports here to make CLI startup more responsive
|
||||
click.echo('[cellxgene] Starting CLI...')
|
||||
click.echo("[cellxgene] Starting CLI...")
|
||||
import matplotlib
|
||||
matplotlib.use('Agg')
|
||||
matplotlib.use("Agg")
|
||||
import scanpy.api as sc
|
||||
|
||||
# scanpy settings
|
||||
@@ -43,45 +43,45 @@ def prepare(data, layout, recipe, output, plotting, sparse, overwrite,
|
||||
sc.settings.autosave = True
|
||||
|
||||
# check args
|
||||
if sparse and not recipe == 'none':
|
||||
raise click.UsageError('Cannot use a recipe when forcing sparsity')
|
||||
if sparse and not recipe == "none":
|
||||
raise click.UsageError("Cannot use a recipe when forcing sparsity")
|
||||
|
||||
output = expanduser(output)
|
||||
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):
|
||||
if isfile(data):
|
||||
name, extension = splitext(data)
|
||||
if extension == '.h5ad':
|
||||
if extension == ".h5ad":
|
||||
adata = sc.read_h5ad(data)
|
||||
elif extension == '.loom':
|
||||
elif extension == ".loom":
|
||||
adata = sc.read_loom(data)
|
||||
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):
|
||||
if not data.endswith(sep):
|
||||
data += sep
|
||||
adata = sc.read_10x_mtx(data)
|
||||
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():
|
||||
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]
|
||||
if not set_var_names == '':
|
||||
if not set_var_names == "":
|
||||
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]
|
||||
if make_obs_names_unique:
|
||||
adata.obs_names_make_unique()
|
||||
if make_var_names_unique:
|
||||
adata.var_names_make_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:
|
||||
click.echo('Warning: var index is not unique')
|
||||
click.echo("Warning: var index is not unique")
|
||||
|
||||
return adata
|
||||
|
||||
@@ -90,9 +90,9 @@ def prepare(data, layout, recipe, output, plotting, sparse, overwrite,
|
||||
adata.X = csc_matrix(adata.X)
|
||||
|
||||
def run_recipe(adata):
|
||||
if recipe == 'seurat':
|
||||
if recipe == "seurat":
|
||||
sc.pp.recipe_seurat(adata)
|
||||
elif recipe == 'zheng17':
|
||||
elif recipe == "zheng17":
|
||||
sc.pp.recipe_zheng17(adata)
|
||||
else:
|
||||
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):
|
||||
if sparse:
|
||||
sc.pp.pca(adata, svd_solver='arpack', zero_center=False)
|
||||
sc.pp.pca(adata, svd_solver="arpack", zero_center=False)
|
||||
else:
|
||||
sc.pp.pca(adata, svd_solver='arpack')
|
||||
sc.pp.pca(adata, svd_solver="arpack")
|
||||
|
||||
def run_neighbors(adata):
|
||||
sc.pp.neighbors(adata)
|
||||
@@ -115,46 +115,46 @@ def prepare(data, layout, recipe, output, plotting, sparse, overwrite,
|
||||
sc.tl.louvain(adata)
|
||||
|
||||
def run_layout(adata):
|
||||
if len(unique(adata.obs['louvain'].values)) < 10:
|
||||
palette = 'tab10'
|
||||
if len(unique(adata.obs["louvain"].values)) < 10:
|
||||
palette = "tab10"
|
||||
else:
|
||||
palette = 'tab20'
|
||||
palette = "tab20"
|
||||
|
||||
if 'umap' in layout:
|
||||
if "umap" in layout:
|
||||
sc.tl.umap(adata)
|
||||
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)
|
||||
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):
|
||||
names = {
|
||||
'make_sparse': 'Ensuring sparsity',
|
||||
'run_recipe': 'Running preprocessing recipe "%s"' % recipe,
|
||||
'run_pca': 'Running PCA',
|
||||
'run_neighbors': 'Calculating neighbors',
|
||||
'run_louvain': 'Calculating clusters',
|
||||
'run_layout': 'Computing layout'
|
||||
"make_sparse": "Ensuring sparsity",
|
||||
"run_recipe": f"Running preprocessing recipe \"{recipe}\"",
|
||||
"run_pca": "Running PCA",
|
||||
"run_neighbors": "Calculating neighbors",
|
||||
"run_louvain": "Calculating clusters",
|
||||
"run_layout": "Computing layout"
|
||||
}
|
||||
if item is not None:
|
||||
return names[item.__name__]
|
||||
|
||||
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)
|
||||
|
||||
click.echo('[cellxgene] Beginning preprocessing...')
|
||||
with click.progressbar(steps, label='[cellxgene] Progress', show_eta=False, item_show_func=show_step) as bar:
|
||||
click.echo("[cellxgene] Beginning preprocessing...")
|
||||
with click.progressbar(steps, label="[cellxgene] Progress", show_eta=False, item_show_func=show_step) as bar:
|
||||
for step in bar:
|
||||
step(adata)
|
||||
|
||||
# saving
|
||||
if not output == '':
|
||||
click.echo(f'[cellxgene] Saving results to {output}...')
|
||||
if not output == "":
|
||||
click.echo(f"[cellxgene] Saving results to {output}...")
|
||||
adata.write(output)
|
||||
|
||||
click.echo('[cellxgene] Success!')
|
||||
click.echo("[cellxgene] Success!")
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
from app.app import app
|
||||
|
||||
app.run(host="0.0.0.0", debug=True, port=5005)
|
||||
@@ -34,7 +34,8 @@ class UtilTest(unittest.TestCase):
|
||||
@pytest.mark.filterwarnings("ignore:Scanpy data matrix")
|
||||
def test_data_type(self):
|
||||
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):
|
||||
filter_ = {
|
||||
|
||||
Reference in New Issue
Block a user