cellarr 0.4.0__tar.gz → 0.4.1.dev2__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.4.0 → cellarr-0.4.1.dev2}/.pre-commit-config.yaml +2 -2
  2. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/PKG-INFO +43 -2
  3. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/README.md +42 -1
  4. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/setup.cfg +2 -0
  5. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/src/cellarr/buildutils_tiledb_frame.py +1 -1
  6. cellarr-0.4.1.dev2/src/cellarr/slurm/README.md +65 -0
  7. cellarr-0.4.1.dev2/src/cellarr/slurm/__init__.py +0 -0
  8. cellarr-0.4.1.dev2/src/cellarr/slurm/build_cellarr_steps.py +319 -0
  9. cellarr-0.4.1.dev2/src/cellarr/slurm/final_assembly.py +29 -0
  10. cellarr-0.4.1.dev2/src/cellarr/slurm/finalize_matrix.py +45 -0
  11. cellarr-0.4.1.dev2/src/cellarr/slurm/process_cell_metadata.py +68 -0
  12. cellarr-0.4.1.dev2/src/cellarr/slurm/process_gene_annotation.py +45 -0
  13. cellarr-0.4.1.dev2/src/cellarr/slurm/process_matrix.py +58 -0
  14. cellarr-0.4.1.dev2/src/cellarr/slurm/process_matrix_all.py +40 -0
  15. cellarr-0.4.1.dev2/src/cellarr/slurm/process_sample_metadata.py +78 -0
  16. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/src/cellarr.egg-info/PKG-INFO +43 -2
  17. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/src/cellarr.egg-info/SOURCES.txt +11 -0
  18. cellarr-0.4.1.dev2/src/cellarr.egg-info/entry_points.txt +2 -0
  19. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/.coveragerc +0 -0
  20. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/.github/workflows/pypi-publish.yml +0 -0
  21. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/.github/workflows/pypi-test.yml +0 -0
  22. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/.gitignore +0 -0
  23. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/.readthedocs.yml +0 -0
  24. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/AUTHORS.md +0 -0
  25. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/CHANGELOG.md +0 -0
  26. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/CONTRIBUTING.md +0 -0
  27. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/LICENSE.txt +0 -0
  28. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/assets/cellarr.jpg +0 -0
  29. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/assets/cellarr.png +0 -0
  30. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/assets/cellarr.svg +0 -0
  31. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/docs/Makefile +0 -0
  32. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/docs/_static/.gitignore +0 -0
  33. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/docs/authors.md +0 -0
  34. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/docs/changelog.md +0 -0
  35. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/docs/conf.py +0 -0
  36. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/docs/contributing.md +0 -0
  37. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/docs/index.md +0 -0
  38. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/docs/license.md +0 -0
  39. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/docs/notes.md +0 -0
  40. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/docs/readme.md +0 -0
  41. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/docs/requirements.txt +0 -0
  42. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/docs/tutorial.md +0 -0
  43. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/pyproject.toml +0 -0
  44. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/setup.py +0 -0
  45. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/src/cellarr/CellArrDataset.py +0 -0
  46. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/src/cellarr/CellArrDatasetSlice.py +0 -0
  47. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/src/cellarr/__init__.py +0 -0
  48. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/src/cellarr/autoencoder.py +0 -0
  49. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/src/cellarr/build_cellarrdataset.py +0 -0
  50. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/src/cellarr/build_options.py +0 -0
  51. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/src/cellarr/buildutils_tiledb_array.py +0 -0
  52. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/src/cellarr/dataloader.py +0 -0
  53. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/src/cellarr/queryutils_tiledb_frame.py +0 -0
  54. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/src/cellarr/utils_anndata.py +0 -0
  55. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/src/cellarr.egg-info/dependency_links.txt +0 -0
  56. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/src/cellarr.egg-info/not-zip-safe +0 -0
  57. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/src/cellarr.egg-info/requires.txt +0 -0
  58. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/src/cellarr.egg-info/top_level.txt +0 -0
  59. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/tests/conftest.py +0 -0
  60. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/tests/data/adata.h5ad +0 -0
  61. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/tests/test_anndata_utils.py +0 -0
  62. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/tests/test_build.py +0 -0
  63. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/tests/test_custom_config.py +0 -0
  64. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/tests/test_dataloader.py +0 -0
  65. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/tests/test_query.py +0 -0
  66. {cellarr-0.4.0 → cellarr-0.4.1.dev2}/tox.ini +0 -0
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cellarr
3
- Version: 0.4.0
3
+ Version: 0.4.1.dev2
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
@@ -45,7 +45,7 @@ datasets but can be generalized to store any 2-dimensional experimental data.
45
45
 
