From 51d6ba32da8dcab5802b12bfa6034c5d0c66d81c Mon Sep 17 00:00:00 2001 From: Joyce Yan <5653616+joyceyan@users.noreply.github.com> Date: Thu, 28 Sep 2023 12:04:41 -0400 Subject: [PATCH 1/5] verify all obsm keys have X_ --- .../cellxgene_schema/validate.py | 57 +++++++++---------- .../tests/test_schema_compliance.py | 45 +++++++++++++++ 2 files changed, 71 insertions(+), 31 deletions(-) diff --git a/cellxgene_schema_cli/cellxgene_schema/validate.py b/cellxgene_schema_cli/cellxgene_schema/validate.py index f67ce99d1..b2f5c012c 100644 --- a/cellxgene_schema_cli/cellxgene_schema/validate.py +++ b/cellxgene_schema_cli/cellxgene_schema/validate.py @@ -1013,7 +1013,8 @@ def _validate_seurat_convertibility(self): def _validate_obsm(self): """ Validates the embedding dictionary -- it checks that all values of adata.obsm are numpy arrays with the correct - dimension. Adds errors to self.errors if any. Checks that the keys start with "X_" + dimension. Adds errors to self.errors if any. Checks that the keys start with "X_", have no whitespace, and have + a suffix at least 1 character long :rtype none """ @@ -1022,46 +1023,40 @@ def _validate_obsm(self): self.errors.append("No embeddings found in 'adata.obsm'.") return - obsm_with_x_prefix = 0 for key, value in self.adata.obsm.items(): + # Checks for invalid keys if " " in key: self.errors.append(f"Embedding key {key} has whitespace in it, please remove it.") + if not key.startswith("X_"): + self.errors.append(f"Embedding key in 'adata.obsm' {key} does not start with X_") + if len(key) <= 3: + self.errors.append( + f"Embedding key in 'adata.obsm' {key} must start with X_ and have a suffix at least one character long." + ) if not isinstance(value, np.ndarray): self.errors.append( f"All embeddings have to be of 'numpy.ndarray' type, " f"'adata.obsm['{key}']' is {type(value)}')." ) + # Skip over the subsequent checks that require the value to be an array continue - # Embeddings to be shown in cellxgene explorer - if key.startswith("X_"): - obsm_with_x_prefix += 1 - - if len(key) <= 3: - self.errors.append( - f"Embedding key in 'adata.obsm' {key} must have a suffix at least one character long." - ) - if len(value.shape) < 2 or value.shape[0] != self.adata.n_obs or value.shape[1] < 2: - self.errors.append( - f"All embeddings must have as many rows as cells, and at least two columns." - f"'adata.obsm['{key}']' has shape of '{value.shape}'." - ) - if not (np.issubdtype(value.dtype, np.integer) or np.issubdtype(value.dtype, np.floating)): - self.errors.append( - f"adata.obsm['{key}'] has an invalid data type. It should be " - "float, integer, or unsigned integer of any precision (8, 16, 32, or 64 bits)." - ) - else: - # Check for inf/NaN values only if the dtype is numeric - if np.isinf(value).any(): - self.errors.append( - f"adata.obsm['{key}'] contains positive infinity or negative infinity values." - ) - if np.all(np.isnan(value)): - self.errors.append(f"adata.obsm['{key}'] contains all NaN values.") - - if obsm_with_x_prefix == 0: - self.errors.append("At least one embedding in 'obsm' has to have a key with an 'X_' prefix.") + if len(value.shape) < 2 or value.shape[0] != self.adata.n_obs or value.shape[1] < 2: + self.errors.append( + f"All embeddings must have as many rows as cells, and at least two columns." + f" 'adata.obsm['{key}']' has shape of '{value.shape}'." + ) + if not (np.issubdtype(value.dtype, np.integer) or np.issubdtype(value.dtype, np.floating)): + self.errors.append( + f"adata.obsm['{key}'] has an invalid data type. It should be " + "float, integer, or unsigned integer of any precision (8, 16, 32, or 64 bits)." + ) + else: + # Check for inf/NaN values only if the dtype is numeric + if np.isinf(value).any(): + self.errors.append(f"adata.obsm['{key}'] contains positive infinity or negative infinity values.") + if np.all(np.isnan(value)): + self.errors.append(f"adata.obsm['{key}'] contains all NaN values.") def _validate_annotation_mapping(self, component_name: str, component: Mapping): for key, value in component.items(): diff --git a/cellxgene_schema_cli/tests/test_schema_compliance.py b/cellxgene_schema_cli/tests/test_schema_compliance.py index a3cf5bf64..eaca2a7d0 100644 --- a/cellxgene_schema_cli/tests/test_schema_compliance.py +++ b/cellxgene_schema_cli/tests/test_schema_compliance.py @@ -1668,6 +1668,29 @@ def test_obsm_suffix_name_valid(self, validator_with_adata): assert validator.errors == [ "ERROR: Embedding key in 'adata.obsm' X_ must have a suffix at least one character long." ] + + def test_obsm_values_must_start_with_X(self): + self.validator.adata.obsm["umap"] = self.validator.adata.obsm["X_umap"] + self.validator.adata.uns["default_embedding"] = "umap" + del self.validator.adata.obsm["X_umap"] + self.validator.validate_adata() + self.assertEqual( + self.validator.errors, + ["ERROR: Embedding key in 'adata.obsm' umap does not start with X_"], + ) + + def test_obsm_suffix_name_valid(self): + """ + Suffix after X_ must be at least 1 character long + """ + self.validator.adata.obsm["X_"] = self.validator.adata.obsm["X_umap"] + self.validator.validate_adata() + self.assertEqual( + self.validator.errors, + [ + "ERROR: Embedding key in 'adata.obsm' X_ must start with X_ and have a suffix at least one character long." + ], + ) def test_obsm_key_name_valid(self, validator_with_adata): """ @@ -1693,6 +1716,16 @@ def test_obsm_shape_one_column(self, validator_with_adata): "at least two columns.'adata.obsm['X_umap']' has shape " "of '(2, 1)'." ] + self.validator.adata.obsm["X_umap"] = numpy.delete(self.validator.adata.obsm["X_umap"], 0, 1) + self.validator.validate_adata() + self.assertEqual( + self.validator.errors, + [ + "ERROR: All embeddings must have as many rows as cells, and " + "at least two columns. 'adata.obsm['X_umap']' has shape " + "of '(2, 1)'." + ], + ) def test_obsm_shape_same_rows_and_columns(self, validator_with_adata): """ @@ -1721,6 +1754,18 @@ def test_obsm_size_zero(self, validator_with_adata): assert validator.errors == [ "ERROR: The size of the ndarray stored for a 'adata.obsm['badsize']' MUST NOT be zero." ] + self.validator.adata = self.save_and_read_adata(adata) + self.validator.validate_adata() + print(self.validator.errors) + self.assertEqual( + self.validator.errors, + [ + "ERROR: The size of the ndarray stored for a 'adata.obsm['badsize']' MUST NOT be zero.", + "ERROR: Embedding key in 'adata.obsm' badsize does not start with X_", + "ERROR: All embeddings must have as many rows as cells, and at least two columns. 'adata.obsm['badsize']' has shape of '(2, 0)'.", + "ERROR: adata.obsm['badsize'] contains all NaN values.", + ], + ) class TestObsp: From 07e3d3ef958b0879bc68163414102aa31a2c1597 Mon Sep 17 00:00:00 2001 From: Joyce Yan <5653616+joyceyan@users.noreply.github.com> Date: Thu, 28 Sep 2023 12:15:25 -0400 Subject: [PATCH 2/5] update --- .../cellxgene_schema/validate.py | 10 ++++++++-- .../tests/test_schema_compliance.py | 20 +++++++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/cellxgene_schema_cli/cellxgene_schema/validate.py b/cellxgene_schema_cli/cellxgene_schema/validate.py index b2f5c012c..deac886c5 100644 --- a/cellxgene_schema_cli/cellxgene_schema/validate.py +++ b/cellxgene_schema_cli/cellxgene_schema/validate.py @@ -1023,16 +1023,19 @@ def _validate_obsm(self): self.errors.append("No embeddings found in 'adata.obsm'.") return + obsm_with_x_prefix = 0 for key, value in self.adata.obsm.items(): # Checks for invalid keys if " " in key: self.errors.append(f"Embedding key {key} has whitespace in it, please remove it.") - if not key.startswith("X_"): - self.errors.append(f"Embedding key in 'adata.obsm' {key} does not start with X_") if len(key) <= 3: self.errors.append( f"Embedding key in 'adata.obsm' {key} must start with X_ and have a suffix at least one character long." ) + if not key.startswith("X_"): + self.errors.append(f"Embedding key in 'adata.obsm' {key} does not start with X_") + else: + obsm_with_x_prefix += 1 if not isinstance(value, np.ndarray): self.errors.append( @@ -1058,6 +1061,9 @@ def _validate_obsm(self): if np.all(np.isnan(value)): self.errors.append(f"adata.obsm['{key}'] contains all NaN values.") + if obsm_with_x_prefix == 0: + self.errors.append("At least one embedding in 'obsm' has to have a key with an 'X_' prefix.") + def _validate_annotation_mapping(self, component_name: str, component: Mapping): for key, value in component.items(): # Check for empty ndarrays diff --git a/cellxgene_schema_cli/tests/test_schema_compliance.py b/cellxgene_schema_cli/tests/test_schema_compliance.py index eaca2a7d0..add814f0e 100644 --- a/cellxgene_schema_cli/tests/test_schema_compliance.py +++ b/cellxgene_schema_cli/tests/test_schema_compliance.py @@ -1645,6 +1645,7 @@ def test_obsm_values_nan(self, validator_with_adata): validator.validate_adata() assert validator.errors == ["ERROR: adata.obsm['X_umap'] contains all NaN values."] +<<<<<<< HEAD def test_obsm_values_at_least_one_X(self, validator_with_adata): """ At least one key for the embedding MUST be prefixed with "X_" @@ -1672,11 +1673,27 @@ def test_obsm_suffix_name_valid(self, validator_with_adata): def test_obsm_values_must_start_with_X(self): self.validator.adata.obsm["umap"] = self.validator.adata.obsm["X_umap"] self.validator.adata.uns["default_embedding"] = "umap" +======= + def test_obsm_values_at_least_one_X(self): + self.validator.adata.obsm["harmony"] = self.validator.adata.obsm["X_umap"] + self.validator.adata.uns["default_embedding"] = "harmony" +>>>>>>> 8dd69eb (update) del self.validator.adata.obsm["X_umap"] self.validator.validate_adata() self.assertEqual( self.validator.errors, - ["ERROR: Embedding key in 'adata.obsm' umap does not start with X_"], + [ + "ERROR: Embedding key in 'adata.obsm' harmony does not start with X_", + "ERROR: At least one embedding in 'obsm' has to have a key with an 'X_' prefix.", + ], + ) + + def test_obsm_values_must_start_with_X(self): + self.validator.adata.obsm["harmony"] = self.validator.adata.obsm["X_umap"] + self.validator.validate_adata() + self.assertEqual( + self.validator.errors, + ["ERROR: Embedding key in 'adata.obsm' harmony does not start with X_"], ) def test_obsm_suffix_name_valid(self): @@ -1756,7 +1773,6 @@ def test_obsm_size_zero(self, validator_with_adata): ] self.validator.adata = self.save_and_read_adata(adata) self.validator.validate_adata() - print(self.validator.errors) self.assertEqual( self.validator.errors, [ From b1c696f066654ca383eced3029f290117b10de8c Mon Sep 17 00:00:00 2001 From: Joyce Yan <5653616+joyceyan@users.noreply.github.com> Date: Thu, 28 Sep 2023 13:32:51 -0400 Subject: [PATCH 3/5] downgrade to warnings --- .../cellxgene_schema/validate.py | 29 ++++++++++--------- .../tests/test_schema_compliance.py | 24 ++++++++++----- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/cellxgene_schema_cli/cellxgene_schema/validate.py b/cellxgene_schema_cli/cellxgene_schema/validate.py index deac886c5..0d0538b15 100644 --- a/cellxgene_schema_cli/cellxgene_schema/validate.py +++ b/cellxgene_schema_cli/cellxgene_schema/validate.py @@ -1025,41 +1025,44 @@ def _validate_obsm(self): obsm_with_x_prefix = 0 for key, value in self.adata.obsm.items(): - # Checks for invalid keys if " " in key: self.errors.append(f"Embedding key {key} has whitespace in it, please remove it.") - if len(key) <= 3: - self.errors.append( - f"Embedding key in 'adata.obsm' {key} must start with X_ and have a suffix at least one character long." - ) - if not key.startswith("X_"): - self.errors.append(f"Embedding key in 'adata.obsm' {key} does not start with X_") - else: + + issue_list = self.errors + if key.startswith("X_"): obsm_with_x_prefix += 1 + if len(key) <= 3: + self.errors.append( + f"Embedding key in 'adata.obsm' {key} must start with X_ and have a suffix at least one character long." + ) + else: + self.warnings.append(f"Embedding key in 'adata.obsm' {key} does not start with X_") + issue_list = self.warnings + # For all subsequent checks, we want to raise an error if it's an X_ embedding key, and a warning otherwise if not isinstance(value, np.ndarray): - self.errors.append( + issue_list.append( f"All embeddings have to be of 'numpy.ndarray' type, " f"'adata.obsm['{key}']' is {type(value)}')." ) # Skip over the subsequent checks that require the value to be an array continue if len(value.shape) < 2 or value.shape[0] != self.adata.n_obs or value.shape[1] < 2: - self.errors.append( + issue_list.append( f"All embeddings must have as many rows as cells, and at least two columns." f" 'adata.obsm['{key}']' has shape of '{value.shape}'." ) if not (np.issubdtype(value.dtype, np.integer) or np.issubdtype(value.dtype, np.floating)): - self.errors.append( + issue_list.append( f"adata.obsm['{key}'] has an invalid data type. It should be " "float, integer, or unsigned integer of any precision (8, 16, 32, or 64 bits)." ) else: # Check for inf/NaN values only if the dtype is numeric if np.isinf(value).any(): - self.errors.append(f"adata.obsm['{key}'] contains positive infinity or negative infinity values.") + issue_list.append(f"adata.obsm['{key}'] contains positive infinity or negative infinity values.") if np.all(np.isnan(value)): - self.errors.append(f"adata.obsm['{key}'] contains all NaN values.") + issue_list.append(f"adata.obsm['{key}'] contains all NaN values.") if obsm_with_x_prefix == 0: self.errors.append("At least one embedding in 'obsm' has to have a key with an 'X_' prefix.") diff --git a/cellxgene_schema_cli/tests/test_schema_compliance.py b/cellxgene_schema_cli/tests/test_schema_compliance.py index add814f0e..fd0276181 100644 --- a/cellxgene_schema_cli/tests/test_schema_compliance.py +++ b/cellxgene_schema_cli/tests/test_schema_compliance.py @@ -1683,17 +1683,28 @@ def test_obsm_values_at_least_one_X(self): self.assertEqual( self.validator.errors, [ - "ERROR: Embedding key in 'adata.obsm' harmony does not start with X_", "ERROR: At least one embedding in 'obsm' has to have a key with an 'X_' prefix.", ], ) + self.assertEqual( + self.validator.warnings, + [ + "WARNING: Embedding key in 'adata.obsm' harmony does not start with X_", + "WARNING: Validation of raw layer was not performed due to current errors, try again after fixing current errors.", + ], + ) - def test_obsm_values_must_start_with_X(self): - self.validator.adata.obsm["harmony"] = self.validator.adata.obsm["X_umap"] + def test_obsm_values_warn_start_with_X(self): + self.validator.adata.obsm["harmony"] = pd.DataFrame( + self.validator.adata.obsm["X_umap"], index=self.validator.adata.obs_names + ) self.validator.validate_adata() self.assertEqual( - self.validator.errors, - ["ERROR: Embedding key in 'adata.obsm' harmony does not start with X_"], + self.validator.warnings, + [ + "WARNING: Embedding key in 'adata.obsm' harmony does not start with X_", + "WARNING: All embeddings have to be of 'numpy.ndarray' type, 'adata.obsm['harmony']' is ').", + ], ) def test_obsm_suffix_name_valid(self): @@ -1777,9 +1788,6 @@ def test_obsm_size_zero(self, validator_with_adata): self.validator.errors, [ "ERROR: The size of the ndarray stored for a 'adata.obsm['badsize']' MUST NOT be zero.", - "ERROR: Embedding key in 'adata.obsm' badsize does not start with X_", - "ERROR: All embeddings must have as many rows as cells, and at least two columns. 'adata.obsm['badsize']' has shape of '(2, 0)'.", - "ERROR: adata.obsm['badsize'] contains all NaN values.", ], ) From 643872da655bdbc5a22fc32a36da3792e75d7a40 Mon Sep 17 00:00:00 2001 From: Joyce Yan <5653616+joyceyan@users.noreply.github.com> Date: Thu, 28 Sep 2023 13:34:16 -0400 Subject: [PATCH 4/5] update comment --- cellxgene_schema_cli/cellxgene_schema/validate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cellxgene_schema_cli/cellxgene_schema/validate.py b/cellxgene_schema_cli/cellxgene_schema/validate.py index 0d0538b15..114743487 100644 --- a/cellxgene_schema_cli/cellxgene_schema/validate.py +++ b/cellxgene_schema_cli/cellxgene_schema/validate.py @@ -1014,7 +1014,8 @@ def _validate_obsm(self): """ Validates the embedding dictionary -- it checks that all values of adata.obsm are numpy arrays with the correct dimension. Adds errors to self.errors if any. Checks that the keys start with "X_", have no whitespace, and have - a suffix at least 1 character long + a suffix at least 1 character long. For keys that don't start with "X_", we will run them through the same + validation checks, but raise warnings instead of errors. :rtype none """ From 034b2ff37a20b6111b192a489a5c1f837bb167c1 Mon Sep 17 00:00:00 2001 From: Joyce Yan <5653616+joyceyan@users.noreply.github.com> Date: Wed, 4 Oct 2023 13:58:55 -0400 Subject: [PATCH 5/5] fix + rebase tests --- .../tests/test_schema_compliance.py | 118 +++++------------- 1 file changed, 30 insertions(+), 88 deletions(-) diff --git a/cellxgene_schema_cli/tests/test_schema_compliance.py b/cellxgene_schema_cli/tests/test_schema_compliance.py index fd0276181..c358f050e 100644 --- a/cellxgene_schema_cli/tests/test_schema_compliance.py +++ b/cellxgene_schema_cli/tests/test_schema_compliance.py @@ -1645,80 +1645,41 @@ def test_obsm_values_nan(self, validator_with_adata): validator.validate_adata() assert validator.errors == ["ERROR: adata.obsm['X_umap'] contains all NaN values."] -<<<<<<< HEAD def test_obsm_values_at_least_one_X(self, validator_with_adata): - """ - At least one key for the embedding MUST be prefixed with "X_" - """ validator = validator_with_adata - obsm = validator.adata.obsm - obsm["umap"] = obsm["X_umap"] - validator.adata.uns["default_embedding"] = "umap" - del obsm["X_umap"] + validator.adata.obsm["harmony"] = validator.adata.obsm["X_umap"] + validator.adata.uns["default_embedding"] = "harmony" + del validator.adata.obsm["X_umap"] validator.validate_adata() - assert validator.errors == ["ERROR: At least one embedding in 'obsm' has to have a " "key with an 'X_' prefix."] + assert validator.errors == [ + "ERROR: At least one embedding in 'obsm' has to have a key with an 'X_' prefix.", + ] + assert validator.warnings == [ + "WARNING: Dataframe 'var' only has 4 rows. Features SHOULD NOT be filtered from expression matrix.", + "WARNING: Embedding key in 'adata.obsm' harmony does not start with X_", + "WARNING: Validation of raw layer was not performed due to current errors, try again after fixing current errors.", + ] - def test_obsm_suffix_name_valid(self, validator_with_adata): - """ - Suffix after X_ must be at least 1 character long - """ + def test_obsm_values_warn_start_with_X(self, validator_with_adata): validator = validator_with_adata - obsm = validator.adata.obsm - obsm["X_"] = obsm["X_umap"] - validator.validate_adata() - assert validator.errors == [ - "ERROR: Embedding key in 'adata.obsm' X_ must have a suffix at least one character long." - ] - - def test_obsm_values_must_start_with_X(self): - self.validator.adata.obsm["umap"] = self.validator.adata.obsm["X_umap"] - self.validator.adata.uns["default_embedding"] = "umap" -======= - def test_obsm_values_at_least_one_X(self): - self.validator.adata.obsm["harmony"] = self.validator.adata.obsm["X_umap"] - self.validator.adata.uns["default_embedding"] = "harmony" ->>>>>>> 8dd69eb (update) - del self.validator.adata.obsm["X_umap"] - self.validator.validate_adata() - self.assertEqual( - self.validator.errors, - [ - "ERROR: At least one embedding in 'obsm' has to have a key with an 'X_' prefix.", - ], - ) - self.assertEqual( - self.validator.warnings, - [ - "WARNING: Embedding key in 'adata.obsm' harmony does not start with X_", - "WARNING: Validation of raw layer was not performed due to current errors, try again after fixing current errors.", - ], - ) - - def test_obsm_values_warn_start_with_X(self): - self.validator.adata.obsm["harmony"] = pd.DataFrame( - self.validator.adata.obsm["X_umap"], index=self.validator.adata.obs_names - ) - self.validator.validate_adata() - self.assertEqual( - self.validator.warnings, - [ - "WARNING: Embedding key in 'adata.obsm' harmony does not start with X_", - "WARNING: All embeddings have to be of 'numpy.ndarray' type, 'adata.obsm['harmony']' is ').", - ], - ) + validator.adata.obsm["harmony"] = pd.DataFrame(validator.adata.obsm["X_umap"], index=validator.adata.obs_names) + validator.validate_adata() + assert validator.warnings == [ + "WARNING: Dataframe 'var' only has 4 rows. Features SHOULD NOT be filtered from expression matrix.", + "WARNING: Embedding key in 'adata.obsm' harmony does not start with X_", + "WARNING: All embeddings have to be of 'numpy.ndarray' type, 'adata.obsm['harmony']' is ').", + ] - def test_obsm_suffix_name_valid(self): + def test_obsm_suffix_name_valid(self, validator_with_adata): """ Suffix after X_ must be at least 1 character long """ - self.validator.adata.obsm["X_"] = self.validator.adata.obsm["X_umap"] - self.validator.validate_adata() - self.assertEqual( - self.validator.errors, - [ - "ERROR: Embedding key in 'adata.obsm' X_ must start with X_ and have a suffix at least one character long." - ], - ) + validator = validator_with_adata + validator.adata.obsm["X_"] = validator.adata.obsm["X_umap"] + validator.validate_adata() + assert validator.errors == [ + "ERROR: Embedding key in 'adata.obsm' X_ must start with X_ and have a suffix at least one character long." + ] def test_obsm_key_name_valid(self, validator_with_adata): """ @@ -1734,26 +1695,15 @@ def test_obsm_shape_one_column(self, validator_with_adata): """ Curators MUST annotate one or more two-dimensional (m >= 2) embeddings """ - validator = validator_with_adata - obsm = validator.adata.obsm # Makes 1 column array - obsm["X_umap"] = numpy.delete(obsm["X_umap"], 0, 1) + validator = validator_with_adata + validator.adata.obsm["X_umap"] = numpy.delete(validator.adata.obsm["X_umap"], 0, 1) validator.validate_adata() assert validator.errors == [ "ERROR: All embeddings must have as many rows as cells, and " - "at least two columns.'adata.obsm['X_umap']' has shape " + "at least two columns. 'adata.obsm['X_umap']' has shape " "of '(2, 1)'." ] - self.validator.adata.obsm["X_umap"] = numpy.delete(self.validator.adata.obsm["X_umap"], 0, 1) - self.validator.validate_adata() - self.assertEqual( - self.validator.errors, - [ - "ERROR: All embeddings must have as many rows as cells, and " - "at least two columns. 'adata.obsm['X_umap']' has shape " - "of '(2, 1)'." - ], - ) def test_obsm_shape_same_rows_and_columns(self, validator_with_adata): """ @@ -1780,16 +1730,8 @@ def test_obsm_size_zero(self, validator_with_adata): validator.adata = save_and_read_adata(adata) validator.validate_adata() assert validator.errors == [ - "ERROR: The size of the ndarray stored for a 'adata.obsm['badsize']' MUST NOT be zero." + "ERROR: The size of the ndarray stored for a 'adata.obsm['badsize']' MUST NOT be zero.", ] - self.validator.adata = self.save_and_read_adata(adata) - self.validator.validate_adata() - self.assertEqual( - self.validator.errors, - [ - "ERROR: The size of the ndarray stored for a 'adata.obsm['badsize']' MUST NOT be zero.", - ], - ) class TestObsp: