Compare commits

...
2 Commits
Author SHA1 Message Date
Charlotte Weaver f2612707bb bumped version (#466) 2018-11-26 11:42:56 -08:00
Bruce Martin 138d30909a improve type handling for non-string annotation data (#465)
* improve type handling for non-string annotation data

* improve clarity of code
2018-11-26 10:32:29 -08:00
13 changed files with 278 additions and 164 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.2.0 current_version = 0.2.1
[bumpversion:file:setup.py] [bumpversion:file:setup.py]
search = version="{current_version}" search = version="{current_version}"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "cellxgene", "name": "cellxgene",
"version": "0.1.0", "version": "0.2.1",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "cellxgene", "name": "cellxgene",
"version": "0.2.0", "version": "0.2.1",
"license": "MIT", "license": "MIT",
"description": "cellxgene is a web application for the interactive exploration of single cell sequence data.", "description": "cellxgene is a web application for the interactive exploration of single cell sequence data.",
"repository": "https://github.com/chanzuckerberg/cellxgene", "repository": "https://github.com/chanzuckerberg/cellxgene",
@@ -5,34 +5,13 @@ import { connect } from "react-redux";
import * as globals from "../../globals"; import * as globals from "../../globals";
import Category from "./category"; import Category from "./category";
/* Cap the max number of displayed categories */
const truncateCategories = options => {
const numOptions = _.size(options);
if (numOptions <= globals.maxCategoricalOptionsToDisplay) {
return options;
}
return _(options)
.map((v, k) => ({ name: k, val: v }))
.sortBy("val")
.slice(numOptions - globals.maxCategoricalOptionsToDisplay)
.transform((r, v) => {
r[v.name] = v.val;
}, {})
.value();
};
@connect(state => ({ @connect(state => ({
ranges: _.get(state.controls.world, "summary.obs", null), categoricalSelectionState: state.controls.categoricalSelectionState
categorySelectionLimit: _.get(
state.config,
"parameters.max-category-items",
globals.configDefaults.parameters["max-category-items"]
)
})) }))
class Categories extends React.Component { class Categories extends React.Component {
render() { render() {
const { ranges, categorySelectionLimit } = this.props; const { categoricalSelectionState } = this.props;
if (!ranges) return null; if (!categoricalSelectionState) return null;
return ( return (
<div <div
@@ -47,27 +26,9 @@ class Categories extends React.Component {
> >
Categorical Metadata Categorical Metadata
</p> </p>
{_.map(ranges, (value, key) => { {_.map(categoricalSelectionState, (catState, catName) => (
const isColorField = key.includes("color") || key.includes("Color"); <Category key={catName} metadataField={catName} />
const isSelectableCategory = ))}
value.options &&
!isColorField &&
key !== "name" &&
value.numOptions < categorySelectionLimit;
if (isSelectableCategory) {
const categoryOptions = truncateCategories(value.options);
return (
<Category
key={key}
metadataField={key}
values={categoryOptions}
isTruncated={categoryOptions !== value.options}
/>
);
}
return undefined;
})}
</div> </div>
); );
} }
+34 -38
View File
@@ -2,29 +2,15 @@ import React from "react";
import _ from "lodash"; import _ from "lodash";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { FaChevronRight, FaChevronDown } from "react-icons/fa"; import { FaChevronRight, FaChevronDown } from "react-icons/fa";
import memoize from "memoize-one"; import { Button, Tooltip } from "@blueprintjs/core";
import { Button, Tooltip, Position } from "@blueprintjs/core";
import * as globals from "../../globals"; import * as globals from "../../globals";
import Value from "./value"; import Value from "./value";
import alphabeticallySortedValues from "./util"; import alphabeticallySortedValues from "./util";
const countCategories = (values, optsAsBools) =>
_.reduce(
values,
(r, v, k) => {
r.total += 1;
if (optsAsBools[k]) {
r.on += 1;
}
return r;
},
{ total: 0, on: 0 }
);
@connect(state => ({ @connect(state => ({
colorAccessor: state.controls.colorAccessor, colorAccessor: state.controls.colorAccessor,
categoricalAsBooleansMap: state.controls.categoricalAsBooleansMap categoricalSelectionState: state.controls.categoricalSelectionState
})) }))
class Category extends React.Component { class Category extends React.Component {
constructor(props) { constructor(props) {
@@ -33,24 +19,30 @@ class Category extends React.Component {
isChecked: true, isChecked: true,
isExpanded: false isExpanded: false
}; };
this.countCategories = memoize((values, optsAsBools) =>
countCategories(values, optsAsBools)
);
} }
componentDidUpdate() { componentDidUpdate() {
const { categoricalAsBooleansMap, metadataField, values } = this.props; const { categoricalSelectionState, metadataField } = this.props;
const categoryCount = this.countCategories( const cat = categoricalSelectionState[metadataField];
values, const categoryCount = {
categoricalAsBooleansMap[metadataField] // total number of options in this category
); totalOptionCount: cat.numOptions,
if (categoryCount.on === categoryCount.total) { // number of selected options in this category
selectedOptionCount: _.reduce(
cat.optionSelected,
(res, cond) => (cond ? res + 1 : res),
0
)
};
if (categoryCount.selectedOptionCount === categoryCount.totalOptionCount) {
/* everything is on, so not indeterminate */ /* everything is on, so not indeterminate */
this.checkbox.indeterminate = false; this.checkbox.indeterminate = false;
} else if (categoryCount.on === 0) { } else if (categoryCount.selectedOptionCount === 0) {
/* nothing is on, so no */ /* nothing is on, so no */
this.checkbox.indeterminate = false; this.checkbox.indeterminate = false;
} else if (categoryCount.on < categoryCount.total) { } else if (
categoryCount.selectedOptionCount < categoryCount.totalOptionCount
) {
/* to be explicit... */ /* to be explicit... */
this.checkbox.indeterminate = true; this.checkbox.indeterminate = true;
} }
@@ -74,11 +66,10 @@ class Category extends React.Component {
} }
toggleNone() { toggleNone() {
const { dispatch, metadataField, value } = this.props; const { dispatch, metadataField } = this.props;
dispatch({ dispatch({
type: "categorical metadata filter none of these", type: "categorical metadata filter none of these",
metadataField, metadataField
value
}); });
this.setState({ isChecked: false }); this.setState({ isChecked: false });
} }
@@ -94,13 +85,15 @@ class Category extends React.Component {
} }
renderCategoryItems() { renderCategoryItems() {
const { values, metadataField } = this.props; const { categoricalSelectionState, metadataField } = this.props;
return _.map(alphabeticallySortedValues(values), (v, i) => (
const cat = categoricalSelectionState[metadataField];
const optTuples = alphabeticallySortedValues([...cat.optionIndex]);
return _.map(optTuples, (tuple, i) => (
<Value <Value
key={v} key={tuple[1]}
metadataField={metadataField} metadataField={metadataField}
count={values[v]} optionIndex={tuple[1]}
value={v}
i={i} i={i}
/> />
)); ));
@@ -108,12 +101,15 @@ class Category extends React.Component {
render() { render() {
const { isExpanded, isChecked } = this.state; const { isExpanded, isChecked } = this.state;
const { metadataField, colorAccessor, isTruncated } = this.props; const {
metadataField,
colorAccessor,
categoricalSelectionState
} = this.props;
const { isTruncated } = categoricalSelectionState[metadataField];
return ( return (
<div <div
style={{ style={{
// display: "flex",
// alignItems: "baseline",
maxWidth: globals.maxControlsWidth maxWidth: globals.maxControlsWidth
}} }}
> >
+7 -3
View File
@@ -1,7 +1,11 @@
// jshint esversion: 6 // jshint esversion: 6
// values is [ [optVal, optIdx], ...]
// index is range array
// return sorted index
export default values => export default values =>
Object.keys(values).sort((a, b) => { values.sort((a, b) => {
const textA = a.toUpperCase(); const textA = String(a[0]).toUpperCase();
const textB = b.toUpperCase(); const textB = String(b[0]).toUpperCase();
return textA < textB ? -1 : textA > textB ? 1 : 0; return textA < textB ? -1 : textA > textB ? 1 : 0;
}); });
+15 -11
View File
@@ -4,45 +4,49 @@ import React from "react";
import _ from "lodash"; import _ from "lodash";
@connect(state => ({ @connect(state => ({
categoricalAsBooleansMap: state.controls.categoricalAsBooleansMap, categoricalSelectionState: state.controls.categoricalSelectionState,
colorScale: state.controls.colorScale, colorScale: state.controls.colorScale,
colorAccessor: state.controls.colorAccessor, colorAccessor: state.controls.colorAccessor,
schema: _.get(state.controls.world, "schema", null) schema: _.get(state.controls.world, "schema", null)
})) }))
class CategoryValue extends React.Component { class CategoryValue extends React.Component {
toggleOff() { toggleOff() {
const { dispatch, metadataField, value } = this.props; const { dispatch, metadataField, optionIndex } = this.props;
dispatch({ dispatch({
type: "categorical metadata filter deselect", type: "categorical metadata filter deselect",
metadataField, metadataField,
value optionIndex
}); });
} }
toggleOn() { toggleOn() {
const { dispatch, metadataField, value } = this.props; const { dispatch, metadataField, optionIndex } = this.props;
dispatch({ dispatch({
type: "categorical metadata filter select", type: "categorical metadata filter select",
metadataField, metadataField,
value optionIndex
}); });
} }
render() { render() {
const { const {
categoricalAsBooleansMap, categoricalSelectionState,
metadataField, metadataField,
count, optionIndex,
value,
colorAccessor, colorAccessor,
colorScale, colorScale,
i, i,
schema schema
} = this.props; } = this.props;
if (!categoricalAsBooleansMap) return null; if (!categoricalSelectionState) return null;
const category = categoricalSelectionState[metadataField];
const selected = category.optionSelected[optionIndex];
const count = category.optionCount[optionIndex];
const value = category.optionValue[optionIndex];
const displayString = String(category.optionValue[optionIndex]).valueOf();
const selected = categoricalAsBooleansMap[metadataField][value];
/* this is the color scale, so add swatches below */ /* this is the color scale, so add swatches below */
const c = metadataField === colorAccessor; const c = metadataField === colorAccessor;
let categories = null; let categories = null;
@@ -78,7 +82,7 @@ class CategoryValue extends React.Component {
type="checkbox" type="checkbox"
/> />
<span className="bp3-control-indicator" /> <span className="bp3-control-indicator" />
{value} {displayString}
</label> </label>
</div> </div>
<span> <span>
+151 -55
View File
@@ -12,34 +12,109 @@ import {
diffexpDimensionName, diffexpDimensionName,
makeContinuousDimensionName makeContinuousDimensionName
} from "../util/nameCreators"; } from "../util/nameCreators";
import { fillRange } from "../util/typedCrossfilter/util";
function createCategoricalAsBooleansMap(world) { /*
Selection state for categoricals are tracked in an Object that
has two main components for each category:
1. mapping of option value to an index
2. array of bool selection state by index
Remember that option values can be ANY js type, except undefined/null.
{
_category_name_1: {
// map of option value to index
optionIndex: Map([
optval1: index,
...
])
// index->selection true/false state
optionSelected: [ true/false, true/false, ... ]
// number of options
numOptions: number,
// isTruncated - true if the options for selection has
// been truncated (ie, was too large to implement)
}
}
*/
function topNoptions(summary) {
const counts = _.map(summary.categories, cat => summary.options[cat]);
const sortIndex = fillRange(new Array(summary.numOptions)).sort(
(a, b) => counts[b] - counts[a]
);
const sortedCategories = _.map(sortIndex, i => summary.categories[i]);
const sortedCounts = _.map(sortIndex, i => counts[i]);
const N = globals.maxCategoricalOptionsToDisplay;
if (sortedCategories.length < N) {
return [sortedCategories, sortedCounts];
}
return [sortedCategories.slice(0, N), sortedCounts.slice(0, N)];
}
function createCategoricalSelectionState(state, world) {
const res = {}; const res = {};
_.each(world.summary.obs, (value, key) => { _.forEach(world.summary.obs, (value, key) => {
if (value.options && key !== "name") { if (value.categories) {
const optionsAsBooleans = {}; const isColorField = key.includes("color") || key.includes("Color");
_.each(value.options, (_value, _key) => { const isSelectableCategory =
optionsAsBooleans[_key] = true; !isColorField &&
}); key !== "name" &&
res[key] = optionsAsBooleans; value.categories.length < state.maxCategoryItems;
if (isSelectableCategory) {
const [optionValue, optionCount] = topNoptions(value);
// const optionCount = Object.values(value.options);
const optionIndex = new Map(optionValue.map((v, i) => [v, i]));
const numOptions = optionIndex.size;
const optionSelected = new Array(numOptions).fill(true);
const isTruncated = optionValue.length < value.numOptions;
res[key] = {
optionValue, // array: of natively typed option values
optionIndex, // map: option value (native type) -> option index
optionSelected, // array: t/f selection state
numOptions, // number: of options
isTruncated, // bool: true if list was truncated
optionCount // array: cardinality of each option
};
}
} }
}); });
return res; return res;
} }
/*
given a categoricalSelectionState, return the list of all option values
where selection state is true (ie, they are selected).
*/
function selectedValuesForCategory(categorySelectionState) {
const selectedValues = _([...categorySelectionState.optionIndex])
.filter(tuple => categorySelectionState.optionSelected[tuple[1]])
.map(tuple => tuple[0])
.value();
return selectedValues;
}
const Controls = ( const Controls = (
state = { state = {
// data loading flag // data loading flag
loading: false, loading: false,
error: null, error: null,
// configuration
maxCategoryItems: globals.configDefaults.parameters["max-category-items"],
// the whole big bang
universe: null, universe: null,
// all of the data + selection state // all of the data + selection state
world: null, world: null,
colorName: null, colorName: null,
colorRGB: null, colorRGB: null,
categoricalAsBooleansMap: null, categoricalSelectionState: null,
crossfilter: null, crossfilter: null,
dimensionMap: null, dimensionMap: null,
userDefinedGenes: [], userDefinedGenes: [],
@@ -72,6 +147,17 @@ const Controls = (
Initialization, World/Universe management Initialization, World/Universe management
and data loading. and data loading.
******************************************************/ ******************************************************/
case "configuration load complete": {
// there are a couple of configuration items we need to retain
return {
...state,
maxCategoryItems: _.get(
state.config,
"parameters.max-category-items",
globals.configDefaults.parameters["max-category-items"]
)
};
}
case "initial data load start": { case "initial data load start": {
return { ...state, loading: true }; return { ...state, loading: true };
} }
@@ -83,7 +169,10 @@ const Controls = (
const world = World.createWorldFromEntireUniverse(universe); const world = World.createWorldFromEntireUniverse(universe);
const colorName = new Array(universe.nObs).fill(globals.defaultCellColor); const colorName = new Array(universe.nObs).fill(globals.defaultCellColor);
const colorRGB = _.map(colorName, c => parseRGB(c)); const colorRGB = _.map(colorName, c => parseRGB(c));
const categoricalAsBooleansMap = createCategoricalAsBooleansMap(world); const categoricalSelectionState = createCategoricalSelectionState(
state,
world
);
const crossfilter = Crossfilter(world.obsAnnotations); const crossfilter = Crossfilter(world.obsAnnotations);
const dimensionMap = World.createObsDimensionMap(crossfilter, world); const dimensionMap = World.createObsDimensionMap(crossfilter, world);
@@ -135,7 +224,7 @@ const Controls = (
world, world,
colorName, colorName,
colorRGB, colorRGB,
categoricalAsBooleansMap, categoricalSelectionState,
crossfilter, crossfilter,
dimensionMap, dimensionMap,
colorAccessor: null colorAccessor: null
@@ -152,7 +241,10 @@ const Controls = (
); );
const colorName = new Array(world.nObs).fill(globals.defaultCellColor); const colorName = new Array(world.nObs).fill(globals.defaultCellColor);
const colorRGB = _.map(colorName, c => parseRGB(c)); const colorRGB = _.map(colorName, c => parseRGB(c));
const categoricalAsBooleansMap = createCategoricalAsBooleansMap(world); const categoricalSelectionState = createCategoricalSelectionState(
state,
world
);
const crossfilter = Crossfilter(world.obsAnnotations); const crossfilter = Crossfilter(world.obsAnnotations);
const dimensionMap = World.createObsDimensionMap(crossfilter, world); const dimensionMap = World.createObsDimensionMap(crossfilter, world);
@@ -197,7 +289,7 @@ const Controls = (
world, world,
colorName, colorName,
colorRGB, colorRGB,
categoricalAsBooleansMap, categoricalSelectionState,
crossfilter, crossfilter,
dimensionMap, dimensionMap,
colorAccessor: null colorAccessor: null
@@ -422,83 +514,87 @@ const Controls = (
Categorical metadata Categorical metadata
*******************************/ *******************************/
case "categorical metadata filter select": { case "categorical metadata filter select": {
const newCategoricalAsBooleansMap = { const newOptionSelected = Array.from(
...state.categoricalAsBooleansMap, state.categoricalSelectionState[action.metadataField].optionSelected
);
newOptionSelected[action.optionIndex] = true;
const newCategoricalSelectionState = {
...state.categoricalSelectionState,
[action.metadataField]: { [action.metadataField]: {
...state.categoricalAsBooleansMap[action.metadataField], ...state.categoricalSelectionState[action.metadataField],
[action.value]: true optionSelected: newOptionSelected
} }
}; };
// update the filter for the one category that changed state
// update the filter to match all selected options
const cat = newCategoricalSelectionState[action.metadataField];
state.dimensionMap[obsAnnoDimensionName(action.metadataField)].filterEnum( state.dimensionMap[obsAnnoDimensionName(action.metadataField)].filterEnum(
_.filter( selectedValuesForCategory(cat)
_.map(
newCategoricalAsBooleansMap[action.metadataField],
(val, key) => (val ? key : false)
)
)
); );
return { return {
...state, ...state,
categoricalAsBooleansMap: newCategoricalAsBooleansMap categoricalSelectionState: newCategoricalSelectionState
}; };
} }
case "categorical metadata filter deselect": { case "categorical metadata filter deselect": {
const newCategoricalAsBooleansMap = { const newOptionSelected = Array.from(
...state.categoricalAsBooleansMap, state.categoricalSelectionState[action.metadataField].optionSelected
);
newOptionSelected[action.optionIndex] = false;
const newCategoricalSelectionState = {
...state.categoricalSelectionState,
[action.metadataField]: { [action.metadataField]: {
...state.categoricalAsBooleansMap[action.metadataField], ...state.categoricalSelectionState[action.metadataField],
[action.value]: false optionSelected: newOptionSelected
} }
}; };
// update the filter for the one category that changed state
// update the filter to match all selected options
const cat = newCategoricalSelectionState[action.metadataField];
state.dimensionMap[obsAnnoDimensionName(action.metadataField)].filterEnum( state.dimensionMap[obsAnnoDimensionName(action.metadataField)].filterEnum(
_.filter( selectedValuesForCategory(cat)
_.map(
newCategoricalAsBooleansMap[action.metadataField],
(val, key) => (val ? key : false)
)
)
); );
return { return {
...state, ...state,
categoricalAsBooleansMap: newCategoricalAsBooleansMap categoricalSelectionState: newCategoricalSelectionState
}; };
} }
case "categorical metadata filter none of these": { case "categorical metadata filter none of these": {
const newCategoricalAsBooleansMap = { const newCategoricalSelectionState = {
...state.categoricalAsBooleansMap ...state.categoricalSelectionState,
}; [action.metadataField]: {
_.forEach( ...state.categoricalSelectionState[action.metadataField],
newCategoricalAsBooleansMap[action.metadataField], optionSelected: Array.from(
(v, k, c) => { state.categoricalSelectionState[action.metadataField].optionSelected
c[k] = false; ).fill(false)
} }
); };
state.dimensionMap[ state.dimensionMap[
obsAnnoDimensionName(action.metadataField) obsAnnoDimensionName(action.metadataField)
].filterNone(); ].filterNone();
return { return {
...state, ...state,
categoricalAsBooleansMap: newCategoricalAsBooleansMap categoricalSelectionState: newCategoricalSelectionState
}; };
} }
case "categorical metadata filter all of these": { case "categorical metadata filter all of these": {
const newCategoricalAsBooleansMap = { const newCategoricalSelectionState = {
...state.categoricalAsBooleansMap ...state.categoricalSelectionState,
}; [action.metadataField]: {
_.forEach( ...state.categoricalSelectionState[action.metadataField],
newCategoricalAsBooleansMap[action.metadataField], optionSelected: Array.from(
(v, k, c) => { state.categoricalSelectionState[action.metadataField].optionSelected
c[k] = true; ).fill(true)
} }
); };
state.dimensionMap[ state.dimensionMap[
obsAnnoDimensionName(action.metadataField) obsAnnoDimensionName(action.metadataField)
].filterAll(); ].filterAll();
return { return {
...state, ...state,
categoricalAsBooleansMap: newCategoricalAsBooleansMap categoricalSelectionState: newCategoricalSelectionState
}; };
} }
@@ -48,6 +48,12 @@ Example:
NOTE: will not summarize the required 'name' annotation, as that is NOTE: will not summarize the required 'name' annotation, as that is
specified as unique per element. specified as unique per element.
TODO: XXX - this data structure coerces all metadata categories into a string
(ie, stores values as an Object property in the `options` field). This looses
information (eg, type) for category types which are not strings. Consider an
alterative data structure that does not use the object property for non-string
data types (and does not use _.countBy to summarize).
*/ */
function summarizeDimension(schema, annotations) { function summarizeDimension(schema, annotations) {
return _(schema) return _(schema)
@@ -58,11 +64,13 @@ function summarizeDimension(schema, annotations) {
const continuous = type === "int32" || type === "float32"; const continuous = type === "int32" || type === "float32";
if (!continuous) { if (!continuous) {
const categories = _.uniq(_.flatMap(annotations, name));
const options = _.countBy(annotations, name); const options = _.countBy(annotations, name);
const numOptions = _.size(options); const numOptions = _.size(options);
return { return {
numOptions, numOptions,
options options,
categories
}; };
} }
+27
View File
@@ -161,6 +161,32 @@ function RESTv02LayoutResponseToInternal(response) {
return layout; return layout;
} }
function reconcileSchemaCategoriesWithSummary(universe) {
/*
where we treat types as (essentially) categorical metadata, update
the schema with data-derived categories (in addition to those in
the server declared schema).
For example, boolean defined fields in the schema do not contain
explicit declaration of categories (nor do string fields). In these
cases, add a 'categories' field to the schema so it is accessible.
*/
_.forEach(universe.schema.annotations.obs, s => {
if (
s.type === "string" ||
s.type === "boolean" ||
s.type === "categorical"
) {
const categories = _.union(
_.get(s, "categories", []),
_.get(universe.summary.obs[s.name], "categories", [])
);
s.categories = categories;
}
});
}
export function createUniverseFromRestV02Response( export function createUniverseFromRestV02Response(
configResponse, configResponse,
schemaResponse, schemaResponse,
@@ -199,6 +225,7 @@ export function createUniverseFromRestV02Response(
universe.varAnnotations universe.varAnnotations
); );
reconcileSchemaCategoriesWithSummary(universe);
return finalize(universe); return finalize(universe);
} }
+24 -6
View File
@@ -65,6 +65,22 @@ class ScanpyEngine(CXGDriver):
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.")
@staticmethod
def _can_cast_to_float32(ann):
if ann.dtype.kind == "f" and np.can_cast(ann.dtype, np.float32):
return True
return False
@staticmethod
def _can_cast_to_int32(ann):
if ann.dtype.kind in ["i", "u"]:
if np.can_cast(ann.dtype, np.int32):
return True
ii32 = np.iinfo(np.int32)
if ann.min() >= ii32.min and ann.max() <= ii32.max:
return True
return False
def _create_schema(self): def _create_schema(self):
self.schema = { self.schema = {
"dataframe": { "dataframe": {
@@ -81,16 +97,18 @@ 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:
ann_schema = {"name": ann} ann_schema = {"name": ann}
data_kind = curr_axis[ann].dtype.kind dtype = curr_axis[ann].dtype
if data_kind == "f": data_kind = dtype.kind
if self._can_cast_to_float32(curr_axis[ann]):
ann_schema["type"] = "float32" ann_schema["type"] = "float32"
elif data_kind in ["i", "u"]: elif self._can_cast_to_int32(curr_axis[ann]):
ann_schema["type"] = "int32" ann_schema["type"] = "int32"
elif data_kind == "?": elif dtype == np.bool_:
ann_schema["type"] = "boolean" ann_schema["type"] = "boolean"
elif data_kind == "O" and curr_axis[ann].dtype == "object": elif data_kind == "O" and dtype == "object":
ann_schema["type"] = "string" ann_schema["type"] = "string"
elif data_kind == "O" and curr_axis[ann].dtype == "category": elif data_kind == "O" and dtype == "category":
ann_schema["type"] = "categorical" ann_schema["type"] = "categorical"
ann_schema["categories"] = curr_axis[ann].dtype.categories.tolist() ann_schema["categories"] = curr_axis[ann].dtype.categories.tolist()
else: else:
+1 -1
View File
@@ -5,7 +5,7 @@ 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.2.0", prog_name="cellxgene", message="[%(prog)s] Version %(version)s") @click.version_option(version="0.2.1", prog_name="cellxgene", message="[%(prog)s] Version %(version)s")
def cli(): def cli():
pass pass
+1 -1
View File
@@ -8,7 +8,7 @@ with open("server/requirements.txt") as fh:
setup( setup(
name="cellxgene", name="cellxgene",
version="0.2.0", version="0.2.1",
packages=find_packages(), packages=find_packages(),
url="https://github.com/chanzuckerberg/cellxgene", url="https://github.com/chanzuckerberg/cellxgene",
license="MIT", license="MIT",