46
46
  To get started, install the package from [PyPI](https://pypi.org/project/cellarr/)
47
47
 
48
- ```bash
48
+ ```sh
49
49
  pip install cellarr
50
50
 
51
51
  ## to include optional dependencies
@@ -156,6 +156,47 @@ print(dataset)
156
156
 
157
157
  Check out the [documentation](https://biocpy.github.io/cellarr/tutorial.html) for more details.
158
158
 
159
+ ### Building on HPC environments with `slurm`
160
+
161
+ To simplify building TileDB files on HPC environments that use `slurm`, there are a few steps you need to follow.
162
+
163
+ - Step 1: Construct a manifest file
164
+ A minimal manifest file (json) must contain the following fields
165
+ - `"files"`: A list of file path to the input `h5ad` objects.
166
+ - `"python_env"`: A set of commands to activate the Python environment containing this package and its dependencies.
167
+
168
+ Here’s an example of the manifest file:
169
+ ```json
170
+ manifest = {
171
+ "files": your/list/of/files,
172
+ "python_env": """
173
+ ml Miniforge3
174
+ conda activate cellarr
175
+
176
+ python --version
177
+ which python
178
+ """,
179
+ "matrix_options": [
180
+ {
181
+ "matrix_name": "non_zero_cells",
182
+ "dtype": "uint32"
183
+ },
184
+ {
185
+ "matrix_name": "pseudo_bulk_log_normed",
186
+ "dtype": "float32"
187
+ }
188
+ ],
189
+ }
190
+ ```
191
+ For more options, check out the [README](./src/cellarr/slurm/README.md).
192
+
193
+ - Step 2: Submit the job
194
+ Once your manifest file is ready, you can submit the necessary jobs using the `cellarr_build` CLI. Run the following command:
195
+
196
+ ```sh
197
+ cellarr_build --input-manifest your/path/to/test_manifest.json --output-dir your/path/to/output --memory-per-job 8 --cpus-per-task 2
198
+ ```
199
+
159
200
  ### Query a `CellArrDataset`
160
201
 
161
202
  Users have the option to reuse the `dataset` object returned when building the dataset or by creating a `CellArrDataset` object by initializing it to the path where the files were created.
@@ -12,7 +12,7 @@ datasets but can be generalized to store any 2-dimensional experimental data.
12
12
 
13
13
  To get started, install the package from [PyPI](https://pypi.org/project/cellarr/)
14
14
 
15
- ```bash
15
+ ```sh
16
16
  pip install cellarr
17
17
 
18
18
  ## to include optional dependencies
@@ -123,6 +123,47 @@ print(dataset)
123
123
 
124
124
  Check out the [documentation](https://biocpy.github.io/cellarr/tutorial.html) for more details.
125
125
 
126
+ ### Building on HPC environments with `slurm`
127
+
128
+ To simplify building TileDB files on HPC environments that use `slurm`, there are a few steps you need to follow.
129
+
130
+ - Step 1: Construct a manifest file
131
+ A minimal manifest file (json) must contain the following fields
132
+ - `"files"`: A list of file path to the input `h5ad` objects.
133
+ - `"python_env"`: A set of commands to activate the Python environment containing this package and its dependencies.
134
+
135
+ Here’s an example of the manifest file:
136
+ ```json
137
+ manifest = {
138
+ "files": your/list/of/files,
139
+ "python_env": """
140
+ ml Miniforge3
141
+ conda activate cellarr
142
+
143
+ python --version
144
+ which python
145
+ """,
146
+ "matrix_options": [
147
+ {
148
+ "matrix_name": "non_zero_cells",
149
+ "dtype": "uint32"
150
+ },
151
+ {
152
+ "matrix_name": "pseudo_bulk_log_normed",
153
+ "dtype": "float32"
154
+ }
155
+ ],
156
+ }
157
+ ```
158
+ For more options, check out the [README](./src/cellarr/slurm/README.md).
159
+
160
+ - Step 2: Submit the job
161
+ Once your manifest file is ready, you can submit the necessary jobs using the `cellarr_build` CLI. Run the following command:
162
+
163
+ ```sh
164
+ cellarr_build --input-manifest your/path/to/test_manifest.json --output-dir your/path/to/output --memory-per-job 8 --cpus-per-task 2
165
+ ```
166
+
126
167
  ### Query a `CellArrDataset`
127
168
 
128
169
  Users have the option to reuse the `dataset` object returned when building the dataset or by creating a `CellArrDataset` object by initializing it to the path where the files were created.
@@ -49,6 +49,8 @@ testing =
49
49
  pytorch-lightning
50
50
 
51
51
  [options.entry_points]
52
+ console_scripts =
53
+ cellarr_build = cellarr.slurm.build_cellarr_steps:main
52
54
 
53
55
  [tool:pytest]
54
56
  addopts =
@@ -76,7 +76,7 @@ def create_tiledb_frame_from_column_names(
76
76
  )
77
77
 
78
78
 
79
- def create_tiledb_frame_from_dataframe(tiledb_uri_path: str, frame: List[str], column_types=dict):
79
+ def create_tiledb_frame_from_dataframe(tiledb_uri_path: str, frame: List[str], column_types: dict = None):
80
80
  """Create a TileDB file with the provided attributes to persistent storage.
81
81
 
82
82
  This will materialize the array directory and all
@@ -0,0 +1,65 @@
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
+ },
41
+ "python_env": """
42
+ . /system/gredit/clientos/etc/profile
43
+
44
+ ml Miniforge3
45
+ conda activate biocpy_miniforge
46
+
47
+ ~/.conda/envs/biocpy_miniforge/bin/python --version
48
+ which python
49
+ python --version
50
+ """,
51
+ }
52
+ ```
53
+
54
+
55
+ Run
56
+
57
+ ```sh
58
+
59
+ python build_cellarr_steps.py \
60
+ --input-manifest manifest.json \
61
+ --output-dir /path/to/output \
62
+ --memory-per-job 64 \
63
+ --cpus-per-task 4
64
+
65
+ ```
File without changes
@@ -0,0 +1,319 @@
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 numpy as np
8
+
9
+ from cellarr.buildutils_tiledb_array import create_tiledb_array
10
+
11
+ __author__ = "Jayaram Kancherla"
12
+ __copyright__ = "Jayaram Kancherla"
13
+ __license__ = "MIT"
14
+
15
+
16
+ class SlurmBuilder:
17
+ """SLURM-based builder for CellArrDataset."""
18
+
19
+ def __init__(
20
+ self,
21
+ output_dir: str,
22
+ log_dir: str,
23
+ temp_dir: str,
24
+ memory_gb: int = 64,
25
+ time_hours: int = 24,
26
+ cpus_per_task: int = 4,
27
+ ):
28
+ """Initialize the SLURM builder.
29
+
30
+ Args:
31
+ output_dir:
32
+ Path to final output directory.
33
+
34
+ log_dir:
35
+ Path to store SLURM logs.
36
+
37
+ temp_dir:
38
+ Path for temporary files.
39
+
40
+ memory_gb:
41
+ Memory per job in GB.
42
+
43
+ time_hours:
44
+ Time limit per job in hours.
45
+
46
+ cpus_per_task:
47
+ CPUs per task.
48
+ """
49
+ self.output_dir = Path(output_dir)
50
+ self.log_dir = Path(log_dir)
51
+ self.temp_dir = Path(temp_dir)
52
+ self.memory_gb = memory_gb
53
+ self.time_hours = time_hours
54
+ self.cpus_per_task = cpus_per_task
55
+
56
+ def create_slurm_script(
57
+ self,
58
+ job_name: str,
59
+ python_script: str,
60
+ args: Dict,
61
+ dependencies: Optional[str] = None,
62
+ python_env: str = "",
63
+ ) -> str:
64
+ """Create a SLURM job submission script."""
65
+ script = f"""#!/bin/bash
66
+ #SBATCH --job-name={job_name}
67
+ #SBATCH --output={self.log_dir}/{job_name}_%j.out
68
+ #SBATCH --error={self.log_dir}/{job_name}_%j.err
69
+ #SBATCH --time={self.time_hours}:00:00
70
+ #SBATCH --mem={self.memory_gb}G
71
+ #SBATCH --cpus-per-task={self.cpus_per_task}
72
+ """
73
+ if dependencies:
74
+ script += f"#SBATCH --dependency={dependencies}\n"
75
+
76
+ script += f"""
77
+ {python_env}
78
+
79
+ python {python_script} '{json.dumps(args)}'
80
+ """
81
+ script_path = self.log_dir / f"{job_name}_submit.sh"
82
+ with open(script_path, "w") as f:
83
+ f.write(script)
84
+ return script_path
85
+
86
+ def create_array_script(
87
+ self,
88
+ job_name: str,
89
+ python_script: str,
90
+ args: Dict,
91
+ n_tasks: int,
92
+ dependencies: Optional[str] = None,
93
+ python_env: str = "",
94
+ ) -> str:
95
+ """Create a SLURM array job submission script."""
96
+ script = f"""#!/bin/bash
97
+ #SBATCH --job-name={job_name}
98
+ #SBATCH --output={self.log_dir}/{job_name}_%A_%a.out
99
+ #SBATCH --error={self.log_dir}/{job_name}_%A_%a.err
100
+ #SBATCH --time={self.time_hours}:00:00
101
+ #SBATCH --mem={self.memory_gb}G
102
+ #SBATCH --cpus-per-task={self.cpus_per_task}
103
+ #SBATCH --array=0-{n_tasks-1}
104
+ """
105
+ if dependencies:
106
+ script += f"#SBATCH --dependency={dependencies}\n"
107
+
108
+ script += f"""
109
+ {python_env}
110
+
111
+ python {python_script} '{json.dumps(args)}'
112
+ """
113
+ script_path = self.log_dir / f"{job_name}_array.sh"
114
+ with open(script_path, "w") as f:
115
+ f.write(script)
116
+ return script_path
117
+
118
+ def submit_job(self, script_path: str) -> str:
119
+ """Submit a SLURM job and return job ID."""
120
+ result = subprocess.run(["sbatch", script_path], capture_output=True, text=True)
121
+ return result.stdout.strip().split()[-1]
122
+
123
+ def submit_gene_annotation_job(self, files: List[str], gene_options: Dict, python_env: str) -> str:
124
+ """Submit gene annotation processing job."""
125
+ args = {
126
+ "files": files,
127
+ "output_dir": str(self.output_dir),
128
+ "gene_options": gene_options,
129
+ "temp_dir": str(self.temp_dir / "gene_annotation"),
130
+ }
131
+
132
+ parent_dir = str(Path(__file__).parent)
133
+
134
+ script_path = self.create_slurm_script(
135
+ job_name="cellarr_gene_annot",
136
+ python_script=f"{parent_dir}/process_gene_annotation.py",
137
+ args=args,
138
+ python_env=python_env,
139
+ )
140
+ return self.submit_job(script_path)
141
+
142
+ def submit_sample_metadata_job(
143
+ self, files: List[str], sample_options: Dict, dependency: str, python_env: str
144
+ ) -> str:
145
+ """Submit sample metadata processing job."""
146
+ args = {
147
+ "files": files,
148
+ "output_dir": str(self.output_dir),
149
+ "sample_options": sample_options,
150
+ "temp_dir": str(self.temp_dir / "sample_metadata"),
151
+ }
152
+
153
+ parent_dir = str(Path(__file__).parent)
154
+
155
+ script_path = self.create_slurm_script(
156
+ job_name="cellarr_sample_meta",
157
+ python_script=f"{parent_dir}/process_sample_metadata.py",
158
+ args=args,
159
+ python_env=python_env,
160
+ # dependencies=f"afterok:{dependency}",
161
+ )
162
+ return self.submit_job(script_path)
163
+
164
+ def submit_cell_metadata_job(self, files: List[str], cell_options: Dict, dependency: str, python_env: str) -> str:
165
+ """Submit cell metadata processing job."""
166
+ args = {
167
+ "files": files,
168
+ "output_dir": str(self.output_dir),
169
+ "cell_options": cell_options,
170
+ "temp_dir": str(self.temp_dir / "cell_metadata"),
171
+ }
172
+
173
+ parent_dir = str(Path(__file__).parent)
174
+
175
+ script_path = self.create_slurm_script(
176
+ job_name="cellarr_cell_meta",
177
+ python_script=f"{parent_dir}/process_cell_metadata.py",
178
+ args=args,
179
+ python_env=python_env,
180
+ # dependencies=f"afterok:{dependency}",
181
+ )
182
+ return self.submit_job(script_path)
183
+
184
+ def submit_matrix_processing(
185
+ self, files: List[str], matrix_options: Dict, dependency: str, python_env: str
186
+ ) -> Tuple[str, str]:
187
+ """Submit matrix processing as SLURM array job."""
188
+
189
+ # Create matrix TileDB array
190
+ matrix_uri = str(self.output_dir / "assays" / matrix_options["matrix_name"])
191
+ create_tiledb_array(
192
+ matrix_uri,
193
+ matrix_attr_name=matrix_options.get("matrix_attr_name", "data"),
194
+ matrix_dim_dtype=np.dtype(matrix_options.get("dtype", "float32")),
195
+ )
196
+
197
+ # Prepare array job arguments
198
+ array_args = {
199
+ "output_dir": str(self.output_dir),
200
+ "temp_dir": str(self.temp_dir / f"matrix_{matrix_options['matrix_name']}"),
201
+ "matrix_options": matrix_options,
202
+ "gene_annotation_file": str(self.temp_dir / "gene_annotation/gene_set.json"),
203
+ "files": files,
204
+ }
205
+
206
+ parent_dir = str(Path(__file__).parent)
207
+
208
+ # Submit array job
209
+ array_script = self.create_array_script(
210
+ job_name=f"matrix_{matrix_options['matrix_name']}",
211
+ python_script=f"{parent_dir}/process_matrix.py",
212
+ args=array_args,
213
+ n_tasks=len(files),
214
+ dependencies=f"afterok:{dependency}",
215
+ python_env=python_env,
216
+ )
217
+ array_job_id = self.submit_job(array_script)
218
+
219
+ # Submit finalization job
220
+ final_args = {
221
+ "output_dir": str(self.output_dir),
222
+ "temp_dir": str(self.temp_dir / f"matrix_{matrix_options['matrix_name']}"),
223
+ "matrix_options": matrix_options,
224
+ "files": files,
225
+ }
226
+
227
+ final_script = self.create_slurm_script(
228
+ job_name=f"finalize_matrix_{matrix_options['matrix_name']}",
229
+ python_script=f"{parent_dir}/finalize_matrix.py",
230
+ args=final_args,
231
+ dependencies=f"afterok:{array_job_id}",
232
+ python_env=python_env,
233
+ )
234
+ final_job_id = self.submit_job(final_script)
235
+
236
+ return array_job_id, final_job_id
237
+
238
+ def submit_final_assembly(self, matrix_names: List[str], dependencies: List[str], python_env: str) -> str:
239
+ """Submit final assembly job."""
240
+ args = {"input_dir": str(self.output_dir), "output_dir": str(self.output_dir), "matrix_names": matrix_names}
241
+
242
+ parent_dir = str(Path(__file__).parent)
243
+
244
+ script_path = self.create_slurm_script(
245
+ job_name="cellarr_final_assembly",
246
+ python_script=f"{parent_dir}/final_assembly.py",
247
+ args=args,
248
+ dependencies=f"afterok:{','.join(dependencies)}",
249
+ python_env=python_env,
250
+ )
251
+ return self.submit_job(script_path)
252
+
253
+
254
+ def main():
255
+ parser = argparse.ArgumentParser(description="Build CellArrDataset using SLURM steps")
256
+ parser.add_argument("--input-manifest", required=True, help="Path to JSON manifest file")
257
+ parser.add_argument("--output-dir", required=True, help="Base output directory")
258
+ parser.add_argument("--memory-per-job", type=int, default=64, help="Memory in GB per job")
259
+ parser.add_argument("--time-per-job", type=int, default=24, help="Time in hours per job")
260
+ parser.add_argument("--cpus-per-task", type=int, default=4, help="CPUs per task")
261
+ args = parser.parse_args()
262
+
263
+ # Create directories
264
+ base_dir = Path(args.output_dir)
265
+ log_dir = base_dir / "logs"
266
+ temp_dir = base_dir / "temp"
267
+ final_dir = base_dir / "final"
268
+ assays_dir = base_dir / "final/assays"
269
+
270
+ for d in [log_dir, temp_dir, final_dir, assays_dir]:
271
+ d.mkdir(parents=True, exist_ok=True)
272
+
273
+ # Read manifest
274
+ with open(args.input_manifest) as f:
275
+ manifest = json.load(f)
276
+
277
+ # Initialize builder
278
+ builder = SlurmBuilder(
279
+ output_dir=str(final_dir),
280
+ log_dir=str(log_dir),
281
+ temp_dir=str(temp_dir),
282
+ memory_gb=args.memory_per_job,
283
+ time_hours=args.time_per_job,
284
+ cpus_per_task=args.cpus_per_task,
285
+ )
286
+
287
+ # Submit jobs
288
+ gene_job_id = builder.submit_gene_annotation_job(
289
+ manifest["files"], manifest.get("gene_options", {}), manifest["python_env"]
290
+ )
291
+
292
+ sample_job_id = builder.submit_sample_metadata_job(
293
+ manifest["files"], manifest.get("sample_options", {}), gene_job_id, manifest["python_env"]
294
+ )
295
+
296
+ cell_job_id = builder.submit_cell_metadata_job(
297
+ manifest["files"], manifest.get("cell_options", {}), sample_job_id, manifest["python_env"]
298
+ )
299
+
300
+ # Process matrices
301
+ matrix_options = manifest.get("matrix_options", [{"matrix_name": "counts"}])
302
+ if not isinstance(matrix_options, list):
303
+ matrix_options = [matrix_options]
304
+
305
+ matrix_job_ids = []
306
+ for matrix_opt in matrix_options:
307
+ _, final_id = builder.submit_matrix_processing(
308
+ manifest["files"], matrix_opt, f"{cell_job_id},{gene_job_id}", manifest["python_env"]
309
+ )
310
+ matrix_job_ids.append(final_id)
311
+
312
+ # Submit final assembly
313
+ builder.submit_final_assembly(
314
+ [opt["matrix_name"] for opt in matrix_options], matrix_job_ids, manifest["python_env"]
315
+ )
316
+
317
+
318
+ if __name__ == "__main__":
319
+ main()
@@ -0,0 +1,29 @@
1
+ import json
2
+ import sys
3
+
4
+ from cellarr import CellArrDataset
5
+
6
+ __author__ = "Jayaram Kancherla"
7
+ __copyright__ = "Jayaram Kancherla"
8
+ __license__ = "MIT"
9
+
10
+
11
+ def final_assembly(args_json: str):
12
+ args = json.loads(args_json)
13
+
14
+ # Perform any final optimizations or validations
15
+ dataset = CellArrDataset(dataset_path=args["output_dir"], assay_uri=args["matrix_names"])
16
+
17
+ # Save final metadata
18
+ metadata = {"shape": dataset.shape, "matrices": args["matrix_names"]}
19
+
20
+ with open(f"{args['output_dir']}/metadata.json", "w") as f:
21
+ json.dump(metadata, f)
22
+
23
+
24
+ if __name__ == "__main__":
25
+ if len(sys.argv) != 2:
26
+ print("Usage: python final_assembly.py '<json_args>'")
27
+ sys.exit(1)
28
+
29
+ final_assembly(sys.argv[1])
@@ -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["files"])
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["files"]),
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,45 @@
1
+ import json
2
+ import sys
3
+ from pathlib import Path
4
+
5
+ import pandas as pd
6
+
7
+ from cellarr import utils_anndata as uad
8
+ from cellarr.buildutils_tiledb_frame import create_tiledb_frame_from_dataframe
9
+
10
+ __author__ = "Jayaram Kancherla"
11
+ __copyright__ = "Jayaram Kancherla"
12
+ __license__ = "MIT"
13
+
14
+
15
+ def process_gene_annotation(args_json: str):
16
+ args = json.loads(args_json)
17
+
18
+ # Extract gene information from all files
19
+ files_cache = uad.extract_anndata_info(
20
+ args["files"], var_feature_column=args.get("gene_options", {}).get("feature_column", "index")
21
+ )
22
+
23
+ # Scan for features
24
+ gene_set = uad.scan_for_features(files_cache)
25
+ gene_set = sorted(gene_set)
26
+
27
+ # Create gene annotation dataframe
28
+ gene_annotation = pd.DataFrame({"cellarr_gene_index": gene_set}, index=gene_set)
29
+ gene_annotation.reset_index(drop=True, inplace=True)
30
+
31
+ # Create TileDB store
32
+ create_tiledb_frame_from_dataframe(f"{args['output_dir']}/gene_annotation", gene_annotation)
33
+
34
+ # Save gene set for later use
35
+ Path(args["temp_dir"]).mkdir(parents=True, exist_ok=True)
36
+ with open(f"{args['temp_dir']}/gene_set.json", "w") as f:
37
+ json.dump(gene_set, f)
38
+
39
+
40
+ if __name__ == "__main__":
41
+ if len(sys.argv) != 2:
42
+ print("Usage: python process_gene_annotation.py '<json_args>'")
43
+ sys.exit(1)
44
+
45
+ process_gene_annotation(sys.argv[1])
@@ -0,0 +1,58 @@
1
+ import json
2
+ import os
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ import tiledb
7
+
8
+ from cellarr import utils_anndata as uad
9
+ from cellarr.buildutils_tiledb_array import write_csr_matrix_to_tiledb
10
+
11
+ __author__ = "Jayaram Kancherla"
12
+ __copyright__ = "Jayaram Kancherla"
13
+ __license__ = "MIT"
14
+
15
+
16
+ def process_matrix_file(args_json: str):
17
+ """Process a single file for matrix creation."""
18
+ args = json.loads(args_json)
19
+
20
+ # Get SLURM array task ID
21
+ task_id = int(os.environ.get("SLURM_ARRAY_TASK_ID", 0))
22
+
23
+ # Load gene set mapping
24
+ with open(args["gene_annotation_file"]) as f:
25
+ gene_set = json.load(f)
26
+ gene_map = {gene: idx for idx, gene in enumerate(gene_set)}
27
+
28
+ # Get file and offset for this task
29
+ file_info = args["files"][task_id]
30
+ input_file = file_info
31
+
32
+ # get sample offset
33
+ sample_uri = tiledb.open(f"{args['output_dir']}/sample_metadata", "r")
34
+ sample_row = sample_uri.df[task_id]
35
+ row_offset = sample_row["cellarr_sample_start_index"]
36
+
37
+ # Process the file
38
+ matrix = uad.remap_anndata(input_file, gene_map, layer_matrix_name=args["matrix_options"]["matrix_name"])
39
+
40
+ # Write to TileDB
41
+ matrix_uri = f"{args['output_dir']}/assays/{args['matrix_options']['matrix_name']}"
42
+ write_csr_matrix_to_tiledb(matrix_uri, matrix[args["matrix_options"]["matrix_name"]], row_offset=row_offset)
43
+
44
+ # Save task completion marker
45
+ Path(args["temp_dir"]).mkdir(parents=True, exist_ok=True)
46
+ Path(args["temp_dir"] + "/completed").mkdir(parents=True, exist_ok=True)
47
+ task_marker = Path(args["temp_dir"]) / "completed" / f"task_{task_id}.json"
48
+ task_marker.parent.mkdir(exist_ok=True)
49
+ with open(task_marker, "w") as f:
50
+ json.dump({"file": input_file, "cells_processed": matrix[args["matrix_options"]["matrix_name"]].shape[0]}, f)
51
+
52
+
53
+ if __name__ == "__main__":
54
+ if len(sys.argv) != 2:
55
+ print("Usage: python process_matrix.py '<json_args>'")
56
+ sys.exit(1)
57
+
58
+ 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,78 @@
1
+ import json
2
+ import sys
3
+ from pathlib import Path
4
+
5
+ import pandas as pd
6
+
7
+ from cellarr import utils_anndata as uad
8
+ from cellarr.buildutils_tiledb_frame import create_tiledb_frame_from_dataframe
9
+
10
+ __author__ = "Jayaram Kancherla"
11
+ __copyright__ = "Jayaram Kancherla"
12
+ __license__ = "MIT"
13
+
14
+
15
+ def process_sample_metadata(args_json: str):
16
+ """Process and create sample metadata store.
17
+
18
+ Creates sample metadata including:
19
+ - Basic sample information
20
+ - Cell counts per sample
21
+ - Original gene sets
22
+ - Sample index information
23
+ """
24
+ args = json.loads(args_json)
25
+
26
+ # Extract information from files
27
+ files_cache = uad.extract_anndata_info(args["files"], var_feature_column=args.get("feature_column", "index"))
28
+
29
+ # Get cell counts for each sample
30
+ cell_counts = uad.scan_for_cellcounts(files_cache)
31
+
32
+ # Create basic sample metadata
33
+ sample_names = [f"sample_{idx+1}" for idx in range(len(args["files"]))]
34
+ sample_metadata = pd.DataFrame(
35
+ {"cellarr_sample": sample_names, "cellarr_cell_counts": cell_counts, "cellarr_filename": args["files"]}
36
+ )
37
+
38
+ # Add sample indices for efficient slicing
39
+ counter = sample_metadata["cellarr_cell_counts"].shift(1)
40
+ counter.iloc[0] = 0
41
+ sample_metadata["cellarr_sample_start_index"] = counter.cumsum().astype(int)
42
+
43
+ # Calculate end indices
44
+ ends = sample_metadata["cellarr_sample_start_index"].shift(-1)
45
+ ends.iloc[-1] = int(sample_metadata["cellarr_cell_counts"].sum())
46
+ ends = ends - 1
47
+ sample_metadata["cellarr_sample_end_index"] = ends.astype(int)
48
+
49
+ # Add original gene sets for each sample
50
+ gene_sets = uad.scan_for_features(files_cache, unique=False)
51
+ sample_metadata["cellarr_original_gene_set"] = [",".join(genes) for genes in gene_sets]
52
+
53
+ # Add any custom metadata if provided in options
54
+ custom_metadata = args.get("sample_options", {}).get("metadata", {})
55
+ for sample, metadata in custom_metadata.items():
56
+ for key, value in metadata.items():
57
+ if key not in sample_metadata.columns:
58
+ sample_metadata[key] = None
59
+ sample_idx = sample_metadata.index[sample_metadata["cellarr_sample"] == sample][0]
60
+ sample_metadata.at[sample_idx, key] = value
61
+
62
+ # Create TileDB store
63
+ create_tiledb_frame_from_dataframe(f"{args['output_dir']}/sample_metadata", sample_metadata)
64
+
65
+ # Save metadata for subsequent steps
66
+ metadata = {"num_samples": len(sample_names), "total_cells": int(sample_metadata["cellarr_cell_counts"].sum())}
67
+
68
+ Path(args["temp_dir"]).mkdir(parents=True, exist_ok=True)
69
+ with open(f"{args['temp_dir']}/sample_metadata.json", "w") as f:
70
+ json.dump(metadata, f)
71
+
72
+
73
+ if __name__ == "__main__":
74
+ if len(sys.argv) != 2:
75
+ print("Usage: python process_sample_metadata.py '<json_args>'")
76
+ sys.exit(1)
77
+
78
+ process_sample_metadata(sys.argv[1])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cellarr
3
- Version: 0.4.0
3
+ Version: 0.4.1.dev2
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
@@ -45,7 +45,7 @@ datasets but can be generalized to store any 2-dimensional experimental data.
45
45
 
46
46
  To get started, install the package from [PyPI](https://pypi.org/project/cellarr/)
47
47
 
48
- ```bash
48
+ ```sh
49
49
  pip install cellarr
50
50
 
51
51
  ## to include optional dependencies
@@ -156,6 +156,47 @@ print(dataset)
156
156
 
157
157
  Check out the [documentation](https://biocpy.github.io/cellarr/tutorial.html) for more details.
158
158
 
159
+ ### Building on HPC environments with `slurm`
160
+
161
+ To simplify building TileDB files on HPC environments that use `slurm`, there are a few steps you need to follow.
162
+
163
+ - Step 1: Construct a manifest file
164
+ A minimal manifest file (json) must contain the following fields
165
+ - `"files"`: A list of file path to the input `h5ad` objects.
166
+ - `"python_env"`: A set of commands to activate the Python environment containing this package and its dependencies.
167
+
168
+ Here’s an example of the manifest file:
169
+ ```json
170
+ manifest = {
171
+ "files": your/list/of/files,
172
+ "python_env": """
173
+ ml Miniforge3
174
+ conda activate cellarr
175
+
176
+ python --version
177
+ which python
178
+ """,
179
+ "matrix_options": [
180
+ {
181
+ "matrix_name": "non_zero_cells",
182
+ "dtype": "uint32"
183
+ },
184
+ {
185
+ "matrix_name": "pseudo_bulk_log_normed",
186
+ "dtype": "float32"
187
+ }
188
+ ],
189
+ }
190
+ ```
191
+ For more options, check out the [README](./src/cellarr/slurm/README.md).
192
+
193
+ - Step 2: Submit the job
194
+ Once your manifest file is ready, you can submit the necessary jobs using the `cellarr_build` CLI. Run the following command:
195
+
196
+ ```sh
197
+ cellarr_build --input-manifest your/path/to/test_manifest.json --output-dir your/path/to/output --memory-per-job 8 --cpus-per-task 2
198
+ ```
199
+
159
200
  ### Query a `CellArrDataset`
160
201
 
161
202
  Users have the option to reuse the `dataset` object returned when building the dataset or by creating a `CellArrDataset` object by initializing it to the path where the files were created.
@@ -42,9 +42,20 @@ src/cellarr/utils_anndata.py
42
42
  src/cellarr.egg-info/PKG-INFO
43
43
  src/cellarr.egg-info/SOURCES.txt
44
44
  src/cellarr.egg-info/dependency_links.txt
45
+ src/cellarr.egg-info/entry_points.txt
45
46
  src/cellarr.egg-info/not-zip-safe
46
47
  src/cellarr.egg-info/requires.txt
47
48
  src/cellarr.egg-info/top_level.txt
49
+ src/cellarr/slurm/README.md
50
+ src/cellarr/slurm/__init__.py
51
+ src/cellarr/slurm/build_cellarr_steps.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
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ cellarr_build = cellarr.slurm.build_cellarr_steps:main
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