hca-schema-validator 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. hca_schema_validator/__init__.py +11 -0
  2. hca_schema_validator/_vendored/__init__.py +18 -0
  3. hca_schema_validator/_vendored/cellxgene_schema/LICENSE +20 -0
  4. hca_schema_validator/_vendored/cellxgene_schema/__init__.py +1 -0
  5. hca_schema_validator/_vendored/cellxgene_schema/atac_seq.py +675 -0
  6. hca_schema_validator/_vendored/cellxgene_schema/cli.py +206 -0
  7. hca_schema_validator/_vendored/cellxgene_schema/env.py +9 -0
  8. hca_schema_validator/_vendored/cellxgene_schema/gencode.py +163 -0
  9. hca_schema_validator/_vendored/cellxgene_schema/gencode_files/.gitignore +3 -0
  10. hca_schema_validator/_vendored/cellxgene_schema/gencode_files/drosophila_melanogaster_diff.txt +42 -0
  11. hca_schema_validator/_vendored/cellxgene_schema/gencode_files/gene_info.md +29 -0
  12. hca_schema_validator/_vendored/cellxgene_schema/gencode_files/gene_info.yml +51 -0
  13. hca_schema_validator/_vendored/cellxgene_schema/gencode_files/genes_caenorhabditis_elegans.csv.gz +0 -0
  14. hca_schema_validator/_vendored/cellxgene_schema/gencode_files/genes_callithrix_jacchus.csv.gz +0 -0
  15. hca_schema_validator/_vendored/cellxgene_schema/gencode_files/genes_danio_rerio.csv.gz +0 -0
  16. hca_schema_validator/_vendored/cellxgene_schema/gencode_files/genes_drosophila_melanogaster.csv.gz +0 -0
  17. hca_schema_validator/_vendored/cellxgene_schema/gencode_files/genes_ercc.csv.gz +0 -0
  18. hca_schema_validator/_vendored/cellxgene_schema/gencode_files/genes_gorilla_gorilla.csv.gz +0 -0
  19. hca_schema_validator/_vendored/cellxgene_schema/gencode_files/genes_homo_sapiens.csv.gz +0 -0
  20. hca_schema_validator/_vendored/cellxgene_schema/gencode_files/genes_macaca_fascicularis.csv.gz +0 -0
  21. hca_schema_validator/_vendored/cellxgene_schema/gencode_files/genes_macaca_mulatta.csv.gz +0 -0
  22. hca_schema_validator/_vendored/cellxgene_schema/gencode_files/genes_microcebus_murinus.csv.gz +0 -0
  23. hca_schema_validator/_vendored/cellxgene_schema/gencode_files/genes_mus_musculus.csv.gz +0 -0
  24. hca_schema_validator/_vendored/cellxgene_schema/gencode_files/genes_oryctolagus_cuniculus.csv.gz +0 -0
  25. hca_schema_validator/_vendored/cellxgene_schema/gencode_files/genes_pan_troglodytes.csv.gz +0 -0
  26. hca_schema_validator/_vendored/cellxgene_schema/gencode_files/genes_rattus_norvegicus.csv.gz +0 -0
  27. hca_schema_validator/_vendored/cellxgene_schema/gencode_files/genes_sars_cov_2.csv.gz +0 -0
  28. hca_schema_validator/_vendored/cellxgene_schema/gencode_files/genes_sus_scrofa.csv.gz +0 -0
  29. hca_schema_validator/_vendored/cellxgene_schema/gencode_files/homo_sapiens_diff.txt +1237 -0
  30. hca_schema_validator/_vendored/cellxgene_schema/gencode_files/mus_musculus_diff.txt +549 -0
  31. hca_schema_validator/_vendored/cellxgene_schema/gencode_files/rattus_norvegicus_diff.txt +7306 -0
  32. hca_schema_validator/_vendored/cellxgene_schema/map_species.py +40 -0
  33. hca_schema_validator/_vendored/cellxgene_schema/matrix_utils.py +134 -0
  34. hca_schema_validator/_vendored/cellxgene_schema/migrate.py +145 -0
  35. hca_schema_validator/_vendored/cellxgene_schema/migrate_files/automigrate_terms.json +2 -0
  36. hca_schema_validator/_vendored/cellxgene_schema/migrate_files/donor_updates.json +2 -0
  37. hca_schema_validator/_vendored/cellxgene_schema/migrate_files/non_csr_list.txt +54 -0
  38. hca_schema_validator/_vendored/cellxgene_schema/migrate_files/private_non_csr.json +1 -0
  39. hca_schema_validator/_vendored/cellxgene_schema/migrate_files/title_donor_updates.json +2 -0
  40. hca_schema_validator/_vendored/cellxgene_schema/ontology_files/.gitignore +1 -0
  41. hca_schema_validator/_vendored/cellxgene_schema/ontology_parser.py +3 -0
  42. hca_schema_validator/_vendored/cellxgene_schema/remove_labels.py +74 -0
  43. hca_schema_validator/_vendored/cellxgene_schema/schema.py +21 -0
  44. hca_schema_validator/_vendored/cellxgene_schema/schema_definitions/schema_definition.yaml +841 -0
  45. hca_schema_validator/_vendored/cellxgene_schema/utils.py +242 -0
  46. hca_schema_validator/_vendored/cellxgene_schema/validate.py +2389 -0
  47. hca_schema_validator/_vendored/cellxgene_schema/validation_internals/__init__.py +0 -0
  48. hca_schema_validator/_vendored/cellxgene_schema/validation_internals/check_duplicates.py +87 -0
  49. hca_schema_validator/_vendored/cellxgene_schema/write_labels.py +403 -0
  50. hca_schema_validator/schema_definitions/hca_schema_definition.yaml +839 -0
  51. hca_schema_validator/validator.py +47 -0
  52. hca_schema_validator-0.2.0.dist-info/METADATA +116 -0
  53. hca_schema_validator-0.2.0.dist-info/RECORD +55 -0
  54. hca_schema_validator-0.2.0.dist-info/WHEEL +4 -0
  55. hca_schema_validator-0.2.0.dist-info/licenses/NOTICE +15 -0
@@ -0,0 +1,40 @@
1
+ import logging
2
+
3
+ import anndata as ad
4
+ from cellxgene_ontology_guide.ontology_parser import OntologyParser
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ def map_species(input_file, output_file):
10
+ ontology_parser = OntologyParser("6.0.0")
11
+ adata = ad.read_h5ad(input_file, backed="r")
12
+ map_columns = [
13
+ ("organism_cell_type_ontology_term_id", "cell_type_ontology_term_id", "CL"),
14
+ ("organism_tissue_ontology_term_id", "tissue_ontology_term_id", "UBERON"),
15
+ ]
16
+ for map_from, map_to, ontology in map_columns:
17
+ logger.info(f"Mapping {ontology} terms to {map_to}")
18
+ if not hasattr(adata.obs, map_to):
19
+ logger.info(f"No existing {map_to} column detected, creating one and defaulting to empty strings.")
20
+ adata.obs[map_to] = ""
21
+ for category, group in adata.obs.groupby(map_from):
22
+ try:
23
+ closest_terms = ontology_parser.get_closest_bridge_term_ids(category, ontology)
24
+ except Exception:
25
+ logger.error(f"{category} in {map_from} is not a valid ontology term ID, cannot find {ontology} map.")
26
+ continue
27
+ if len(closest_terms) < 1:
28
+ logger.warning(f"{category} has no closest match for {ontology} in its ancestry tree.")
29
+ elif len(closest_terms) == 1:
30
+ adata.obs.loc[group.index, map_to] = closest_terms[0]
31
+ logger.info(f"Setting {category} {map_to} rows to single closest match: {closest_terms[0]}")
32
+ else:
33
+ logger.warning(
34
+ f"{category} has multiple closest matches for {ontology} in its ancestry tree: "
35
+ f"{closest_terms}. Pick one based on biological context and assign manually."
36
+ )
37
+ adata.obs[map_to] = adata.obs[map_to].astype("category")
38
+
39
+ logger.info(f"Mappings complete, writing to {output_file}")
40
+ adata.write_h5ad(output_file, compression="gzip")
@@ -0,0 +1,134 @@
1
+ from typing import Union
2
+
3
+ import anndata as ad
4
+ import numpy as np
5
+ import sparse as sp
6
+ from anndata.compat import DaskArray
7
+ from dask.array import map_blocks
8
+ from scipy import sparse
9
+
10
+ SPARSE_MATRIX_TYPES = {"csr", "csc", "coo"}
11
+ SUPPORTED_SPARSE_MATRIX_TYPES = {"csr"}
12
+
13
+
14
+ def determine_matrix_format(matrix: DaskArray) -> str:
15
+ """
16
+ Given a matrix, returns the format as one of: csc, csr, coo, dense
17
+ or unknown.
18
+
19
+ This mimics the scipy.sparse `format` property, but extends it to
20
+ support ndarray and other classes AnnData may proxy the matrix with.
21
+ """
22
+
23
+ # Note: the AnnData proxy classes DO support the `format_str` property, but
24
+ # doing a slice seemed safer, if less performant. Using `format_str`, which
25
+ # currently works, uses private API:
26
+ #
27
+ # >>> return getattr(matrix, "format_str", "dense)
28
+ #
29
+ matrix_format = "unknown"
30
+ try:
31
+ matrix_slice = matrix[0:1, 0:1].compute()
32
+ except AttributeError:
33
+ # compute() may fail on an unknown matrix value. if so, return "unknown"
34
+ return matrix_format
35
+ if isinstance(matrix_slice, sparse.spmatrix):
36
+ matrix_format = matrix_slice.format
37
+ elif isinstance(matrix_slice, np.ndarray):
38
+ matrix_format = "dense"
39
+ assert matrix_format in SPARSE_MATRIX_TYPES.union({"unknown", "dense"})
40
+ return matrix_format
41
+
42
+
43
+ def compute_column_sums(matrix: Union[DaskArray, np.ndarray, sparse.spmatrix]) -> np.ndarray:
44
+ """
45
+ Compute column-wise sums for a Dask array (dense or sparse), NumPy ndarray, or SciPy sparse matrix.
46
+ Returns a NumPy array of sums.
47
+
48
+ For example, this matrix:
49
+ [
50
+ [1, 0, 0],
51
+ [1, 2, 0],
52
+ [1, 0, 1],
53
+ ]
54
+
55
+ would return a NumPy array of [3, 12, 1]
56
+ """
57
+ if isinstance(matrix, np.ndarray):
58
+ return matrix.sum(axis=0).ravel()
59
+
60
+ if sparse.issparse(matrix):
61
+ return np.array(matrix.sum(axis=0)).ravel()
62
+
63
+ # Handle Dask array (could be sparse or dense)
64
+ if isinstance(matrix, DaskArray):
65
+
66
+ def to_coo(chunk: Union[np.ndarray, sparse.spmatrix]) -> Union[sp.COO, np.ndarray]:
67
+ # convert to sparse.COO, so we can use dask's internal chunked sum
68
+ if sparse.issparse(chunk):
69
+ return sp.COO.from_scipy_sparse(chunk)
70
+ else:
71
+ return chunk
72
+
73
+ x_coo = matrix.map_blocks(to_coo, dtype=matrix.dtype)
74
+
75
+ # 'split_every' is used to ensure that the computation is split into memory manageable chunks
76
+ col_sums = x_coo.sum(axis=0, split_every=8).compute()
77
+ return col_sums
78
+
79
+
80
+ def calculate_matrix_nonzero(matrix: DaskArray) -> int:
81
+ def count_nonzeros(matrix_chunk: Union[np.ndarray, sparse.spmatrix], is_sparse_matrix: bool) -> np.array:
82
+ nnz = matrix_chunk.nnz if is_sparse_matrix else np.count_nonzero(matrix_chunk)
83
+ return np.array([nnz])
84
+
85
+ is_sparse_matrix = determine_matrix_format(matrix) in SPARSE_MATRIX_TYPES
86
+ if len(matrix.chunks[0]) > 1:
87
+ nonzeros = map_blocks(count_nonzeros, matrix, is_sparse_matrix, drop_axis=1, dtype=int).compute().sum()
88
+ else:
89
+ nonzeros = count_nonzeros(matrix.compute(), is_sparse_matrix)[0]
90
+ return nonzeros
91
+
92
+
93
+ def check_non_csr_matrixes(adata: ad.AnnData):
94
+ """
95
+ Check X, raw.X and layers matrices for having more than 50% zeros and not being csr_matrix
96
+
97
+ If found, convert to csr_matrix
98
+ """
99
+
100
+ def get_sparsity(matrix: DaskArray, format: str):
101
+ is_sparse_matrix = format in SPARSE_MATRIX_TYPES
102
+ nnz = calculate_matrix_nonzero(matrix, is_sparse_matrix)
103
+ sparsity = 1 - nnz / np.prod(matrix.shape)
104
+ return sparsity
105
+
106
+ format = determine_matrix_format(adata.X)
107
+ if format != "csr" and get_sparsity(adata.X, format) >= 0.5:
108
+ adata.X = adata.X.map_blocks(sparse.csr_matrix, dtype=adata.X.dtype)
109
+
110
+ if adata.raw is not None:
111
+ format = determine_matrix_format(adata.raw.X)
112
+ if format != "csr" and get_sparsity(adata.raw.X, format) >= 0.5:
113
+ raw_adata = ad.AnnData(adata.raw.X, var=adata.raw.var, obs=adata.obs)
114
+ raw_adata.X = raw_adata.X.map_blocks(sparse.csr_matrix, dtype=raw_adata.X.dtype)
115
+ adata.raw = raw_adata
116
+ del raw_adata
117
+
118
+ for layer in adata.layers:
119
+ format = determine_matrix_format(adata.layers[layer])
120
+ if format != "csr" and get_sparsity(adata.layers[layer], format) >= 0.5:
121
+ adata.layers[layer] = adata.layers[layer].map_blocks(sparse.csr_matrix, dtype=adata.layers[layer].X.dtype)
122
+
123
+ return adata
124
+
125
+
126
+ def debug_print_matrix(matrix: ad.AnnData, matrix_name: str, max_rows=20, max_cols=20):
127
+ computed = matrix[:max_rows, :max_cols].compute()
128
+
129
+ # If sparse, convert to dense for display
130
+ if hasattr(computed, "toarray"):
131
+ computed = computed.toarray()
132
+
133
+ print(f"{matrix_name} (first {max_rows} rows, {max_cols} cols):")
134
+ print(np.round(computed, 2)) # round for readability
@@ -0,0 +1,145 @@
1
+ import dask
2
+
3
+ from . import utils
4
+
5
+ # fmt: off
6
+ # ONTOLOGY TERMS TO UPDATE ACROSS ALL DATASETS IN CORPUS
7
+ # Initialization is AUTOMATED for newly deprecated terms that have 'Replaced By' terms in their ontology files
8
+
9
+ # Curators should review the monthly 'Curator Report' and add deprecated term replacements to corresponding map if
10
+ # 'Replaced By' is not available for a deprecated term.
11
+
12
+ # If Curators have non-deprecated term changes to apply to all datasets in the corpus where applicable,
13
+ # add them here.
14
+ # added two CL terms here instead of as applicable to individual datasets
15
+ ONTOLOGY_TERM_OBS_MAPS = {
16
+ "assay": {
17
+ },
18
+ "cell_type": {
19
+ "CL:0000651": "CL:0002181",
20
+ "CL:0000212": "CL:0000677"
21
+ },
22
+ "development_stage": {
23
+ },
24
+ "disease": {
25
+ },
26
+ "self_reported_ethnicity": {
27
+ },
28
+ "sex": {
29
+ },
30
+ "tissue": {
31
+ },
32
+ }
33
+
34
+ ONTOLOGY_TERM_UNS_MAPS = {
35
+ "organism": {
36
+ },
37
+ }
38
+
39
+ DEPRECATED_FEATURE_IDS = [
40
+ ]
41
+
42
+ # Dictionary for CURATOR-DEFINED remapping of deprecated feature IDs, if any, to new feature IDs.
43
+ GENCODE_MAPPER = {}
44
+ # fmt: on
45
+
46
+
47
+ def migrate(input_file, output_file, collection_id, dataset_id):
48
+ print(f"Converting {input_file} into {output_file}")
49
+
50
+ dataset = utils.read_h5ad(input_file)
51
+
52
+ # Migrate organism_ontology_term_id for 6.0.0
53
+ dataset = utils.move_column_from_obs_to_uns(adata=dataset, column_name="organism_ontology_term_id")
54
+
55
+ # Migrate delimter from `,` to ` || ` for self_reported_ethnicity_ontology_term_id
56
+ utils.replace_delimiter(
57
+ dataframe=dataset.obs,
58
+ old_delimiter=",",
59
+ new_delimiter=" || ",
60
+ column_name="self_reported_ethnicity_ontology_term_id",
61
+ )
62
+
63
+ # AUTOMATED, DO NOT CHANGE
64
+ for ontology_name, deprecated_term_map in ONTOLOGY_TERM_OBS_MAPS.items():
65
+ utils.replace_ontology_term(dataset.obs, ontology_name, deprecated_term_map)
66
+ for ontology_name, deprecated_term_map in ONTOLOGY_TERM_UNS_MAPS.items():
67
+ dataset = utils.replace_ontology_term_uns(dataset, ontology_name, deprecated_term_map)
68
+
69
+ # CURATOR-DEFINED, DATASET-SPECIFIC UPDATES
70
+ # Use the template below to define dataset and collection specific ontology changes. Will only apply to dataset
71
+ # if it matches a condition.
72
+ # If no such changes are needed, leave blank
73
+ # Examples:
74
+ # if dataset_id == "<dataset_1_id>":
75
+ # <no further logic necessary>
76
+ # utils.replace_ontology_term(df, <ontology_name>, {"term_to_replace": "replacement_term", ...})
77
+ # elif dataset_id == "<dataset_2_id>":
78
+ # <custom transformation logic beyond scope of util functions>
79
+ # elif collection_id == "<collection_1_id>":
80
+ # <no further logic necessary>
81
+ # utils.replace_ontology_term(df, <ontology_name>, {"term_to_replace": "replacement_term", ...})
82
+ # elif collection_id == "<collection_2_id>":
83
+ # <custom transformation logic beyond scope of replace_ontology_term>
84
+ # ...
85
+
86
+ if (
87
+ dataset.uns["title"]
88
+ == "Time-resolved single-cell analysis of Brca1 associated mammary tumourigenesis reveals aberrant differentiation of luminal progenitors"
89
+ ):
90
+ utils.replace_ontology_term(dataset.obs, "cell_type", {"CL:0000451": "CL:4047054"})
91
+
92
+ if collection_id == "126afc71-47fb-4e9d-8aaf-9d9f61e0ac77":
93
+ utils.map_ontology_term(
94
+ dataset.obs,
95
+ "disease",
96
+ "disease_group",
97
+ {
98
+ "Healthy": "PATO:0000461",
99
+ "HIV": "MONDO:0005109",
100
+ "VL_HIV": "MONDO:0005109 || MONDO:0005445",
101
+ "aL_HIV": "MONDO:0005109 || MONDO:0011989",
102
+ "cVL_HIV": "MONDO:0005109 || MONDO:0005445",
103
+ "pVL_HIV": "MONDO:0005109 || MONDO:0005445",
104
+ },
105
+ )
106
+
107
+ if collection_id == "eeba7193-4d32-46bd-a21b-089936d60601":
108
+ utils.map_ontology_term(
109
+ dataset.obs,
110
+ "disease",
111
+ "sample_id",
112
+ {
113
+ "mraf1": "MONDO:0004981 || MONDO:1030008",
114
+ "mraf2": "MONDO:0004981 || MONDO:1030008",
115
+ "mraf3": "MONDO:0004981 || MONDO:1030008",
116
+ "mraf4": "MONDO:0004981 || MONDO:1030008",
117
+ "mraf7": "MONDO:0004981 || MONDO:1030008",
118
+ "mraf13": "MONDO:0004981 || MONDO:1030008",
119
+ "mraf14": "MONDO:0004981 || MONDO:1030008",
120
+ },
121
+ )
122
+
123
+ # errors found in 3 dev migration runs
124
+ # will just check for key and delete instead of list of dataset_ids
125
+ if "organism" in dataset.uns:
126
+ del dataset.uns["organism"]
127
+
128
+ # feature_is_filtered set incorrectly for dataset with no raw.X matrix, should be all False
129
+ if dataset_id == "ee195b7d-184d-4dfa-9b1c-51a7e601ac11":
130
+ dataset.var["feature_is_filtered"] = False
131
+
132
+ # 11 datasets with "organism_ontology_term_id_colors" in uns, all can be removed
133
+ # seems easy enough to quickly check for key, can also list out the 11 dataset_ids
134
+ if "organism_ontology_term_id_colors" in dataset.uns:
135
+ del dataset.uns["organism_ontology_term_id_colors"]
136
+
137
+ if GENCODE_MAPPER:
138
+ dataset = utils.remap_deprecated_features(adata=dataset, remapped_features=GENCODE_MAPPER)
139
+
140
+ # AUTOMATED, DO NOT CHANGE -- IF GENCODE UPDATED, DEPRECATED FEATURE FILTERING ALGORITHM WILL GO HERE.
141
+ if DEPRECATED_FEATURE_IDS:
142
+ dataset = utils.remove_deprecated_features(adata=dataset, deprecated=DEPRECATED_FEATURE_IDS)
143
+
144
+ with dask.config.set(scheduler="single-threaded"):
145
+ dataset.write_h5ad(output_file, compression="gzip")
@@ -0,0 +1,54 @@
1
+ 7f7faf6b-f11d-4f07-bc1c-188a4472748d
2
+ 7c497c66-c2e2-4291-8b96-339e5e65f314
3
+ 7b55fe5c-d5c8-48e0-a1a3-54c5b9074f3f
4
+ 2ac76f1b-43ef-4271-8686-2f165570989f
5
+ f9cfac8d-bff6-47a2-a1f6-503827d375f5
6
+ ea95cdd5-cf7e-4d5d-a0bd-032776d32ae5
7
+ e34f3f89-3a48-4173-a06a-9e9803f2e2b9
8
+ e2a325e2-5633-4950-ae46-ebff70979e3b
9
+ d5a2d011-3765-4dad-ac36-3fff087589fe
10
+ b6e502d7-0d07-408e-a291-91b63af0a40a
11
+ a30db232-08a6-48f0-ab9f-ad6977b8a6b1
12
+ 9ea52f85-a35e-4b5b-9207-e2f1de7dbbcd
13
+ 93091496-be48-4122-b945-9af9c22a7535
14
+ 78f40dd6-ed22-470d-8547-6fb5746c25da
15
+ 6929d6d1-6f81-44d7-bd35-5a580259af81
16
+ 5e7fff82-b8eb-4220-bf65-c6feb6765ea1
17
+ 4e374783-0152-423e-bcb6-b87cc232f64d
18
+ 4859642c-d155-437b-adf5-83ca6c1bef67
19
+ 390b376d-8857-4b4b-991f-0d7326938172
20
+ 34229bdc-a895-4394-8820-574e4028d8c6
21
+ 31285bcd-9a28-48ce-8cc0-543af80261d3
22
+ 3079e9b0-c6db-45c8-b998-a4555a73968e
23
+ 1fc48734-35ee-4372-885c-95fd2674c75a
24
+ 1a974af7-8b4b-4abb-8aba-23e2979d9518
25
+ 171b03c9-516b-499c-a434-fa4f70191988
26
+ 149b2c3f-ee11-47a7-984b-923570280bd7
27
+ 0de831e0-a525-4ec5-b717-df56f2de2bf0
28
+ 0087cde2-967d-4f7c-8e6e-40e4c9ad1891
29
+ e04cb7d9-7487-4bf7-99db-b16f236a351a
30
+ b2dd6bc9-36ed-4cd4-a2b3-51f6a2386afa
31
+ a19d1667-a7b5-4556-9e5f-f9bfa690c0f1
32
+ 510151f7-0cf7-4679-a15b-b7cd824f19bb
33
+ 30a2789f-7b76-4414-9c63-1deb3eacdde1
34
+ 20de7fd1-ab21-45b8-a1e2-f7b36261f8e2
35
+ f0939ae1-0877-42b2-bc0e-3249a42c2c0f
36
+ e8d1c9cc-29e6-4b51-90f1-29e75de6098f
37
+ d6f3c096-a6f0-4e67-91ad-514bd286f13e
38
+ a12d95f7-480f-4c8a-bf24-302175b27bc4
39
+ 8f83f50e-c091-4095-8456-a7acf6c9d423
40
+ 52f0b078-0fb7-4cdb-81f8-d1bcf7b4468b
41
+ 427bfbe6-1141-4505-98e3-38aa05339310
42
+ 3ed61f1e-bd31-411e-b4aa-9729e8437732
43
+ 38e5738d-3870-433b-858a-5c364f322439
44
+ 346c5aad-b034-4248-8cbe-0a05fd634b9c
45
+ 33da10b0-9c1d-4c82-9b14-c67cdcf9fae5
46
+ 0500e103-38db-456d-9c3f-b96b8a693ab2
47
+ cb34cb2d-aee2-4272-86ed-f8e1af870e52
48
+ c20f1b97-0d47-4192-af0a-1e012621f8d9
49
+ 3bbb6cf9-72b9-41be-b568-656de6eb18b5
50
+ e40c6272-af77-4a10-9385-62a398884f27
51
+ d6dfdef1-406d-4efb-808c-3c5eddbfe0cb
52
+ 6a270451-b4d9-43e0-aa89-e33aac1ac74b
53
+ c888b684-6c51-431f-972a-6c963044cef0
54
+ ae4f8ddd-cac9-4172-9681-2175da462f2e
@@ -0,0 +1 @@
1
+ [["Single-cell characterization of the stress response in the adult ventral hippocampus", "Ctrl_401"], ["Single-cell RNA sequencing of human intestinal blood endothelial cells", "donor_ulxdkr"], ["Kidney-injury enriched MKA", "Kirita20"]]
@@ -0,0 +1,3 @@
1
+ from cellxgene_ontology_guide.ontology_parser import OntologyParser
2
+
3
+ ONTOLOGY_PARSER = OntologyParser(schema_version="v7.0.0")
@@ -0,0 +1,74 @@
1
+ import logging
2
+
3
+ import anndata as ad
4
+ from pandas import DataFrame
5
+
6
+ from . import schema
7
+ from .utils import getattr_anndata
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class AnnDataLabelRemover:
13
+ """
14
+ From valid h5ad, handles writing a new h5ad file with appended ontology/gene labels removed
15
+ from adata.obs, adata.var, and adata.raw.var respectively as indicated in the schema definition
16
+ """
17
+
18
+ def __init__(self, adata: ad.AnnData = None):
19
+ self.adata = adata
20
+ self.schema_def = schema.get_schema_definition()
21
+
22
+ def remove_labels(self):
23
+ """
24
+ From a valid (per cellxgene's schema) adata, this function parses the associated schema definition for
25
+ 'add_labels.to_column' annotations, and removes specified columns from self.adata
26
+ """
27
+ for component_name in ["obs", "var", "raw.var", "uns"]:
28
+ # If the component does not exist, skip (this is for raw.var)
29
+ component = getattr_anndata(self.adata, component_name)
30
+ if component is None:
31
+ continue
32
+
33
+ component_def = self.schema_def["components"][component_name]
34
+
35
+ # Doing it for columns
36
+ if "columns" in component_def:
37
+ for column_def in component_def["columns"].values():
38
+ if "add_labels" in column_def:
39
+ self._remove_columns(component, column_def)
40
+
41
+ # Remove automatically annotated columns
42
+ if "reserved_columns" in component_def:
43
+ for field in component_def["reserved_columns"]:
44
+ del component[field]
45
+
46
+ # Doing it for index
47
+ if "index" in component_def:
48
+ index_def = component_def["index"]
49
+ if "add_labels" in index_def:
50
+ self._remove_columns(component, index_def)
51
+
52
+ # Remove any labels added as dict keys
53
+ if "keys" in component_def:
54
+ for key in component_def["keys"]:
55
+ key_def = component_def["keys"][key]
56
+ if "add_labels" in key_def:
57
+ key_to_remove = key_def["add_labels"][0]["to_key"]
58
+ del component[key_to_remove]
59
+
60
+ def _remove_columns(self, component: DataFrame, subcomponent_definition: dict):
61
+ """
62
+ Given an adata component and subcomponent definition, this function deletes all existing columns in the
63
+ self.adata component that are defined as added labels ('add_labels.to_column') in the subcomponent definition.
64
+
65
+ :param pd.Dataframe component: dataframe within adata dataset (i.e. 'obs', 'var', 'raw.var')
66
+ :param dict subcomponent_definition: yaml-defined schema for subcomponent of component (i.e. index, or a
67
+ particular column)
68
+
69
+ :rtype None
70
+ """
71
+ for label_def in subcomponent_definition["add_labels"]:
72
+ column_name = label_def["to_column"]
73
+ if column_name in component:
74
+ del component[column_name]
@@ -0,0 +1,21 @@
1
+ import semver
2
+ import yaml
3
+
4
+ from . import __schema_version__, env
5
+
6
+
7
+ def get_schema_definition() -> dict:
8
+ """
9
+ Look up and read the current schema definition
10
+ :return The schema definition
11
+ :rtype dict
12
+ """
13
+
14
+ path = env.SCHEMA_DEFINITION_FILE
15
+ with open(path) as fp:
16
+ return yaml.load(fp, Loader=yaml.FullLoader)
17
+
18
+
19
+ def get_current_schema_version() -> str:
20
+ current_version: semver.Version = semver.Version.parse(__schema_version__)
21
+ return f"{str(current_version.major)}.{str(current_version.minor)}.0"