cellarr 0.3.2__tar.gz → 0.4.1.dev1__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 (66) hide show
  1. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/.github/workflows/pypi-publish.yml +2 -2
  2. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/.github/workflows/pypi-test.yml +1 -1
  3. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/.pre-commit-config.yaml +2 -2
  4. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/CHANGELOG.md +5 -0
  5. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/PKG-INFO +2 -2
  6. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/setup.cfg +1 -1
  7. cellarr-0.4.1.dev1/src/cellarr/slurm/README.md +58 -0
  8. cellarr-0.4.1.dev1/src/cellarr/slurm/__init__.py +0 -0
  9. cellarr-0.4.1.dev1/src/cellarr/slurm/build_cellarr_steps.py +287 -0
  10. cellarr-0.4.1.dev1/src/cellarr/slurm/build_cellarr_steps_bkp.py +185 -0
  11. cellarr-0.4.1.dev1/src/cellarr/slurm/final_assembly.py +20 -0
  12. cellarr-0.4.1.dev1/src/cellarr/slurm/finalize_matrix.py +45 -0
  13. cellarr-0.4.1.dev1/src/cellarr/slurm/process_cell_metadata.py +68 -0
  14. cellarr-0.4.1.dev1/src/cellarr/slurm/process_gene_annotation.py +33 -0
  15. cellarr-0.4.1.dev1/src/cellarr/slurm/process_matrix.py +50 -0
  16. cellarr-0.4.1.dev1/src/cellarr/slurm/process_matrix_all.py +40 -0
  17. cellarr-0.4.1.dev1/src/cellarr/slurm/process_sample_metadata.py +76 -0
  18. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/src/cellarr.egg-info/PKG-INFO +2 -2
  19. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/src/cellarr.egg-info/SOURCES.txt +11 -0
  20. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/.coveragerc +0 -0
  21. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/.gitignore +0 -0
  22. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/.readthedocs.yml +0 -0
  23. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/AUTHORS.md +0 -0
  24. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/CONTRIBUTING.md +0 -0
  25. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/LICENSE.txt +0 -0
  26. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/README.md +0 -0
  27. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/assets/cellarr.jpg +0 -0
  28. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/assets/cellarr.png +0 -0
  29. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/assets/cellarr.svg +0 -0
  30. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/docs/Makefile +0 -0
  31. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/docs/_static/.gitignore +0 -0
  32. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/docs/authors.md +0 -0
  33. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/docs/changelog.md +0 -0
  34. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/docs/conf.py +0 -0
  35. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/docs/contributing.md +0 -0
  36. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/docs/index.md +0 -0
  37. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/docs/license.md +0 -0
  38. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/docs/notes.md +0 -0
  39. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/docs/readme.md +0 -0
  40. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/docs/requirements.txt +0 -0
  41. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/docs/tutorial.md +0 -0
  42. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/pyproject.toml +0 -0
  43. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/setup.py +0 -0
  44. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/src/cellarr/CellArrDataset.py +0 -0
  45. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/src/cellarr/CellArrDatasetSlice.py +0 -0
  46. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/src/cellarr/__init__.py +0 -0
  47. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/src/cellarr/autoencoder.py +0 -0
  48. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/src/cellarr/build_cellarrdataset.py +0 -0
  49. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/src/cellarr/build_options.py +0 -0
  50. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/src/cellarr/buildutils_tiledb_array.py +0 -0
  51. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/src/cellarr/buildutils_tiledb_frame.py +0 -0
  52. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/src/cellarr/dataloader.py +0 -0
  53. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/src/cellarr/queryutils_tiledb_frame.py +0 -0
  54. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/src/cellarr/utils_anndata.py +0 -0
  55. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/src/cellarr.egg-info/dependency_links.txt +0 -0
  56. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/src/cellarr.egg-info/not-zip-safe +0 -0
  57. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/src/cellarr.egg-info/requires.txt +0 -0
  58. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/src/cellarr.egg-info/top_level.txt +0 -0
  59. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/tests/conftest.py +0 -0
  60. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/tests/data/adata.h5ad +0 -0
  61. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/tests/test_anndata_utils.py +0 -0
  62. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/tests/test_build.py +0 -0
  63. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/tests/test_custom_config.py +0 -0
  64. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/tests/test_dataloader.py +0 -0
  65. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/tests/test_query.py +0 -0
  66. {cellarr-0.3.2 → cellarr-0.4.1.dev1}/tox.ini +0 -0
@@ -14,10 +14,10 @@ jobs:
14
14
 
15
15
  steps:
16
16
  - uses: actions/checkout@v4
17
- - name: Set up Python 3.9
17
+ - name: Set up Python 3.11
18
18
  uses: actions/setup-python@v5
19
19
  with:
20
- python-version: 3.9
20
+ python-version: 3.11
21
21
  - name: Install dependencies
22
22
  run: |
23
23
  python -m pip install --upgrade pip
@@ -15,7 +15,7 @@ jobs:
15
15
  runs-on: ubuntu-latest
16
16
  strategy:
17
17
  matrix:
18
- python-version: [ '3.8', '3.9', '3.10', '3.11', '3.12' ]
18
+ python-version: [ '3.9', '3.10', '3.11', '3.12' ]
19
19
 
20
20
  name: Python ${{ matrix.python-version }}
21
21
  steps:
@@ -2,7 +2,7 @@ exclude: '^docs/conf.py'
2
2
 
3
3
  repos:
4
4
  - repo: https://github.com/pre-commit/pre-commit-hooks
5
- rev: v4.6.0
5
+ rev: v5.0.0
6
6
  hooks:
7
7
  - id: trailing-whitespace
