cellmaps-utils 0.8.0__tar.gz → 0.9.0__tar.gz

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 (104) hide show
  1. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/HISTORY.rst +7 -0
  2. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/PKG-INFO +10 -14
  3. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/README.rst +1 -1
  4. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils/__init__.py +1 -1
  5. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils/provenance.py +10 -1
  6. cellmaps_utils-0.9.0/cellmaps_utils/statsgenerator.py +237 -0
  7. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils.egg-info/PKG-INFO +10 -14
  8. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils.egg-info/SOURCES.txt +3 -0
  9. cellmaps_utils-0.9.0/tests/.DS_Store +0 -0
  10. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/test_provenance.py +54 -0
  11. cellmaps_utils-0.9.0/tests/test_statsgenerator.py +127 -0
  12. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/AUTHORS.rst +0 -0
  13. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/CONTRIBUTING.rst +0 -0
  14. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/LICENSE +0 -0
  15. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/MANIFEST.in +0 -0
  16. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils/apms_readme.txt +0 -0
  17. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils/apmstool.py +0 -0
  18. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils/basecmdtool.py +0 -0
  19. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils/cellmaps_utilscmd.py +0 -0
  20. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils/challenge.py +0 -0
  21. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils/constants.py +0 -0
  22. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils/crispr_readme.txt +0 -0
  23. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils/crisprtool.py +0 -0
  24. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils/ddotconverter.py +0 -0
  25. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils/exceptions.py +0 -0
  26. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils/hcx_utils.py +0 -0
  27. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils/hidefconverter.py +0 -0
  28. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils/hierdiff.py +0 -0
  29. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils/ifimage_readme.txt +0 -0
  30. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils/iftool.py +0 -0
  31. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils/logutils.py +0 -0
  32. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils/music_utils.py +0 -0
  33. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils/ndexupload.py +0 -0
  34. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils/tabletool.py +0 -0
  35. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils.egg-info/dependency_links.txt +0 -0
  36. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils.egg-info/not-zip-safe +0 -0
  37. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils.egg-info/requires.txt +0 -0
  38. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/cellmaps_utils.egg-info/top_level.txt +0 -0
  39. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/Makefile +0 -0
  40. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/_build/html/_static/file.png +0 -0
  41. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/_build/html/_static/minus.png +0 -0
  42. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/_build/html/_static/plus.png +0 -0
  43. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/authors.rst +0 -0
  44. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/cellmaps_utils.rst +0 -0
  45. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/cm4ai_data_release.rst +0 -0
  46. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/conf.py +0 -0
  47. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/contributing.rst +0 -0
  48. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/cx2_to_hcx_conversion.rst +0 -0
  49. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/ddot_converters.rst +0 -0
  50. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/devbranches.rst +0 -0
  51. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/developer.rst +0 -0
  52. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/hidef_converters.rst +0 -0
  53. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/hierarchical_networks.rst +0 -0
  54. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/hierarchy_robustness.rst +0 -0
  55. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/history.rst +0 -0
  56. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/in_project.rst +0 -0
  57. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/index.rst +0 -0
  58. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/installation.rst +0 -0
  59. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/integrationtesting.rst +0 -0
  60. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/make.bat +0 -0
  61. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/modules.rst +0 -0
  62. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/newrelease.rst +0 -0
  63. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/outputs.rst +0 -0
  64. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/pypircfile.rst +0 -0
  65. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/upload_to_ndex.rst +0 -0
  66. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/usage.rst +0 -0
  67. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/docs/versioningscheme.rst +0 -0
  68. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/pyproject.toml +0 -0
  69. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/setup.cfg +0 -0
  70. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/setup.py +0 -0
  71. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/__init__.py +0 -0
  72. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/data/ddot_output.ont +0 -0
  73. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/data/hidef_output.edges +0 -0
  74. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/data/hidef_output.nodes +0 -0
  75. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/data/images/blue.jpg +0 -0
  76. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/data/images/green.jpg +0 -0
  77. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/data/images/red.jpg +0 -0
  78. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/data/images/yellow.jpg +0 -0
  79. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/data/parent_edgelist +0 -0
  80. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/data/reactome_ddot.txt +0 -0
  81. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/data/repl1_example.tsv +0 -0
  82. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/data/repl1_repl2_combined.tsv +0 -0
  83. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/data/repl1_repl2_id_mapping.json +0 -0
  84. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/data/repl2_example.tsv +0 -0
  85. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/data/ro-crate-metadata.json +0 -0
  86. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/data/test_password +0 -0
  87. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/data/testsolrepl1.tsv +0 -0
  88. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/data/testsolrepl2.tsv +0 -0
  89. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/data/testsolution.csv +0 -0
  90. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/data/testsolution_genes.cx2 +0 -0
  91. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/test_apmstool.py +0 -0
  92. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/test_basecommandlinetool.py +0 -0
  93. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/test_challenge.py +0 -0
  94. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/test_crisprtool.py +0 -0
  95. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/test_ddotconverter.py +0 -0
  96. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/test_helloworldcommand.py +0 -0
  97. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/test_hidefconverter.py +0 -0
  98. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/test_iftool.py +0 -0
  99. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/test_integation_provenance.py +0 -0
  100. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/test_logutils.py +0 -0
  101. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/test_ndexupload.py +0 -0
  102. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/test_solutiongenerator.py +0 -0
  103. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/test_tabletool.py +0 -0
  104. {cellmaps_utils-0.8.0 → cellmaps_utils-0.9.0}/tests/test_tworeplcoelutionchallengegenerator.py +0 -0
