|
20 | 20 | "uns_metrics": "metrics_cellranger", |
21 | 21 | "output_compression": "gzip", |
22 | 22 | "sample_csv": "samples.csv", |
| 23 | + "output_filtered_data": False, |
23 | 24 | } |
24 | 25 | meta = {"resources_dir": "./src/utils/"} |
25 | 26 | ## VIASH END |
@@ -101,6 +102,7 @@ def gather_input_data(dir: Path): |
101 | 102 | # | +-- per_barcode.csv |
102 | 103 | # | +-- antigen_specificity_scores.csv |
103 | 104 | # +-- count |
| 105 | + # | +-- sample_filtered_feature_bc_matrix.h5 |
104 | 106 | # | +-- antibody_analysis |
105 | 107 | # | +-- crispr_analysis |
106 | 108 | # | +-- perturbation_efficiencies_by_feature.csv |
@@ -174,6 +176,26 @@ def gather_input_data(dir: Path): |
174 | 176 | file_name = found_file.name.removesuffix(".csv") |
175 | 177 | found_input.setdefault(file_name, {})[samples_dir.name] = found_file |
176 | 178 |
|
| 179 | + if par["output_filtered_data"]: |
| 180 | + for samples_dir in samples_dirs: |
| 181 | + for file_part in ( |
| 182 | + "count/sample_filtered_feature_bc_matrix.h5", |
| 183 | + "sample_filtered_feature_bc_matrix.h5", # Cell Ranger v10 |
| 184 | + ): |
| 185 | + found_file = samples_dir / file_part |
| 186 | + if found_file.exists(): |
| 187 | + found_input.setdefault("filtered_counts", {})[samples_dir.name] = ( |
| 188 | + found_file |
| 189 | + ) |
| 190 | + break |
| 191 | + else: |
| 192 | + raise ValueError( |
| 193 | + f"Expected a filtered count matrix under {samples_dir}, " |
| 194 | + "but none was found. Make sure the input directory is a " |
| 195 | + "valid cellranger multi output that contains per-sample " |
| 196 | + "filtered feature-barcode matrices." |
| 197 | + ) |
| 198 | + |
177 | 199 | return found_input |
178 | 200 |
|
179 | 201 |
|
@@ -219,22 +241,21 @@ def process_feature_reference( |
219 | 241 | return mudatas |
220 | 242 |
|
221 | 243 |
|
222 | | -def process_counts(counts_folder: Path, multiplexing_info, metrics_files): |
223 | | - counts_matrix_file = counts_folder / "raw_feature_bc_matrix.h5" |
224 | | - logger.info("Reading %s.", counts_matrix_file) |
225 | | - adata = scanpy.read_10x_h5(counts_matrix_file, gex_only=False) |
| 244 | +def _modality_name_factory(library_type): |
| 245 | + return ("".join(library_type.replace("-", "_").split())).lower() |
226 | 246 |
|
227 | | - # set the gene ids as var_names |
228 | | - logger.info("Renaming var columns") |
229 | | - adata.var = adata.var.rename_axis("gene_symbol").reset_index().set_index("gene_ids") |
230 | 247 |
|
231 | | - # generate output |
232 | | - logger.info("Convert to mudata") |
| 248 | +def _rename_var_to_gene_ids(adata: anndata.AnnData): |
| 249 | + # set the gene ids as var_names (unique Ensembl IDs); gene symbols, which |
| 250 | + # scanpy.read_10x_h5 uses as var_names by default, are not unique |
| 251 | + adata.var = adata.var.rename_axis("gene_symbol").reset_index().set_index("gene_ids") |
233 | 252 |
|
234 | | - def modality_name_factory(library_type): |
235 | | - return ("".join(library_type.replace("-", "_").split())).lower() |
236 | 253 |
|
237 | | - feature_types = defaultdict(modality_name_factory, FEATURE_TYPES_NAMES) |
| 254 | +def _aggregated_counts_to_per_sample_mudatas( |
| 255 | + adata: anndata.AnnData, multiplexing_info, metrics_files |
| 256 | +): |
| 257 | + logger.info("Convert to mudata") |
| 258 | + feature_types = defaultdict(_modality_name_factory, FEATURE_TYPES_NAMES) |
238 | 259 | mudata_all_samples = mudata.MuData(adata, feature_types_names=feature_types) |
239 | 260 | if multiplexing_info: |
240 | 261 | # Get the mapping between the barcode and the sample ID from one of the metrics files |
@@ -267,6 +288,29 @@ def modality_name_factory(library_type): |
267 | 288 | return {"run": mudata_all_samples} |
268 | 289 |
|
269 | 290 |
|
| 291 | +def process_counts_filtered(filtered_counts: dict[str, Path]): |
| 292 | + # Unlike the raw matrix, per-sample filtered matrices are already |
| 293 | + # demultiplexed by cellranger, so each h5 maps 1:1 to an output mudata. |
| 294 | + feature_types = defaultdict(_modality_name_factory, FEATURE_TYPES_NAMES) |
| 295 | + mudatas = {} |
| 296 | + for sample_name, filtered_h5 in filtered_counts.items(): |
| 297 | + logger.info("Reading %s.", filtered_h5) |
| 298 | + adata = scanpy.read_10x_h5(filtered_h5, gex_only=False) |
| 299 | + _rename_var_to_gene_ids(adata) |
| 300 | + mudatas[sample_name] = mudata.MuData(adata, feature_types_names=feature_types) |
| 301 | + return mudatas |
| 302 | + |
| 303 | + |
| 304 | +def process_counts(counts_folder: Path, multiplexing_info, metrics_files): |
| 305 | + counts_matrix_file = counts_folder / "raw_feature_bc_matrix.h5" |
| 306 | + logger.info("Reading %s.", counts_matrix_file) |
| 307 | + adata = scanpy.read_10x_h5(counts_matrix_file, gex_only=False) |
| 308 | + _rename_var_to_gene_ids(adata) |
| 309 | + return _aggregated_counts_to_per_sample_mudatas( |
| 310 | + adata, multiplexing_info, metrics_files |
| 311 | + ) |
| 312 | + |
| 313 | + |
270 | 314 | def split_samples(mudata_obj, multiplexing_analysis_folder, barcode_sample_mapping): |
271 | 315 | result = {} |
272 | 316 | cells_per_tag_file = multiplexing_analysis_folder / "cells_per_tag.json" |
@@ -410,14 +454,19 @@ def get_modalities(input_data): |
410 | 454 | ), |
411 | 455 | "antigen_analysis": process_antigen_analysis, |
412 | 456 | } |
413 | | - mudata_per_sample = process_counts( |
414 | | - input_data["count"], |
415 | | - input_data["multiplexing_analysis"], |
416 | | - input_data["metrics_summary"], |
417 | | - ) |
| 457 | + if input_data.get("filtered_counts"): |
| 458 | + mudata_per_sample = process_counts_filtered( |
| 459 | + input_data["filtered_counts"], |
| 460 | + ) |
| 461 | + else: |
| 462 | + mudata_per_sample = process_counts( |
| 463 | + input_data["count"], |
| 464 | + input_data["multiplexing_analysis"], |
| 465 | + input_data["metrics_summary"], |
| 466 | + ) |
418 | 467 | for modality_name, modality_data_path in input_data.items(): |
419 | 468 | if ( |
420 | | - modality_name in ("count", "multiplexing_analysis") |
| 469 | + modality_name in ("count", "multiplexing_analysis", "filtered_counts") |
421 | 470 | or not modality_data_path |
422 | 471 | ): |
423 | 472 | continue |
|
0 commit comments