UNPKG

bakana

Version:

Backend for kana's single-cell analyses. This supports single or multiple samples, execution in Node.js or the browser, in-memory caching of results for iterative analyses, and serialization to/from file for redistribution.

159 lines (137 loc) 5.36 kB
import * as scran from "scran.js"; import * as bioc from "bioconductor"; function create_solo_default_object(value, modality) { let output = {}; output[modality] = value; return output; } export function reportFeatures(rawFeatures, typeField) { if (rawFeatures.hasColumn(typeField)) { let by_type = bioc.presplitFactor(rawFeatures.column(typeField)); let copy = rawFeatures.removeColumn(typeField); return bioc.SPLIT(copy, by_type); } else { return create_solo_default_object(rawFeatures, ""); } } function is_subset_noop(indices, full_length) { if (indices.length != full_length) { return false; } for (var i = 0; i < full_length; i++) { if (i !== indices[i]) { return false; } } return true; } function renameByModality(input, indexToNames, featureTypeMapping) { let output = {}; for (let [k, v] of Object.entries(featureTypeMapping)) { if (v !== null) { if (typeof v !== "string") { if (v < indexToNames.length) { v = indexToNames[v]; } else { continue; } } if (v in input) { output[k] = input[v]; } } } return output; } function splitByModality(features, typeField, featureTypeMapping) { let by_type = bioc.presplitFactor(features.column(typeField)); if (featureTypeMapping === null) { return by_type; } return renameByModality(by_type, [], featureTypeMapping); // positional indices for feature types don't make sense when splitting by (string) levels of a type column. } function findUnnamedDefault(featureTypeMapping, featureTypeDefault) { let found = null; let multiple = false; for (const [k, v] of Object.entries(featureTypeMapping)) { if (v !== null) { if (found !== null) { multiple = true; } found = k; } } if (found === null || multiple) { return featureTypeDefault; } else { return found; } } export function extractSplitPrimaryIds(features, typeField, featureTypeMapping, featureTypeDefault, primary) { if (typeField !== null && features.hasColumn(typeField)) { let by_type = splitByModality(features, typeField, featureTypeMapping); for (const [k, v] of Object.entries(by_type)) { let col = extractPrimaryIdColumn(k, features, primary); by_type[k] = bioc.SLICE(col, v); } return by_type; } // Seeing if any featureTypeMapping is set to the unnamed string. let new_default = findUnnamedDefault(featureTypeMapping, featureTypeDefault); let output = {}; output[new_default] = extractPrimaryIdColumn(new_default, features, primary); return output; } export function splitScranMatrixAndFeatures(loaded, rawFeatures, typeField, featureTypeMapping, featureTypeDefault) { let output = { matrix: new scran.MultiMatrix }; try { output.matrix.add("", loaded); let current_features = bioc.CLONE(rawFeatures, { deepCopy: false }); // because we're deleting a column. if (typeField !== null && current_features.hasColumn(typeField)) { let by_type = splitByModality(current_features, typeField, featureTypeMapping); let type_keys = Object.keys(by_type); let skip_subset = is_subset_noop(type_keys[0], loaded.numberOfRows()); if (type_keys.length > 1 || !skip_subset) { let replacement = new scran.MultiMatrix({ store: scran.splitRows(loaded, by_type) }); scran.free(output.matrix); output.matrix = replacement; } else { output.matrix.rename("", type_keys[0]); } delete current_features[typeField]; output.features = bioc.SPLIT(current_features, by_type); } else { let new_default = featureTypeDefault; if (featureTypeMapping !== null) { new_default = findUnnamedDefault(featureTypeMapping, featureTypeDefault); } output.matrix.rename("", new_default); output.features = create_solo_default_object(current_features, new_default); } } catch (e) { scran.free(output.matrix); throw e; } return output; } function extractPrimaryIdColumn(modality, modality_features, primary) { if (!(modality in primary)) { throw new Error("modality '" + modality + "' has no primary key identifier"); } let id = primary[modality]; if ((typeof id == "string" && modality_features.hasColumn(id)) || (typeof id == "number" && id < modality_features.numberOfColumns())) { return modality_features.column(id); } return modality_features.rowNames(); } export function extractPrimaryIds(features, primary) { let output = {}; for (const [k, v] of Object.entries(features)) { output[k] = extractPrimaryIdColumn(k, v, primary); } return output; } export function extractRemappedPrimaryIds(features, indexToNames, featureTypeMapping, primary) { let renamed = renameByModality(features, indexToNames, featureTypeMapping); return extractPrimaryIds(renamed, primary); }