8
8
  - id: check-added-large-files
@@ -33,7 +33,7 @@ repos:
33
33
 
34
34
  - repo: https://github.com/astral-sh/ruff-pre-commit
35
35
  # Ruff version.
36
- rev: v0.6.8
36
+ rev: v0.8.2
37
37
  hooks:
38
38
  - id: ruff
39
39
  args: [--fix, --exit-non-zero-on-fix]
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## Version 0.4.0
4
+
5
+ - chore: Remove Python 3.8 (EOL).
6
+ - precommit: Replace docformatter with ruff's formatter.
7
+
3
8
  ## Version 0.3.2
4
9
 
5
10
  - Functionality to iterate over samples and cells.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cellarr
3
- Version: 0.3.2
3
+ Version: 0.4.1.dev1
4
4
  Summary: TileDB-based array storage for genomics data collections.
5
5
  Home-page: https://github.com/BiocPy/cellarr
6
6
  Author: Jayaram Kancherla
@@ -10,7 +10,7 @@ Project-URL: Documentation, https://github.com/BiocPy/cellarr
10
10
  Platform: any
11
11
  Classifier: Development Status :: 4 - Beta
12
12
  Classifier: Programming Language :: Python
13
- Requires-Python: >=3.8
13
+ Requires-Python: >=3.9
14
14
  Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
15
15
  License-File: LICENSE.txt
16
16
  Requires-Dist: importlib-metadata; python_version < "3.8"
@@ -21,7 +21,7 @@ packages = find_namespace:
21
21
  include_package_data = True
22
22
  package_dir =
23
23
  =src
24
- python_requires = >=3.8
24
+ python_requires = >=3.9
25
25
  install_requires =
26
26
  importlib-metadata; python_version<"3.8"
27
27
  anndata
