diff --git a/server/converters/cxgtool.py b/server/converters/cxgtool.py index de1aadc4..0b8fb3a4 100644 --- a/server/converters/cxgtool.py +++ b/server/converters/cxgtool.py @@ -391,13 +391,13 @@ def create_emb(e_name, emb): def is_valid_embedding(adata, name, arr): """ return True if this layout data is a valid array for front-end presentation: * ndarray, with shape (n_obs, >= 2), dtype float/int/uint - * contains only finite values * follows ScanPy embedding naming conventions + * with all values finite or NaN (no +Inf or -Inf) """ is_valid = type(name) == str and name.startswith("X_") and len(name) > 2 is_valid = is_valid and type(arr) == np.ndarray and arr.dtype.kind in "fiu" is_valid = is_valid and arr.shape[0] == adata.n_obs and arr.shape[1] >= 2 - is_valid = is_valid and np.all(np.isfinite(arr)) + is_valid = is_valid and not np.any(np.isinf(arr)) and not np.all(np.isnan(arr)) return is_valid diff --git a/server/data_anndata/anndata_adaptor.py b/server/data_anndata/anndata_adaptor.py index 1a9b0d17..6022928e 100644 --- a/server/data_anndata/anndata_adaptor.py +++ b/server/data_anndata/anndata_adaptor.py @@ -197,12 +197,13 @@ class AnndataAdaptor(DataAdaptor): def _is_valid_layout(self, arr): """ return True if this layout data is a valid array for front-end presentation: - * ndarray, with shape (n_obs, >= 2), dtype float/int/uint - * contains only finite values + * ndarray, dtype float/int/uint + * with shape (n_obs, >= 2) + * with all values finite or NaN (no +Inf or -Inf) """ is_valid = type(arr) == np.ndarray and arr.dtype.kind in "fiu" is_valid = is_valid and arr.shape[0] == self.data.n_obs and arr.shape[1] >= 2 - is_valid = is_valid and np.all(np.isfinite(arr)) + is_valid = is_valid and not np.any(np.isinf(arr)) and not np.all(np.isnan(arr)) return is_valid def _validate_data_types(self): @@ -291,7 +292,7 @@ class AnndataAdaptor(DataAdaptor): raise PrepareError("No valid layout data.") # cap layouts to MAX_LAYOUTS - return layouts[0:MAX_LAYOUTS] + return valid_layouts[0:MAX_LAYOUTS] def get_embedding_array(self, ename, dims=2): full_embedding = self.data.obsm[f"X_{ename}"] diff --git a/server/data_common/data_adaptor.py b/server/data_common/data_adaptor.py index d0eddf7e..eabec8d1 100644 --- a/server/data_common/data_adaptor.py +++ b/server/data_common/data_adaptor.py @@ -328,8 +328,14 @@ class DataAdaptor(metaclass=ABCMeta): """ # scale isotropically - min = embedding.min(axis=0) - max = embedding.max(axis=0) + try: + min = np.nanmin(embedding, axis=0) + max = np.nanmax(embedding, axis=0) + except RuntimeError: + # indicates entire array was NaN, which should propagate + min = np.NaN + max = np.NaN + scale = np.amax(max - min) normalized_layout = (embedding - min) / scale