@@ -2,6 +2,13 @@
2
2
  History
3
3
  =======
4
4
 
5
+ 0.9.0 (2025-07-03)
6
+ -------------------
7
+
8
+ * Add statistics generator for ro-crates
9
+
10
+ * Fix provenance bugs for new versions of ro-crate
11
+
5
12
  0.8.0 (2025-04-23)
6
13
  -------------------
7
14
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: cellmaps_utils
3
- Version: 0.8.0
3
+ Version: 0.9.0
4
4
  Summary: Contains utilities needed by Cell Maps tools
5
5
  Home-page: https://github.com/idekerlab/cellmaps_utils
6
6
  Author: Cell Maps team
@@ -27,17 +27,6 @@ Requires-Dist: numpy<2.0.0,>=1.24.4
27
27
  Requires-Dist: dill<0.4.0,>=0.3.8
28
28
  Requires-Dist: tqdm<5.0.0,>=4.66.6
29
29
  Requires-Dist: requests<3.0.0,>=2.31.0
30
- Dynamic: author
31
- Dynamic: author-email
32
- Dynamic: classifier
33
- Dynamic: description
34
- Dynamic: description-content-type
35
- Dynamic: home-page
36
- Dynamic: keywords
37
- Dynamic: license
38
- Dynamic: license-file
39
- Dynamic: requires-dist
40
- Dynamic: summary
41
30
 
42
31
  =============================
43
32
  Cell Maps Pipeline Utilities
@@ -120,7 +109,7 @@ Output:
120
109
  dist builds source and wheel package
121
110
  install install the package to the active Python's site-packages
122
111
 
123
- Before running tests, please install ``pip install -r requirements_dev``
112
+ Before running tests, please install ``pip install -r requirements_dev.txt``
124
113
 
125
114
  For developers
126
115
  -------------------------------------------
@@ -160,6 +149,13 @@ This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypack
160
149
  History
161
150
  =======
162
151
 
152
+ 0.9.0 (2025-07-03)
153
+ -------------------
154
+
155
+ * Add statistics generator for ro-crates
156
+
157
+ * Fix provenance bugs for new versions of ro-crate
158
+
163
159
  0.8.0 (2025-04-23)
164
160
  -------------------
165
161
 
@@ -79,7 +79,7 @@ Output:
79
79
  dist builds source and wheel package
80
80
  install install the package to the active Python's site-packages
81
81
 
82
- Before running tests, please install ``pip install -r requirements_dev``
82
+ Before running tests, please install ``pip install -r requirements_dev.txt``
83
83
 
84
84
  For developers
85
85
  -------------------------------------------
@@ -4,6 +4,6 @@
4
4
 
5
5
  __author__ = 'Cell Maps team'
6
6
  __email__ = 'tools@cm4ai.org'
7
- __version__ = '0.8.0'
7
+ __version__ = '0.9.0'
8
8
  __repo_url__ = 'https://github.com/idekerlab/cellmaps_utils'
9
9
  __description__ = 'Contains utilities needed by Cell Maps tools'
@@ -361,7 +361,13 @@ class ProvenanceUtil(object):
361
361
  data = rocrate
362
362
  else:
363
363
  data = self.get_rocrate_as_dict(rocrate)
364
- return data['@id']
364
+ if '@id' not in data.keys():
365
+ rocrate_id = data.get('@graph')[1].get('@id', None)
366
+ if rocrate_id is None:
367
+ raise CellMapsProvenanceError('ID of ro-crate not found')
368
+ return rocrate_id
369
+ else:
370
+ return data['@id']
365
371
 
366
372
  def get_name_project_org_of_rocrate(self, rocrate):
367
373
  """
@@ -395,6 +401,9 @@ class ProvenanceUtil(object):
395
401
  else:
396
402
  data = self.get_rocrate_as_dict(rocrate)
397
403
 
404
+ if 'name' not in data.keys():
405
+ data = data.get('@graph')[1]
406
+
398
407
  name = data['name']
399
408
  description = data['description']
400
409
  keywords = data['keywords']
@@ -0,0 +1,237 @@
1
+ import os
2
+ import argparse
3
+ import json
4
+ import pandas as pd
5
+ import re
6
+ from collections import defaultdict
7
+ from functools import lru_cache
8
+ import mygene
9
+
10
+ CELL_LINES = ['MDA-MB-468', 'Kolf2.1J']
11
+
12
+
13
+ @lru_cache(maxsize=10000)
14
+ def query_gene_symbols(ensembl_id_tuple):
15
+ mg = mygene.MyGeneInfo()
16
+ results = mg.querymany(list(ensembl_id_tuple), scopes='ensembl.gene', fields='symbol', species='human')
17
+ mapping = {}
18
+ for entry in results:
19
+ if not entry.get('notfound') and 'symbol' in entry:
20
+ mapping[entry['query']] = entry['symbol']
21
+ else:
22
+ mapping[entry['query']] = f"UNKNOWN:{entry['query']}"
23
+ return mapping
24
+
25
+
26
+ def get_gene_symbols_from_ensembl_ids(ensembl_ids):
27
+ unique_ids = set(ensembl_ids)
28
+ return query_gene_symbols(tuple(unique_ids))
29
+
30
+
31
+ def extract_cell_line_names(json_path):
32
+ known_cell_lines = CELL_LINES
33
+ found = []
34
+ try:
35
+ with open(json_path, "r", encoding="utf-8") as f:
36
+ data = json.load(f)
37
+
38
+ def collect_texts(d):
39
+ texts = []
40
+ for value in d.values():
41
+ if isinstance(value, str):
42
+ texts.append(value)
43
+ elif isinstance(value, list):
44
+ texts.extend([v for v in value if isinstance(v, str)])
45
+ elif isinstance(value, dict):
46
+ texts.extend(collect_texts(value))
47
+ return texts
48
+
49
+ all_text = collect_texts(data)
50
+ if "@graph" in data and isinstance(data["@graph"], list):
51
+ for entry in data["@graph"]:
52
+ if isinstance(entry, dict):
53
+ all_text.extend(collect_texts(entry))
54
+ combined_text = " ".join(all_text)
55
+
56
+ for cl in known_cell_lines:
57
+ if re.search(rf'\b{re.escape(cl)}\b', combined_text, re.IGNORECASE):
58
+ found.append(cl)
59
+
60
+ return sorted(found) if found else ["<not found>"]
61
+ except Exception as e:
62
+ return [f"Error reading file: {e}"]
63
+
64
+
65
+ def find_first_csv_or_tsv(folder):
66
+ for fname in os.listdir(folder):
67
+ if fname.lower().endswith(".csv") or fname.lower().endswith(".tsv"):
68
+ return os.path.join(folder, fname)
69
+ return None
70
+
71
+
72
+ def get_ensembl_ids(file_path):
73
+ try:
74
+ if file_path.endswith(".tsv"):
75
+ df = pd.read_csv(file_path, sep='\t')
76
+ else:
77
+ df = pd.read_csv(file_path)
78
+ if "ENSEMBL ID" in df.columns:
79
+ return df["ENSEMBL ID"].dropna().tolist()
80
+ except Exception:
81
+ pass
82
+ return []
83
+
84
+
85
+ def get_protein_names(file_path):
86
+ try:
87
+ if file_path.endswith(".tsv"):
88
+ df = pd.read_csv(file_path, sep='\t')
89
+ else:
90
+ df = pd.read_csv(file_path)
91
+ if "Bait" in df.columns and "PreyGene.x" in df.columns:
92
+ bait = df["Bait"].dropna().tolist()
93
+ prey = df["PreyGene.x"].dropna().tolist()
94
+ all_proteins = bait + prey
95
+ cleaned = {p.split("_")[0] for p in all_proteins}
96
+ return cleaned
97
+ except Exception:
98
+ pass
99
+ return set()
100
+
101
+
102
+ def count_unique_image_filenames_per_cell_line(images_crates):
103
+ channels = ['blue', 'green', 'red', 'yellow']
104
+ cell_line_to_filenames = defaultdict(set)
105
+
106
+ for crate in images_crates:
107
+ crate = crate.strip()
108
+ if not os.path.isdir(crate):
109
+ continue
110
+ metadata_path = os.path.join(crate, "ro-crate-metadata.json")
111
+ if not os.path.isfile(metadata_path):
112
+ continue
113
+ cell_lines = extract_cell_line_names(metadata_path)
114
+ for color in channels:
115
+ color_path = os.path.join(crate, color)
116
+ if not os.path.isdir(color_path):
117
+ continue
118
+ filenames = [f for f in os.listdir(color_path) if os.path.isfile(os.path.join(color_path, f))]
119
+ for cl in cell_lines:
120
+ cell_line_to_filenames[cl].update(filenames)
121
+
122
+ return {cl: len(files) for cl, files in cell_line_to_filenames.items()}
123
+
124
+
125
+ def check_and_aggregate_images(images_crates):
126
+ cell_line_to_ensembl = defaultdict(set)
127
+
128
+ for path in images_crates:
129
+ path = path.strip()
130
+ metadata_path = os.path.join(path, "ro-crate-metadata.json")
131
+ if not os.path.isfile(metadata_path):
132
+ continue
133
+ cell_lines = extract_cell_line_names(metadata_path)
134
+ if not cell_lines or cell_lines == ["<not found>"]:
135
+ continue
136
+ ensembl_file = find_first_csv_or_tsv(path)
137
+ if not ensembl_file:
138
+ continue
139
+ ensembl_ids = get_ensembl_ids(ensembl_file)
140
+ if not ensembl_ids:
141
+ continue
142
+ for cell_line in cell_lines:
143
+ cell_line_to_ensembl[cell_line].update(ensembl_ids)
144
+
145
+ all_gene_symbols = defaultdict(set)
146
+ for cl, ids in cell_line_to_ensembl.items():
147
+ symbol_map = get_gene_symbols_from_ensembl_ids(list(ids))
148
+ all_gene_symbols[cl] = set(symbol_map.values())
149
+
150
+ print("\nImages Crates:")
151
+ for cl in CELL_LINES:
152
+ print(f"{cl}: {len(all_gene_symbols[cl])} unique proteins")
153
+ return all_gene_symbols
154
+
155
+
156
+ def check_and_aggregate_interactions(interaction_crates):
157
+ cell_line_to_proteins = defaultdict(set)
158
+
159
+ for path in interaction_crates:
160
+ path = path.strip()
161
+ metadata_path = os.path.join(path, "ro-crate-metadata.json")
162
+ if not os.path.isfile(metadata_path):
163
+ continue
164
+ cell_lines = extract_cell_line_names(metadata_path)
165
+ if not cell_lines or cell_lines == ["<not found>"]:
166
+ continue
167
+ interaction_file = find_first_csv_or_tsv(path)
168
+ if not interaction_file:
169
+ continue
170
+ names = get_protein_names(interaction_file)
171
+ if not names:
172
+ continue
173
+ for cell_line in cell_lines:
174
+ cell_line_to_proteins[cell_line].update(names)
175
+
176
+ print("\nInteraction Crates:")
177
+ for cl in CELL_LINES:
178
+ print(f"{cl}: {len(cell_line_to_proteins[cl])} unique proteins")
179
+ return cell_line_to_proteins
180
+
181
+
182
+ def save_summary_table(proteins, interactions, image_counts, output_file="statistics.csv"):
183
+ data = []
184
+ differentiation = {
185
+ "MDA-MB-468": ["NA"],
186
+ "Kolf2.1J": ["undifferentiated", "neuron", "cardiomycyte"]
187
+ }
188
+
189
+ for cl in CELL_LINES:
190
+ interaction_proteins = interactions.get(cl, set())
191
+ protein_crate_proteins = proteins.get(cl, set())
192
+ total = interaction_proteins.union(protein_crate_proteins)
193
+ image_count = image_counts.get(cl, "")
194
+
195
+ for diff in differentiation[cl]:
196
+ row = {
197
+ "Cell Line/Type": cl,
198
+ "Differentiation": diff,
199
+ "Protein interactions detected": len(interaction_proteins),
200
+ "Number of immunofluorescent images": image_count,
201
+ "Number immunofluorescent proteins detected": len(protein_crate_proteins),
202
+ "Number of genes knocked down in perturb seq": "",
203
+ "Total proteins investigated": len(total)
204
+ }
205
+ data.append(row)
206
+
207
+ df = pd.DataFrame(data)
208
+ df.to_csv(output_file, index=False)
209
+ print(f"\nSummary table saved to: {output_file}")
210
+
211
+
212
+ def generate_dataset_statistics(images_crates, interaction_crates):
213
+ proteins = defaultdict(set)
214
+ interactions = defaultdict(set)
215
+ image_counts = defaultdict(int)
216
+
217
+ if images_crates:
218
+ proteins = check_and_aggregate_images(images_crates)
219
+ image_counts = count_unique_image_filenames_per_cell_line(images_crates)
220
+
221
+ if interaction_crates:
222
+ interactions = check_and_aggregate_interactions(interaction_crates)
223
+
224
+ print("\nAll:")
225
+ for cl in CELL_LINES:
226
+ union = proteins[cl] | interactions[cl]
227
+ print(f"{cl}: {len(union)} unique proteins")
228
+
229
+ save_summary_table(proteins, interactions, image_counts)
230
+
231
+ if __name__ == "__main__":
232
+ parser = argparse.ArgumentParser(description="Generate statistics for datasets")
233
+ parser.add_argument("--interaction_crates", nargs='+', help="List of interaction RO-Crate folder paths.")
234
+ parser.add_argument("--images_crates", nargs='+', help="List of image RO-Crate folder paths.")
235
+ args = parser.parse_args()
236
+
237
+ generate_dataset_statistics(args.images_crates, args.interaction_crates)
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: cellmaps_utils
3
- Version: 0.8.0
3
+ Version: 0.9.0
4
4
  Summary: Contains utilities needed by Cell Maps tools
5
5
  Home-page: https://github.com/idekerlab/cellmaps_utils
6
6
  Author: Cell Maps team
@@ -27,17 +27,6 @@ Requires-Dist: numpy<2.0.0,>=1.24.4
27
27
  Requires-Dist: dill<0.4.0,>=0.3.8
28
28
  Requires-Dist: tqdm<5.0.0,>=4.66.6
29
29
  Requires-Dist: requests<3.0.0,>=2.31.0
30
- Dynamic: author
31
- Dynamic: author-email
32
- Dynamic: classifier
33
- Dynamic: description
34
- Dynamic: description-content-type
35
- Dynamic: home-page
36
- Dynamic: keywords
37
- Dynamic: license
38
- Dynamic: license-file
39
- Dynamic: requires-dist
40
- Dynamic: summary
41
30
 
42
31
  =============================
43
32
  Cell Maps Pipeline Utilities
@@ -120,7 +109,7 @@ Output:
120
109
  dist builds source and wheel package
121
110
  install install the package to the active Python's site-packages
122
111
 
123
- Before running tests, please install ``pip install -r requirements_dev``
112
+ Before running tests, please install ``pip install -r requirements_dev.txt``
124
113
 
125
114
  For developers
126
115
  -------------------------------------------
@@ -160,6 +149,13 @@ This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypack
160
149
  History
161
150
  =======
162
151
 
152
+ 0.9.0 (2025-07-03)
153
+ -------------------
154
+
155
+ * Add statistics generator for ro-crates
156
+
157
+ * Fix provenance bugs for new versions of ro-crate
158
+
163
159
  0.8.0 (2025-04-23)
164
160
  -------------------
165
161
 
@@ -27,6 +27,7 @@ cellmaps_utils/logutils.py
27
27
  cellmaps_utils/music_utils.py
28
28
  cellmaps_utils/ndexupload.py
29
29
  cellmaps_utils/provenance.py
30
+ cellmaps_utils/statsgenerator.py
30
31
  cellmaps_utils/tabletool.py
31
32
  cellmaps_utils.egg-info/PKG-INFO
32
33
  cellmaps_utils.egg-info/SOURCES.txt
@@ -63,6 +64,7 @@ docs/versioningscheme.rst
63
64
  docs/_build/html/_static/file.png
64
65
  docs/_build/html/_static/minus.png
65
66
  docs/_build/html/_static/plus.png
67
+ tests/.DS_Store
66
68
  tests/__init__.py
67
69
  tests/test_apmstool.py
68
70
  tests/test_basecommandlinetool.py
@@ -77,6 +79,7 @@ tests/test_logutils.py
77
79
  tests/test_ndexupload.py
78
80
  tests/test_provenance.py
79
81
  tests/test_solutiongenerator.py
82
+ tests/test_statsgenerator.py
80
83
  tests/test_tabletool.py
81
84
  tests/test_tworeplcoelutionchallengegenerator.py
82
85
  tests/data/ddot_output.ont
Binary file
@@ -753,3 +753,57 @@ class TestProvenanceUtil(unittest.TestCase):
753
753
  import time
754
754
  print(os.listdir(os.path.join(temp_dir, 'test_rocrate')))
755
755
  shutil.rmtree(temp_dir)
756
+
757
+ def test_get_rocrate_provenance_attributes_from_valid_dict(self):
758
+ prov = ProvenanceUtil()
759
+ rocrate_dict = {
760
+ 'name': 'Test Crate',
761
+ 'description': 'Crate for testing',
762
+ 'keywords': ['proj', 'v1.0', 'HeLa', 'drugA', 'embedding'],
763
+ 'isPartOf': [
764
+ {'@type': 'Organization', 'name': 'TestOrg'},
765
+ {'@type': 'Project', 'name': 'TestProject'}
766
+ ]
767
+ }
768
+
769
+ attrs = prov.get_rocrate_provenance_attributes(rocrate_dict)
770
+ self.assertEqual(attrs.get_name(), 'Test Crate')
771
+ self.assertEqual(attrs.get_description(), 'Crate for testing')
772
+ self.assertEqual(attrs.get_organization_name(), 'TestOrg')
773
+ self.assertEqual(attrs.get_project_name(), 'TestProject')
774
+ self.assertEqual(attrs.get_keywords(), ['proj', 'v1.0', 'HeLa', 'drugA', 'embedding'])
775
+
776
+ def test_get_rocrate_provenance_attributes_missing_fields(self):
777
+ prov = ProvenanceUtil()
778
+ rocrate_dict = {
779
+ 'name': 'Test Crate',
780
+ 'description': 'Missing project/org',
781
+ 'keywords': ['x'],
782
+ 'isPartOf': []
783
+ }
784
+
785
+ attrs = prov.get_rocrate_provenance_attributes(rocrate_dict)
786
+ self.assertEqual(attrs.get_project_name(), None)
787
+ self.assertEqual(attrs.get_organization_name(), None)
788
+
789
+ def test_get_id_of_rocrate_from_graph_structure(self):
790
+ prov = ProvenanceUtil()
791
+ crate_dict = {
792
+ '@graph': [
793
+ {'@id': 'ignored'},
794
+ {'@id': 'expected_id'}
795
+ ]
796
+ }
797
+ result = prov.get_id_of_rocrate(crate_dict)
798
+ self.assertEqual(result, 'expected_id')
799
+
800
+ def test_get_id_of_rocrate_fails_if_id_not_found(self):
801
+ prov = ProvenanceUtil()
802
+ crate_dict = {
803
+ '@graph': [
804
+ {'@id': 'ignored'},
805
+ {'wrongkey': 'oops'}
806
+ ]
807
+ }
808
+ with self.assertRaises(CellMapsProvenanceError):
809
+ prov.get_id_of_rocrate(crate_dict)
@@ -0,0 +1,127 @@
1
+ import unittest
2
+ import tempfile
3
+ import os
4
+ import json
5
+ from unittest.mock import patch
6
+
7
+ import pandas as pd
8
+ from cellmaps_utils.statsgenerator import (
9
+ extract_cell_line_names,
10
+ find_first_csv_or_tsv,
11
+ get_ensembl_ids,
12
+ count_unique_image_filenames_per_cell_line,
13
+ get_protein_names,
14
+ save_summary_table,
15
+ generate_dataset_statistics
16
+ )
17
+
18
+ class TestDatasetStatistics(unittest.TestCase):
19
+
20
+ def setUp(self):
21
+ self.test_dir = tempfile.TemporaryDirectory()
22
+ self.base_path = self.test_dir.name
23
+
24
+ def tearDown(self):
25
+ self.test_dir.cleanup()
26
+
27
+ def test_extract_cell_line_names_found(self):
28
+ metadata = {
29
+ "@graph": [{"description": "Experiment using MDA-MB-468 cell line"}]
30
+ }
31
+ file_path = os.path.join(self.base_path, "ro-crate-metadata.json")
32
+ with open(file_path, "w") as f:
33
+ json.dump(metadata, f)
34
+
35
+ result = extract_cell_line_names(file_path)
36
+ self.assertEqual(result, ["MDA-MB-468"])
37
+
38
+ def test_extract_cell_line_names_not_found(self):
39
+ metadata = {
40
+ "@graph": [{"description": "No matching cell line"}]
41
+ }
42
+ file_path = os.path.join(self.base_path, "ro-crate-metadata.json")
43
+ with open(file_path, "w") as f:
44
+ json.dump(metadata, f)
45
+
46
+ result = extract_cell_line_names(file_path)
47
+ self.assertEqual(result, ["<not found>"])
48
+
49
+ def test_find_first_csv_or_tsv(self):
50
+ file_path = os.path.join(self.base_path, "data.tsv")
51
+ with open(file_path, "w") as f:
52
+ f.write("ENSEMBL ID\nENSG000001234\n")
53
+
54
+ found = find_first_csv_or_tsv(self.base_path)
55
+ self.assertTrue(found.endswith("data.tsv"))
56
+
57
+ def test_get_ensembl_ids(self):
58
+ df = pd.DataFrame({"ENSEMBL ID": ["ENSG000001", "ENSG000002"]})
59
+ file_path = os.path.join(self.base_path, "mock.csv")
60
+ df.to_csv(file_path, index=False)
61
+
62
+ result = get_ensembl_ids(file_path)
63
+ self.assertEqual(result, ["ENSG000001", "ENSG000002"])
64
+
65
+ def test_count_unique_image_filenames_per_cell_line(self):
66
+ meta = {
67
+ "@graph": [{"name": "image crate for Kolf2.1J"}]
68
+ }
69
+ metadata_path = os.path.join(self.base_path, "ro-crate-metadata.json")
70
+ with open(metadata_path, "w") as f:
71
+ json.dump(meta, f)
72
+
73
+ # Setup: image file in "green"
74
+ green_path = os.path.join(self.base_path, "green")
75
+ os.makedirs(green_path)
76
+ with open(os.path.join(green_path, "img1.png"), "w") as f:
77
+ f.write("fake image")
78
+
79
+ result = count_unique_image_filenames_per_cell_line([self.base_path])
80
+ self.assertEqual(result["Kolf2.1J"], 1)
81
+
82
+ def test_get_protein_names(self):
83
+ df = pd.DataFrame({
84
+ "Bait": ["TP53_abc", "BRCA1_123"],
85
+ "PreyGene.x": ["EGFR_1", "MYC_test"]
86
+ })
87
+ file_path = os.path.join(self.base_path, "interact.csv")
88
+ df.to_csv(file_path, index=False)
89
+
90
+ result = get_protein_names(file_path)
91
+ self.assertEqual(result, {"TP53", "BRCA1", "EGFR", "MYC"})
92
+
93
+ def test_save_summary_table(self):
94
+ proteins = {"MDA-MB-468": {"A", "B"}, "Kolf2.1J": {"X", "Y"}}
95
+ interactions = {"MDA-MB-468": {"B", "C"}, "Kolf2.1J": {"Y"}}
96
+ image_counts = {"MDA-MB-468": 4, "Kolf2.1J": 10}
97
+
98
+ file_path = os.path.join(self.base_path, "output.csv")
99
+ save_summary_table(proteins, interactions, image_counts, output_file=file_path)
100
+
101
+ df = pd.read_csv(file_path)
102
+ self.assertEqual(len(df), 4) # 1 MDA + 3 Kolf2 rows
103
+ self.assertIn("Total proteins investigated", df.columns)
104
+ self.assertEqual(df[df["Cell Line/Type"] == "MDA-MB-468"]["Total proteins investigated"].values[0], 3)
105
+
106
+ def test_generate_dataset_statistics_workflow(self):
107
+ # Setup minimal image crate
108
+ img_dir = os.path.join(self.base_path, "blue")
109
+ os.makedirs(img_dir)
110
+ with open(os.path.join(img_dir, "img.tif"), "w") as f:
111
+ f.write("fake")
112
+
113
+ # Add metadata with a cell line
114
+ with open(os.path.join(self.base_path, "ro-crate-metadata.json"), "w") as f:
115
+ json.dump({"@graph": [{"name": "Study with Kolf2.1J"}]}, f)
116
+
117
+ # Add ENSEMBL file
118
+ df = pd.DataFrame({"ENSEMBL ID": ["ENSG000001"]})
119
+ df.to_csv(os.path.join(self.base_path, "table.tsv"), sep="\t", index=False)
120
+
121
+ # Patch gene symbol lookup to avoid mygene dependency
122
+ with patch("cellmaps_utils.statsgenerator.get_gene_symbols_from_ensembl_ids") as mock_mapper:
123
+ mock_mapper.return_value = {"ENSG000001": "TESTGENE"}
124
+ generate_dataset_statistics(images_crates=[self.base_path], interaction_crates=[])
125
+
126
+ if __name__ == "__main__":
127
+ unittest.main()
File without changes
File without changes
File without changes