@@ -0,0 +1,58 @@
1
+
2
+ # manifest json
3
+
4
+ ```json
5
+ {
6
+ "files": [
7
+ "/path/to/dataset1.h5ad",
8
+ "/path/to/dataset2.h5ad"
9
+ ],
10
+ "matrix_options": [
11
+ {
12
+ "matrix_name": "counts",
13
+ "dtype": "uint32"
14
+ },
15
+ {
16
+ "matrix_name": "normalized",
17
+ "dtype": "float32"
18
+ }
19
+ ],
20
+ "gene_options": {
21
+ "feature_column": "index"
22
+ },
23
+ "sample_options": {
24
+ "metadata": {
25
+ "sample_1": {
26
+ "condition": "control",
27
+ "batch": "1"
28
+ },
29
+ "sample_2": {
30
+ "condition": "treatment",
31
+ "batch": "1"
32
+ }
33
+ }
34
+ },
35
+ "cell_options": {
36
+ "column_types": {
37
+ "cell_type": "ascii",
38
+ "quality_score": "float32"
39
+ },
40
+ "metadata": {
41
+ "processing_date": ["2024-01-01", "2024-01-02", ...]
42
+ }
43
+ }
44
+ }
45
+ ```
46
+
47
+
48
+ Run
49
+
50
+ ```sh
51
+
52
+ python build_cellarr_steps.py \
53
+ --input-manifest manifest.json \
54
+ --output-dir /path/to/output \
55
+ --memory-per-job 64 \
56
+ --cpus-per-task 4
57
+
58
+ ```
File without changes
@@ -0,0 +1,287 @@
1
+ import argparse
2
+ import json
3
+ import subprocess
4
+ from pathlib import Path
5
+ from typing import Dict, List, Optional, Tuple
6
+
7
+ import anndata
8
+ import numpy as np
9
+
10
+ from cellarr.buildutils_tiledb_array import create_tiledb_array
11
+
12
+ __author__ = "Jayaram Kancherla"
13
+ __copyright__ = "Jayaram Kancherla"
14
+ __license__ = "MIT"
15
+
16
+
17
+ class SlurmBuilder:
18
+ """SLURM-based builder for CellArrDataset."""
19
+
20
+ def __init__(
21
+ self,
22
+ output_dir: str,
23
+ log_dir: str,
24
+ temp_dir: str,
25
+ memory_gb: int = 64,
26
+ time_hours: int = 24,
27
+ cpus_per_task: int = 4,
28
+ ):
29
+ """Initialize the SLURM builder.
30
+
31
+ Args:
32
+ output_dir:
33
+ Path to final output directory.
34
+
35
+ log_dir:
36
+ Path to store SLURM logs.
37
+
38
+ temp_dir:
39
+ Path for temporary files.
40
+
41
+ memory_gb:
42
+ Memory per job in GB.
43
+
44
+ time_hours:
45
+ Time limit per job in hours.
46
+
47
+ cpus_per_task:
48
+ CPUs per task.
49
+ """
50
+ self.output_dir = Path(output_dir)
51
+ self.log_dir = Path(log_dir)
52
+ self.temp_dir = Path(temp_dir)
53
+ self.memory_gb = memory_gb
54
+ self.time_hours = time_hours
55
+ self.cpus_per_task = cpus_per_task
56
+
57
+ def create_slurm_script(
58
+ self, job_name: str, python_script: str, args: Dict, dependencies: Optional[str] = None
59
+ ) -> str:
60
+ """Create a SLURM job submission script."""
61
+ script = f"""#!/bin/bash
62
+ #SBATCH --job-name={job_name}
63
+ #SBATCH --output={self.log_dir}/{job_name}_%j.out
64
+ #SBATCH --error={self.log_dir}/{job_name}_%j.err
65
+ #SBATCH --time={self.time_hours}:00:00
66
+ #SBATCH --mem={self.memory_gb}G
67
+ #SBATCH --cpus-per-task={self.cpus_per_task}
68
+ """
69
+ if dependencies:
70
+ script += f"#SBATCH --dependency={dependencies}\n"
71
+
72
+ script += f"""
73
+ source ~/.bashrc
74
+ conda activate cellarr_env # MODIFY AS NEEDED
75
+
76
+ python {python_script} '{json.dumps(args)}'
77
+ """
78
+ script_path = self.log_dir / f"{job_name}_submit.sh"
79
+ with open(script_path, "w") as f:
80
+ f.write(script)
81
+ return script_path
82
+
83
+ def create_array_script(
84
+ self, job_name: str, python_script: str, args: Dict, n_tasks: int, dependencies: Optional[str] = None
85
+ ) -> str:
86
+ """Create a SLURM array job submission script."""
87
+ script = f"""#!/bin/bash
88
+ #SBATCH --job-name={job_name}
89
+ #SBATCH --output={self.log_dir}/{job_name}_%A_%a.out
90
+ #SBATCH --error={self.log_dir}/{job_name}_%A_%a.err
91
+ #SBATCH --time={self.time_hours}:00:00
92
+ #SBATCH --mem={self.memory_gb}G
93
+ #SBATCH --cpus-per-task={self.cpus_per_task}
94
+ #SBATCH --array=0-{n_tasks-1}
95
+ """
96
+ if dependencies:
97
+ script += f"#SBATCH --dependency={dependencies}\n"
98
+
99
+ script += f"""
100
+ source ~/.bashrc
101
+ conda activate cellarr_env # MODIFY AS NEEDED
102
+
103
+ python {python_script} '{json.dumps(args)}'
104
+ """
105
+ script_path = self.log_dir / f"{job_name}_array.sh"
106
+ with open(script_path, "w") as f:
107
+ f.write(script)
108
+ return script_path
109
+
110
+ def submit_job(self, script_path: str) -> str:
111
+ """Submit a SLURM job and return job ID."""
112
+ result = subprocess.run(["sbatch", script_path], capture_output=True, text=True)
113
+ return result.stdout.strip().split()[-1]
114
+
115
+ def submit_gene_annotation_job(self, files: List[str], gene_options: Dict) -> str:
116
+ """Submit gene annotation processing job."""
117
+ args = {
118
+ "files": files,
119
+ "output_dir": str(self.output_dir),
120
+ "gene_options": gene_options,
121
+ "temp_dir": str(self.temp_dir / "gene_annotation"),
122
+ }
123
+
124
+ script_path = self.create_slurm_script(
125
+ job_name="cellarr_gene_annot", python_script="process_gene_annotation.py", args=args
126
+ )
127
+ return self.submit_job(script_path)
128
+
129
+ def submit_sample_metadata_job(self, files: List[str], sample_options: Dict, dependency: str) -> str:
130
+ """Submit sample metadata processing job."""
131
+ args = {
132
+ "files": files,
133
+ "output_dir": str(self.output_dir),
134
+ "sample_options": sample_options,
135
+ "temp_dir": str(self.temp_dir / "sample_metadata"),
136
+ }
137
+
138
+ script_path = self.create_slurm_script(
139
+ job_name="cellarr_sample_meta",
140
+ python_script="process_sample_metadata.py",
141
+ args=args,
142
+ # dependencies=f"afterok:{dependency}",
143
+ )
144
+ return self.submit_job(script_path)
145
+
146
+ def submit_cell_metadata_job(self, files: List[str], cell_options: Dict, dependency: str) -> str:
147
+ """Submit cell metadata processing job."""
148
+ args = {
149
+ "files": files,
150
+ "output_dir": str(self.output_dir),
151
+ "cell_options": cell_options,
152
+ "temp_dir": str(self.temp_dir / "cell_metadata"),
153
+ }
154
+
155
+ script_path = self.create_slurm_script(
156
+ job_name="cellarr_cell_meta",
157
+ python_script="process_cell_metadata.py",
158
+ args=args,
159
+ # dependencies=f"afterok:{dependency}",
160
+ )
161
+ return self.submit_job(script_path)
162
+
163
+ def submit_matrix_processing(self, files: List[str], matrix_options: Dict, dependency: str) -> Tuple[str, str]:
164
+ """Submit matrix processing as SLURM array job."""
165
+ # Calculate cell offsets for each file
166
+ offset = 0
167
+ file_infos = []
168
+ for file in files:
169
+ file_infos.append({"file": file, "offset": offset})
170
+ adata = anndata.read_h5ad(file, backed="r")
171
+ offset += adata.n_obs
172
+
173
+ # Create matrix TileDB array
174
+ matrix_uri = str(self.output_dir / "assays" / matrix_options["matrix_name"])
175
+ create_tiledb_array(
176
+ matrix_uri,
177
+ matrix_attr_name=matrix_options.get("matrix_attr_name", "data"),
178
+ matrix_dim_dtype=np.dtype(matrix_options.get("dtype", "float32")),
179
+ )
180
+
181
+ # Prepare array job arguments
182
+ array_args = {
183
+ "output_dir": str(self.output_dir),
184
+ "temp_dir": str(self.temp_dir / f"matrix_{matrix_options['matrix_name']}"),
185
+ "matrix_options": matrix_options,
186
+ "gene_annotation_file": str(self.temp_dir / "gene_annotation/gene_set.json"),
187
+ "file_infos": file_infos,
188
+ }
189
+
190
+ # Submit array job
191
+ array_script = self.create_array_script(
192
+ job_name=f"matrix_{matrix_options['matrix_name']}",
193
+ python_script="process_matrix.py",
194
+ args=array_args,
195
+ n_tasks=len(files),
196
+ dependencies=f"afterok:{dependency}",
197
+ )
198
+ array_job_id = self.submit_job(array_script)
199
+
200
+ # Submit finalization job
201
+ final_args = {
202
+ "output_dir": str(self.output_dir),
203
+ "temp_dir": str(self.temp_dir / f"matrix_{matrix_options['matrix_name']}"),
204
+ "matrix_options": matrix_options,
205
+ "file_infos": file_infos,
206
+ }
207
+
208
+ final_script = self.create_slurm_script(
209
+ job_name=f"finalize_matrix_{matrix_options['matrix_name']}",
210
+ python_script="finalize_matrix.py",
211
+ args=final_args,
212
+ dependencies=f"afterok:{array_job_id}",
213
+ )
214
+ final_job_id = self.submit_job(final_script)
215
+
216
+ return array_job_id, final_job_id
217
+
218
+ def submit_final_assembly(self, matrix_names: List[str], dependencies: List[str]) -> str:
219
+ """Submit final assembly job."""
220
+ args = {"input_dir": str(self.output_dir), "output_dir": str(self.output_dir), "matrix_names": matrix_names}
221
+
222
+ script_path = self.create_slurm_script(
223
+ job_name="cellarr_final_assembly",
224
+ python_script="final_assembly.py",
225
+ args=args,
226
+ dependencies=f"afterok:{','.join(dependencies)}",
227
+ )
228
+ return self.submit_job(script_path)
229
+
230
+
231
+ def main():
232
+ parser = argparse.ArgumentParser(description="Build CellArrDataset using SLURM steps")
233
+ parser.add_argument("--input-manifest", required=True, help="Path to JSON manifest file")
234
+ parser.add_argument("--output-dir", required=True, help="Base output directory")
235
+ parser.add_argument("--memory-per-job", type=int, default=64, help="Memory in GB per job")
236
+ parser.add_argument("--time-per-job", type=int, default=24, help="Time in hours per job")
237
+ parser.add_argument("--cpus-per-task", type=int, default=4, help="CPUs per task")
238
+ args = parser.parse_args()
239
+
240
+ # Create directories
241
+ base_dir = Path(args.output_dir)
242
+ log_dir = base_dir / "logs"
243
+ temp_dir = base_dir / "temp"
244
+ final_dir = base_dir / "final"
245
+
246
+ for d in [log_dir, temp_dir, final_dir]:
247
+ d.mkdir(parents=True, exist_ok=True)
248
+
249
+ # Read manifest
250
+ with open(args.input_manifest) as f:
251
+ manifest = json.load(f)
252
+
253
+ # Initialize builder
254
+ builder = SlurmBuilder(
255
+ output_dir=str(final_dir),
256
+ log_dir=str(log_dir),
257
+ temp_dir=str(temp_dir),
258
+ memory_gb=args.memory_per_job,
259
+ time_hours=args.time_per_job,
260
+ cpus_per_task=args.cpus_per_task,
261
+ )
262
+
263
+ # Submit jobs
264
+ gene_job_id = builder.submit_gene_annotation_job(manifest["files"], manifest.get("gene_options", {}))
265
+
266
+ sample_job_id = builder.submit_sample_metadata_job(
267
+ manifest["files"], manifest.get("sample_options", {}), gene_job_id
268
+ )
269
+
270
+ cell_job_id = builder.submit_cell_metadata_job(manifest["files"], manifest.get("cell_options", {}), sample_job_id)
271
+
272
+ # Process matrices
273
+ matrix_options = manifest.get("matrix_options", [{"matrix_name": "counts"}])
274
+ if not isinstance(matrix_options, list):
275
+ matrix_options = [matrix_options]
276
+
277
+ matrix_job_ids = []
278
+ for matrix_opt in matrix_options:
279
+ _, final_id = builder.submit_matrix_processing(manifest["files"], matrix_opt, f"{cell_job_id},{gene_job_id}")
280
+ matrix_job_ids.append(final_id)
281
+
282
+ # Submit final assembly
283
+ builder.submit_final_assembly([opt["matrix_name"] for opt in matrix_options], matrix_job_ids)
284
+
285
+
286
+ if __name__ == "__main__":
287
+ main()
@@ -0,0 +1,185 @@
1
+ import argparse
2
+ import json
3
+ import os
4
+ import subprocess
5
+ from pathlib import Path
6
+ from typing import Dict, Optional
7
+
8
+
9
+ __author__ = "Jayaram Kancherla"
10
+ __copyright__ = "Jayaram Kancherla"
11
+ __license__ = "MIT"
12
+
13
+
14
+ def create_slurm_script(
15
+ job_name: str,
16
+ log_dir: str,
17
+ python_script: str,
18
+ args: Dict,
19
+ memory_gb: int = 64,
20
+ time_hours: int = 24,
21
+ cpus_per_task: int = 4,
22
+ dependencies: Optional[str] = None,
23
+ ) -> str:
24
+ """Create SLURM job submission script."""
25
+ script = f"""#!/bin/bash
26
+ #SBATCH --job-name={job_name}
27
+ #SBATCH --output={log_dir}/{job_name}_%j.out
28
+ #SBATCH --error={log_dir}/{job_name}_%j.err
29
+ #SBATCH --time={time_hours}:00:00
30
+ #SBATCH --mem={memory_gb}G
31
+ #SBATCH --cpus-per-task={cpus_per_task}
32
+ """
33
+ if dependencies:
34
+ script += f"#SBATCH --dependency={dependencies}\n"
35
+
36
+ script += """
37
+ source ~/.bashrc
38
+ conda activate cellarr_env # Modify as needed for your environment
39
+
40
+ python {python_script} '{json.dumps(args)}'
41
+ """
42
+ script_path = os.path.join(log_dir, f"{job_name}_submit.sh")
43
+ with open(script_path, "w") as f:
44
+ f.write(script)
45
+ return script_path
46
+
47
+
48
+ def submit_slurm_job(script_path: str, dependency: Optional[str] = None) -> str:
49
+ """Submit SLURM job and return job ID."""
50
+ cmd = ["sbatch"]
51
+ if dependency:
52
+ cmd.extend(["--dependency", dependency])
53
+ cmd.append(script_path)
54
+
55
+ result = subprocess.run(cmd, capture_output=True, text=True)
56
+ job_id = result.stdout.strip().split()[-1]
57
+ return job_id
58
+
59
+
60
+ def main():
61
+ parser = argparse.ArgumentParser(description="Build CellArrDataset using SLURM steps")
62
+ parser.add_argument("--input-manifest", required=True, help="Path to JSON manifest file")
63
+ parser.add_argument("--output-dir", required=True, help="Base output directory")
64
+ parser.add_argument("--memory-per-job", type=int, default=64, help="Memory in GB per job")
65
+ parser.add_argument("--time-per-job", type=int, default=24, help="Time in hours per job")
66
+ parser.add_argument("--cpus-per-task", type=int, default=4, help="CPUs per task")
67
+ args = parser.parse_args()
68
+
69
+ # Create output directories
70
+ base_dir = Path(args.output_dir)
71
+ log_dir = base_dir / "logs"
72
+ temp_dir = base_dir / "temp"
73
+ final_dir = base_dir / "final"
74
+
75
+ for d in [log_dir, temp_dir, final_dir]:
76
+ d.mkdir(parents=True, exist_ok=True)
77
+
78
+ # Read manifest file
79
+ with open(args.input_manifest) as f:
80
+ manifest = json.load(f)
81
+
82
+ # Step 1: Gene Annotation Job
83
+ gene_args = {
84
+ "files": manifest["files"],
85
+ "output_dir": str(final_dir),
86
+ "gene_options": manifest.get("gene_options", {}),
87
+ "temp_dir": str(temp_dir / "gene_annotation"),
88
+ }
89
+
90
+ gene_script = create_slurm_script(
91
+ job_name="cellarr_gene_annot",
92
+ log_dir=str(log_dir),
93
+ python_script="process_gene_annotation.py",
94
+ args=gene_args,
95
+ memory_gb=args.memory_per_job,
96
+ cpus_per_task=args.cpus_per_task,
97
+ )
98
+ gene_job_id = submit_slurm_job(gene_script)
99
+
100
+ # Step 2: Sample Metadata Job
101
+ sample_args = {
102
+ "files": manifest["files"],
103
+ "output_dir": str(final_dir),
104
+ "sample_options": manifest.get("sample_options", {}),
105
+ "temp_dir": str(temp_dir / "sample_metadata"),
106
+ }
107
+
108
+ sample_script = create_slurm_script(
109
+ job_name="cellarr_sample_meta",
110
+ log_dir=str(log_dir),
111
+ python_script="process_sample_metadata.py",
112
+ args=sample_args,
113
+ memory_gb=args.memory_per_job,
114
+ cpus_per_task=args.cpus_per_task,
115
+ # dependencies=f"afterok:{gene_job_id}",
116
+ )
117
+ sample_job_id = submit_slurm_job(sample_script)
118
+
119
+ # Step 3: Cell Metadata Job
120
+ cell_args = {
121
+ "files": manifest["files"],
122
+ "output_dir": str(final_dir),
123
+ "cell_options": manifest.get("cell_options", {}),
124
+ "temp_dir": str(temp_dir / "cell_metadata"),
125
+ }
126
+
127
+ cell_script = create_slurm_script(
128
+ job_name="cellarr_cell_meta",
129
+ log_dir=str(log_dir),
130
+ python_script="process_cell_metadata.py",
131
+ args=cell_args,
132
+ memory_gb=args.memory_per_job,
133
+ cpus_per_task=args.cpus_per_task,
134
+ # dependencies=f"afterok:{sample_job_id}",
135
+ )
136
+ cell_job_id = submit_slurm_job(cell_script)
137
+
138
+ # Step 4: Matrix Jobs (one per matrix type)
139
+ matrix_job_ids = []
140
+ matrix_options = manifest.get("matrix_options", [{"matrix_name": "counts"}])
141
+ if not isinstance(matrix_options, list):
142
+ matrix_options = [matrix_options]
143
+
144
+ for matrix_opt in matrix_options:
145
+ matrix_args = {
146
+ "files": manifest["files"],
147
+ "output_dir": str(final_dir),
148
+ "matrix_options": matrix_opt,
149
+ "gene_annotation_file": str(temp_dir / "gene_annotation/gene_set.json"),
150
+ "temp_dir": str(temp_dir / f"matrix_{matrix_opt['matrix_name']}"),
151
+ }
152
+
153
+ matrix_script = create_slurm_script(
154
+ job_name=f"cellarr_matrix_{matrix_opt['matrix_name']}",
155
+ log_dir=str(log_dir),
156
+ python_script="process_matrix.py",
157
+ args=matrix_args,
158
+ memory_gb=args.memory_per_job * 2, # More memory for matrix processing
159
+ cpus_per_task=args.cpus_per_task,
160
+ dependencies=f"afterok:{cell_job_id},{gene_job_id}",
161
+ )
162
+ job_id = submit_slurm_job(matrix_script)
163
+ matrix_job_ids.append(job_id)
164
+
165
+ # Step 5: Final Assembly Job
166
+ final_args = {
167
+ "input_dir": str(final_dir),
168
+ "output_dir": str(final_dir),
169
+ "matrix_names": [opt["matrix_name"] for opt in matrix_options],
170
+ }
171
+
172
+ final_script = create_slurm_script(
173
+ job_name="cellarr_final_assembly",
174
+ log_dir=str(log_dir),
175
+ python_script="final_assembly.py",
176
+ args=final_args,
177
+ memory_gb=args.memory_per_job,
178
+ cpus_per_task=args.cpus_per_task,
179
+ dependencies=f"afterok:{','.join(matrix_job_ids)}",
180
+ )
181
+ submit_slurm_job(final_script)
182
+
183
+
184
+ if __name__ == "__main__":
185
+ main()
@@ -0,0 +1,20 @@
1
+ import json
2
+
3
+ from cellarr import CellArrDataset
4
+
5
+ __author__ = "Jayaram Kancherla"
6
+ __copyright__ = "Jayaram Kancherla"
7
+ __license__ = "MIT"
8
+
9
+
10
+ def final_assembly(args_json: str):
11
+ args = json.loads(args_json)
12
+
13
+ # Perform any final optimizations or validations
14
+ dataset = CellArrDataset(dataset_path=args["output_dir"], assay_uri=args["matrix_names"])
15
+
16
+ # Save final metadata
17
+ metadata = {"shape": dataset.shape, "matrices": args["matrix_names"]}
18
+
19
+ with open(f"{args['output_dir']}/metadata.json", "w") as f:
20
+ json.dump(metadata, f)
@@ -0,0 +1,45 @@
1
+ import json
2
+ import sys
3
+ from pathlib import Path
4
+
5
+ from cellarr.buildutils_tiledb_array import optimize_tiledb_array
6
+
7
+ __author__ = "Jayaram Kancherla"
8
+ __copyright__ = "Jayaram Kancherla"
9
+ __license__ = "MIT"
10
+
11
+
12
+ def finalize_matrix(args_json: str):
13
+ """Finalize the matrix after all array jobs complete."""
14
+ args = json.loads(args_json)
15
+
16
+ # Verify all tasks completed
17
+ completed_dir = Path(args["temp_dir"]) / "completed"
18
+ expected_tasks = len(args["file_infos"])
19
+ completed_tasks = len(list(completed_dir.glob("task_*.json")))
20
+
21
+ if completed_tasks != expected_tasks:
22
+ raise RuntimeError(f"Expected {expected_tasks} tasks but only {completed_tasks} completed")
23
+
24
+ # Optimize the TileDB array
25
+ matrix_uri = f"{args['output_dir']}/assays/{args['matrix_options']['matrix_name']}"
26
+ optimize_tiledb_array(matrix_uri)
27
+
28
+ # Save completion metadata
29
+ with open(f"{args['temp_dir']}/matrix_metadata.json", "w") as f:
30
+ json.dump(
31
+ {
32
+ "matrix_name": args["matrix_options"]["matrix_name"],
33
+ "files_processed": len(args["file_infos"]),
34
+ "uri": matrix_uri,
35
+ },
36
+ f,
37
+ )
38
+
39
+
40
+ if __name__ == "__main__":
41
+ if len(sys.argv) != 2:
42
+ print("Usage: python finalize_matrix.py '<json_args>'")
43
+ sys.exit(1)
44
+
45
+ finalize_matrix(sys.argv[1])
@@ -0,0 +1,68 @@
1
+ import json
2
+ import sys
3
+
4
+ import pandas as pd
5
+
6
+ from cellarr import utils_anndata as uad
7
+ from cellarr.buildutils_tiledb_frame import (
8
+ create_tiledb_frame_from_dataframe,
9
+ )
10
+
11
+ __author__ = "Jayaram Kancherla"
12
+ __copyright__ = "Jayaram Kancherla"
13
+ __license__ = "MIT"
14
+
15
+
16
+ def process_cell_metadata(args_json: str):
17
+ """Process and create cell metadata store.
18
+
19
+ Creates cell metadata including:
20
+ - Sample mapping
21
+ - Cell indices within samples
22
+ - Original cell annotations from input files
23
+ """
24
+ args = json.loads(args_json)
25
+
26
+ # Extract cell metadata with specific column subset if provided
27
+ cell_meta_columns = args.get("cell_options", {}).get("column_types", {})
28
+ files_cache = uad.extract_anndata_info(
29
+ args["files"], obs_subset_columns=list(cell_meta_columns.keys()) if cell_meta_columns else None
30
+ )
31
+
32
+ # Get cell counts
33
+ cell_counts = uad.scan_for_cellcounts(files_cache)
34
+
35
+ # Create sample mapping for each cell
36
+ sample_per_cell = []
37
+ cell_index_in_sample = []
38
+ sample_names = [f"sample_{idx+1}" for idx in range(len(args["files"]))]
39
+
40
+ for idx, count in enumerate(cell_counts):
41
+ sample_per_cell.extend([sample_names[idx]] * count)
42
+ cell_index_in_sample.extend(range(count))
43
+
44
+ # Create base cell metadata
45
+ cell_metadata = pd.DataFrame(
46
+ {"cellarr_sample": sample_per_cell, "cellarr_cell_index_in_sample": cell_index_in_sample}
47
+ )
48
+
49
+ # Add original cell annotations from input files
50
+ if cell_meta_columns:
51
+ original_meta = uad.scan_for_cellmetadata(files_cache)
52
+ if not original_meta.empty:
53
+ # Ensure index alignment
54
+ original_meta.reset_index(drop=True, inplace=True)
55
+ cell_metadata = pd.concat([cell_metadata, original_meta], axis=1)
56
+
57
+ # Create TileDB store
58
+ create_tiledb_frame_from_dataframe(
59
+ f"{args['output_dir']}/cell_metadata", cell_metadata, column_types=cell_meta_columns
60
+ )
61
+
62
+
63
+ if __name__ == "__main__":
64
+ if len(sys.argv) != 2:
65
+ print("Usage: python process_cell_metadata.py '<json_args>'")
66
+ sys.exit(1)
67
+
68
+ process_cell_metadata(sys.argv[1])
@@ -0,0 +1,33 @@
1
+ import json
2
+
3
+ import pandas as pd
4
+
5
+ from cellarr import utils_anndata as uad
6
+ from cellarr.buildutils_tiledb_frame import create_tiledb_frame_from_dataframe
7
+
8
+ __author__ = "Jayaram Kancherla"
9
+ __copyright__ = "Jayaram Kancherla"
10
+ __license__ = "MIT"
11
+
12
+
13
+ def process_gene_annotation(args_json: str):
14
+ args = json.loads(args_json)
15
+
16
+ # Extract gene information from all files
17
+ files_cache = uad.extract_anndata_info(
18
+ args["files"], var_feature_column=args["gene_options"].get("feature_column", "index")
19
+ )
20
+
21
+ # Scan for features
22
+ gene_set = uad.scan_for_features(files_cache)
23
+ gene_set = sorted(gene_set)
24
+
25
+ # Create gene annotation dataframe
26
+ gene_annotation = pd.DataFrame({"cellarr_gene_index": gene_set}, index=gene_set)
27
+
28
+ # Save gene set for later use
29
+ with open(f"{args['temp_dir']}/gene_set.json", "w") as f:
30
+ json.dump(gene_set, f)
31
+
32
+ # Create TileDB store
33
+ create_tiledb_frame_from_dataframe(f"{args['output_dir']}/gene_annotation", gene_annotation)
@@ -0,0 +1,50 @@
1
+ import json
2
+ import os
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ from cellarr import utils_anndata as uad
7
+ from cellarr.buildutils_tiledb_array import write_csr_matrix_to_tiledb
8
+
9
+ __author__ = "Jayaram Kancherla"
10
+ __copyright__ = "Jayaram Kancherla"
11
+ __license__ = "MIT"
12
+
13
+
14
+ def process_matrix_file(args_json: str):
15
+ """Process a single file for matrix creation."""
16
+ args = json.loads(args_json)
17
+
18
+ # Get SLURM array task ID
19
+ task_id = int(os.environ.get("SLURM_ARRAY_TASK_ID", 0))
20
+
21
+ # Load gene set mapping
22
+ with open(args["gene_annotation_file"]) as f:
23
+ gene_set = json.load(f)
24
+ gene_map = {gene: idx for idx, gene in enumerate(gene_set)}
25
+
26
+ # Get file and offset for this task
27
+ file_info = args["file_infos"][task_id]
28
+ input_file = file_info["file"]
29
+ row_offset = file_info["offset"]
30
+
31
+ # Process the file
32
+ matrix = uad.remap_anndata(input_file, gene_map, layer_matrix_name=args["matrix_options"]["matrix_name"])
33
+
34
+ # Write to TileDB
35
+ matrix_uri = f"{args['output_dir']}/assays/{args['matrix_options']['matrix_name']}"
36
+ write_csr_matrix_to_tiledb(matrix_uri, matrix[args["matrix_options"]["matrix_name"]], row_offset=row_offset)
37
+
38
+ # Save task completion marker
39
+ task_marker = Path(args["temp_dir"]) / "completed" / f"task_{task_id}.json"
40
+ task_marker.parent.mkdir(exist_ok=True)
41
+ with open(task_marker, "w") as f:
42
+ json.dump({"file": input_file, "cells_processed": matrix[args["matrix_options"]["matrix_name"]].shape[0]}, f)
43
+
44
+
45
+ if __name__ == "__main__":
46
+ if len(sys.argv) != 2:
47
+ print("Usage: python process_matrix.py '<json_args>'")
48
+ sys.exit(1)
49
+
50
+ process_matrix_file(sys.argv[1])
@@ -0,0 +1,40 @@
1
+ import json
2
+
3
+ import numpy as np
4
+
5
+ from cellarr import utils_anndata as uad
6
+ from cellarr.buildutils_tiledb_array import create_tiledb_array, write_csr_matrix_to_tiledb
7
+
8
+ __author__ = "Jayaram Kancherla"
9
+ __copyright__ = "Jayaram Kancherla"
10
+ __license__ = "MIT"
11
+
12
+
13
+ def process_matrix(args_json: str):
14
+ args = json.loads(args_json)
15
+
16
+ # Load gene set
17
+ with open(args["gene_annotation_file"]) as f:
18
+ gene_set = json.load(f)
19
+
20
+ # Create gene set mapping
21
+ gene_map = {gene: idx for idx, gene in enumerate(gene_set)}
22
+
23
+ # Create TileDB array
24
+ create_tiledb_array(
25
+ f"{args['output_dir']}/assays/{args['matrix_options']['matrix_name']}",
26
+ matrix_dim_dtype=np.dtype(args["matrix_options"].get("dtype", "float32")),
27
+ )
28
+
29
+ # Process each file
30
+ offset = 0
31
+ for file in args["files"]:
32
+ matrix = uad.remap_anndata(file, gene_map, layer_matrix_name=args["matrix_options"]["matrix_name"])
33
+
34
+ write_csr_matrix_to_tiledb(
35
+ f"{args['output_dir']}/assays/{args['matrix_options']['matrix_name']}",
36
+ matrix[args["matrix_options"]["matrix_name"]],
37
+ row_offset=offset,
38
+ )
39
+
40
+ offset += matrix[args["matrix_options"]["matrix_name"]].shape[0]
@@ -0,0 +1,76 @@
1
+ import json
2
+ import sys
3
+
4
+ import pandas as pd
5
+
6
+ from cellarr import utils_anndata as uad
7
+ from cellarr.buildutils_tiledb_frame import create_tiledb_frame_from_dataframe
8
+
9
+ __author__ = "Jayaram Kancherla"
10
+ __copyright__ = "Jayaram Kancherla"
11
+ __license__ = "MIT"
12
+
13
+
14
+ def process_sample_metadata(args_json: str):
15
+ """Process and create sample metadata store.
16
+
17
+ Creates sample metadata including:
18
+ - Basic sample information
19
+ - Cell counts per sample
20
+ - Original gene sets
21
+ - Sample index information
22
+ """
23
+ args = json.loads(args_json)
24
+
25
+ # Extract information from files
26
+ files_cache = uad.extract_anndata_info(args["files"], var_feature_column=args.get("feature_column", "index"))
27
+
28
+ # Get cell counts for each sample
29
+ cell_counts = uad.scan_for_cellcounts(files_cache)
30
+
31
+ # Create basic sample metadata
32
+ sample_names = [f"sample_{idx+1}" for idx in range(len(args["files"]))]
33
+ sample_metadata = pd.DataFrame(
34
+ {"cellarr_sample": sample_names, "cellarr_cell_counts": cell_counts, "cellarr_filename": args["files"]}
35
+ )
36
+
37
+ # Add sample indices for efficient slicing
38
+ counter = sample_metadata["cellarr_cell_counts"].shift(1)
39
+ counter.iloc[0] = 0
40
+ sample_metadata["cellarr_sample_start_index"] = counter.cumsum().astype(int)
41
+
42
+ # Calculate end indices
43
+ ends = sample_metadata["cellarr_sample_start_index"].shift(-1)
44
+ ends.iloc[-1] = int(sample_metadata["cellarr_cell_counts"].sum())
45
+ ends = ends - 1
46
+ sample_metadata["cellarr_sample_end_index"] = ends.astype(int)
47
+
48
+ # Add original gene sets for each sample
49
+ gene_sets = uad.scan_for_features(files_cache, unique=False)
50
+ sample_metadata["cellarr_original_gene_set"] = [",".join(genes) for genes in gene_sets]
51
+
52
+ # Add any custom metadata if provided in options
53
+ custom_metadata = args.get("sample_options", {}).get("metadata", {})
54
+ for sample, metadata in custom_metadata.items():
55
+ for key, value in metadata.items():
56
+ if key not in sample_metadata.columns:
57
+ sample_metadata[key] = None
58
+ sample_idx = sample_metadata.index[sample_metadata["cellarr_sample"] == sample][0]
59
+ sample_metadata.at[sample_idx, key] = value
60
+
61
+ # Create TileDB store
62
+ create_tiledb_frame_from_dataframe(f"{args['output_dir']}/sample_metadata", sample_metadata)
63
+
64
+ # Save metadata for subsequent steps
65
+ metadata = {"num_samples": len(sample_names), "total_cells": int(sample_metadata["cellarr_cell_counts"].sum())}
66
+
67
+ with open(f"{args['temp_dir']}/sample_metadata.json", "w") as f:
68
+ json.dump(metadata, f)
69
+
70
+
71
+ if __name__ == "__main__":
72
+ if len(sys.argv) != 2:
73
+ print("Usage: python process_sample_metadata.py '<json_args>'")
74
+ sys.exit(1)
75
+
76
+ process_sample_metadata(sys.argv[1])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cellarr
3
- Version: 0.3.2
3
+ Version: 0.4.1.dev1
4
4
  Summary: TileDB-based array storage for genomics data collections.
5
5
  Home-page: https://github.com/BiocPy/cellarr
6
6
  Author: Jayaram Kancherla
@@ -10,7 +10,7 @@ Project-URL: Documentation, https://github.com/BiocPy/cellarr
10
10
  Platform: any
11
11
  Classifier: Development Status :: 4 - Beta
12
12
  Classifier: Programming Language :: Python
13
- Requires-Python: >=3.8
13
+ Requires-Python: >=3.9
14
14
  Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
15
15
  License-File: LICENSE.txt
16
16
  Requires-Dist: importlib-metadata; python_version < "3.8"
@@ -45,6 +45,17 @@ src/cellarr.egg-info/dependency_links.txt
45
45
  src/cellarr.egg-info/not-zip-safe
46
46
  src/cellarr.egg-info/requires.txt
47
47
  src/cellarr.egg-info/top_level.txt
48
+ src/cellarr/slurm/README.md
49
+ src/cellarr/slurm/__init__.py
50
+ src/cellarr/slurm/build_cellarr_steps.py
51
+ src/cellarr/slurm/build_cellarr_steps_bkp.py
52
+ src/cellarr/slurm/final_assembly.py
53
+ src/cellarr/slurm/finalize_matrix.py
54
+ src/cellarr/slurm/process_cell_metadata.py
55
+ src/cellarr/slurm/process_gene_annotation.py
56
+ src/cellarr/slurm/process_matrix.py
57
+ src/cellarr/slurm/process_matrix_all.py
58
+ src/cellarr/slurm/process_sample_metadata.py
48
59
  tests/conftest.py
49
60
  tests/test_anndata_utils.py
50
61
  tests/test_build.py
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes