From b83e8c860ab8a1fdc396ad590eb5f41e4baa0dac Mon Sep 17 00:00:00 2001 From: Joyce Yan <5653616+joyceyan@users.noreply.github.com> Date: Wed, 11 Oct 2023 16:02:04 -0400 Subject: [PATCH 1/5] string detection --- .../cellxgene_schema/validate.py | 19 ++++++++++----- .../tests/fixtures/examples_validate.py | 12 ++++++++++ .../h5ads/example_valid_modified.h5ad | Bin 0 -> 102432 bytes .../tests/test_schema_compliance.py | 22 +++++++++++------- 4 files changed, 39 insertions(+), 14 deletions(-) create mode 100644 cellxgene_schema_cli/tests/fixtures/h5ads/example_valid_modified.h5ad diff --git a/cellxgene_schema_cli/cellxgene_schema/validate.py b/cellxgene_schema_cli/cellxgene_schema/validate.py index 08a490360..842070781 100644 --- a/cellxgene_schema_cli/cellxgene_schema/validate.py +++ b/cellxgene_schema_cli/cellxgene_schema/validate.py @@ -750,11 +750,13 @@ def _validate_colors_in_uns_dict(self, uns_dict: dict) -> None: for key, value in uns_dict.items(): if key.endswith("_colors"): # 1. Verify that the corresponding categorical field exists in obs - obs_unique_values = category_mapping.get(key.replace("_colors", "")) + column_name = key.replace("_colors", "") + obs_unique_values = category_mapping.get(column_name) if not obs_unique_values: - self.errors.append( - f"Colors field uns[{key}] does not have a corresponding categorical field in obs" - ) + error_message = f"Colors field uns[{key}] does not have a corresponding categorical field in obs" + if column_name in df.columns: + error_message += f" {column_name} is present but is dtype {df[column_name].dtype.name}" + self.errors.append(error_message) continue # 2. Verify that the value is a numpy array if value is None or not isinstance(value, np.ndarray): @@ -764,8 +766,9 @@ def _validate_colors_in_uns_dict(self, uns_dict: dict) -> None: # Skip over all subsequent validations which expect a numpy array continue # 3. Verify that we have strings in the array - if not np.issubdtype(value.dtype, np.character): - self.errors.append(f"Colors in uns[{key}] must be strings. Found: {value}") + all_strings = all(self._validate_string(color) for color in value) + if not all_strings: + self.errors.append(f"Colors in uns[{key}] must be strings. Found: {value} which are {value.dtype.name}") continue # 4. Verify that we have at least as many unique colors as unique values in the corresponding categorical field value = np.unique(value) @@ -781,6 +784,9 @@ def _validate_colors_in_uns_dict(self, uns_dict: dict) -> None: self.errors.append( f"Colors in uns[{key}] must be either all hex colors or all CSS4 named colors. Found: {value}" ) + + def _validate_string(self, color: Any) -> bool: + return isinstance(color, str) def _validate_css4_color(self, color: Any) -> bool: if not isinstance(color, str): @@ -1465,6 +1471,7 @@ def validate_adata(self, h5ad_path: Union[str, bytes, os.PathLike] = None, to_me if self.errors: self.errors = ["ERROR: " + i for i in self.errors] for e in self.errors: + print(f"error: {e}") logger.error(e) self.is_valid = False else: diff --git a/cellxgene_schema_cli/tests/fixtures/examples_validate.py b/cellxgene_schema_cli/tests/fixtures/examples_validate.py index 79e141cda..e1e545ed8 100644 --- a/cellxgene_schema_cli/tests/fixtures/examples_validate.py +++ b/cellxgene_schema_cli/tests/fixtures/examples_validate.py @@ -180,6 +180,16 @@ "batch_condition": ["is_primary_data"], } +good_uns_with_colors = { + "title": "A title", + "default_embedding": "X_umap", + "X_approximate_distribution": "normal", + "batch_condition": ["is_primary_data"], + "suspension_type_colors": numpy.array(["red", "blue"]), + "donor_id_colors": numpy.array(["#000000", "#ffffff"]), + "tissue_type_colors": numpy.array(["black", "pink"]), +} + # --- # 4. Creating expression matrix, # X has integer values and non_raw_X has real values @@ -234,6 +244,8 @@ obsm=good_obsm, ) +# Expected anndata with colors for categorical obs fields +adata_with_colors = anndata.AnnData(X=sparse.csr_matrix(X), obs=good_obs, uns=good_uns_with_colors, obsm=good_obsm, var=good_var) # anndata for testing migration umigrated_obs = pd.DataFrame( diff --git a/cellxgene_schema_cli/tests/fixtures/h5ads/example_valid_modified.h5ad b/cellxgene_schema_cli/tests/fixtures/h5ads/example_valid_modified.h5ad new file mode 100644 index 0000000000000000000000000000000000000000..e72fed468630d95e1e698fdf9496596d9510a419 GIT binary patch literal 102432 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 Date: Wed, 11 Oct 2023 17:14:00 -0400 Subject: [PATCH 2/5] fix linter --- cellxgene_schema_cli/cellxgene_schema/validate.py | 6 ++++-- .../tests/fixtures/examples_validate.py | 4 +++- .../tests/test_schema_compliance.py | 14 ++++++++++---- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/cellxgene_schema_cli/cellxgene_schema/validate.py b/cellxgene_schema_cli/cellxgene_schema/validate.py index 842070781..01b57905b 100644 --- a/cellxgene_schema_cli/cellxgene_schema/validate.py +++ b/cellxgene_schema_cli/cellxgene_schema/validate.py @@ -768,7 +768,9 @@ def _validate_colors_in_uns_dict(self, uns_dict: dict) -> None: # 3. Verify that we have strings in the array all_strings = all(self._validate_string(color) for color in value) if not all_strings: - self.errors.append(f"Colors in uns[{key}] must be strings. Found: {value} which are {value.dtype.name}") + self.errors.append( + f"Colors in uns[{key}] must be strings. Found: {value} which are {value.dtype.name}" + ) continue # 4. Verify that we have at least as many unique colors as unique values in the corresponding categorical field value = np.unique(value) @@ -784,7 +786,7 @@ def _validate_colors_in_uns_dict(self, uns_dict: dict) -> None: self.errors.append( f"Colors in uns[{key}] must be either all hex colors or all CSS4 named colors. Found: {value}" ) - + def _validate_string(self, color: Any) -> bool: return isinstance(color, str) diff --git a/cellxgene_schema_cli/tests/fixtures/examples_validate.py b/cellxgene_schema_cli/tests/fixtures/examples_validate.py index e1e545ed8..33b36dfa5 100644 --- a/cellxgene_schema_cli/tests/fixtures/examples_validate.py +++ b/cellxgene_schema_cli/tests/fixtures/examples_validate.py @@ -245,7 +245,9 @@ ) # Expected anndata with colors for categorical obs fields -adata_with_colors = anndata.AnnData(X=sparse.csr_matrix(X), obs=good_obs, uns=good_uns_with_colors, obsm=good_obsm, var=good_var) +adata_with_colors = anndata.AnnData( + X=sparse.csr_matrix(X), obs=good_obs, uns=good_uns_with_colors, obsm=good_obsm, var=good_var +) # anndata for testing migration umigrated_obs = pd.DataFrame( diff --git a/cellxgene_schema_cli/tests/test_schema_compliance.py b/cellxgene_schema_cli/tests/test_schema_compliance.py index ae6b45244..38181943f 100644 --- a/cellxgene_schema_cli/tests/test_schema_compliance.py +++ b/cellxgene_schema_cli/tests/test_schema_compliance.py @@ -1567,7 +1567,7 @@ def test_deprecated_fields(self, validator_with_adata): "ERROR: The field 'project_name' is present in 'uns', but it is deprecated.", "ERROR: The field 'publication_doi' is present in 'uns', but it is deprecated.", ] - + def test_colors_happy_path(self, validator_with_adata): validator = validator_with_adata validator.adata = examples.adata_with_colors.copy() @@ -1615,7 +1615,9 @@ def test_empty_color_array(self, validator_with_adata): validator = validator_with_adata validator.adata.uns["suspension_type_colors"] = numpy.array([]) validator.validate_adata() - assert validator.errors == ["ERROR: Annotated categorical field suspension_type must have at least 2 color options in uns[suspension_type_colors]. Found: []"] + assert validator.errors == [ + "ERROR: Annotated categorical field suspension_type must have at least 2 color options in uns[suspension_type_colors]. Found: []" + ] def test_not_enough_color_options(self, validator_with_adata): validator = validator_with_adata @@ -1669,7 +1671,9 @@ def test_invalid_named_color_option_integer(self, validator_with_adata): validator = validator_with_adata validator.adata.uns["suspension_type_colors"] = numpy.array([3, 4]) validator.validate_adata() - assert validator.errors == ["ERROR: Colors in uns[suspension_type_colors] must be strings. Found: [3 4] which are int64"] + assert validator.errors == [ + "ERROR: Colors in uns[suspension_type_colors] must be strings. Found: [3 4] which are int64" + ] def test_invalid_named_color_option_none(self, validator_with_adata): validator = validator_with_adata @@ -1683,7 +1687,9 @@ def test_invalid_named_color_option_nan(self, validator_with_adata): validator = validator_with_adata validator.adata.uns["suspension_type_colors"] = numpy.array([numpy.nan, numpy.nan]) validator.validate_adata() - assert validator.errors == ["ERROR: Colors in uns[suspension_type_colors] must be strings. Found: [nan nan] which are float64"] + assert validator.errors == [ + "ERROR: Colors in uns[suspension_type_colors] must be strings. Found: [nan nan] which are float64" + ] class TestObsm: From 1509fbb9a76070fde42d94b2e403235a2ce67955 Mon Sep 17 00:00:00 2001 From: Joyce Yan <5653616+joyceyan@users.noreply.github.com> Date: Wed, 11 Oct 2023 17:14:45 -0400 Subject: [PATCH 3/5] rm print statement --- cellxgene_schema_cli/cellxgene_schema/validate.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cellxgene_schema_cli/cellxgene_schema/validate.py b/cellxgene_schema_cli/cellxgene_schema/validate.py index 01b57905b..f98076fd8 100644 --- a/cellxgene_schema_cli/cellxgene_schema/validate.py +++ b/cellxgene_schema_cli/cellxgene_schema/validate.py @@ -1473,7 +1473,6 @@ def validate_adata(self, h5ad_path: Union[str, bytes, os.PathLike] = None, to_me if self.errors: self.errors = ["ERROR: " + i for i in self.errors] for e in self.errors: - print(f"error: {e}") logger.error(e) self.is_valid = False else: From 2168d6a0f0bb47377912e4c683231c9caf93b299 Mon Sep 17 00:00:00 2001 From: Joyce Yan <5653616+joyceyan@users.noreply.github.com> Date: Wed, 11 Oct 2023 18:18:22 -0400 Subject: [PATCH 4/5] rm unnecessary function --- cellxgene_schema_cli/cellxgene_schema/validate.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cellxgene_schema_cli/cellxgene_schema/validate.py b/cellxgene_schema_cli/cellxgene_schema/validate.py index f98076fd8..6431b7cf8 100644 --- a/cellxgene_schema_cli/cellxgene_schema/validate.py +++ b/cellxgene_schema_cli/cellxgene_schema/validate.py @@ -766,7 +766,7 @@ def _validate_colors_in_uns_dict(self, uns_dict: dict) -> None: # Skip over all subsequent validations which expect a numpy array continue # 3. Verify that we have strings in the array - all_strings = all(self._validate_string(color) for color in value) + all_strings = all(isinstance(color, str) for color in value) if not all_strings: self.errors.append( f"Colors in uns[{key}] must be strings. Found: {value} which are {value.dtype.name}" @@ -787,9 +787,6 @@ def _validate_colors_in_uns_dict(self, uns_dict: dict) -> None: f"Colors in uns[{key}] must be either all hex colors or all CSS4 named colors. Found: {value}" ) - def _validate_string(self, color: Any) -> bool: - return isinstance(color, str) - def _validate_css4_color(self, color: Any) -> bool: if not isinstance(color, str): return False From c7b0f62014ecdfb00ed74efaec2fb9f17161af3d Mon Sep 17 00:00:00 2001 From: Joyce Yan <5653616+joyceyan@users.noreply.github.com> Date: Thu, 12 Oct 2023 12:52:06 -0400 Subject: [PATCH 5/5] add period for clarity --- cellxgene_schema_cli/cellxgene_schema/validate.py | 2 +- cellxgene_schema_cli/tests/test_schema_compliance.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cellxgene_schema_cli/cellxgene_schema/validate.py b/cellxgene_schema_cli/cellxgene_schema/validate.py index 6431b7cf8..100004594 100644 --- a/cellxgene_schema_cli/cellxgene_schema/validate.py +++ b/cellxgene_schema_cli/cellxgene_schema/validate.py @@ -755,7 +755,7 @@ def _validate_colors_in_uns_dict(self, uns_dict: dict) -> None: if not obs_unique_values: error_message = f"Colors field uns[{key}] does not have a corresponding categorical field in obs" if column_name in df.columns: - error_message += f" {column_name} is present but is dtype {df[column_name].dtype.name}" + error_message += f". {column_name} is present but is dtype {df[column_name].dtype.name}" self.errors.append(error_message) continue # 2. Verify that the value is a numpy array diff --git a/cellxgene_schema_cli/tests/test_schema_compliance.py b/cellxgene_schema_cli/tests/test_schema_compliance.py index 38181943f..ed6d11dde 100644 --- a/cellxgene_schema_cli/tests/test_schema_compliance.py +++ b/cellxgene_schema_cli/tests/test_schema_compliance.py @@ -1600,7 +1600,7 @@ def test_colors_bool_obs_counterpart(self, validator_with_adata): validator.adata.uns["is_primary_data_colors"] = numpy.array(["green", "purple"]) validator.validate_adata() assert validator.errors == [ - "ERROR: Colors field uns[is_primary_data_colors] does not have a corresponding categorical field in obs is_primary_data is present but is dtype bool" + "ERROR: Colors field uns[is_primary_data_colors] does not have a corresponding categorical field in obs. is_primary_data is present but is dtype bool" ] def test_colors_without_obs_counterpart(self, validator_with_adata):