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.

167 lines (152 loc) 8.77 kB
import * as sce from "./SingleCellExperiment.js"; import * as markers from "./markers.js"; import * as ass from "./assays.js"; import * as rd from "./reducedDimensions.js"; import * as internal from "./abstract/dump.js"; import { AlabasterGlobalsInterface } from "./interfaces.js"; import * as jsp from "jaspagate"; import * as wa from "wasmarrays.js"; function saveOtherDataFrameColumns(y, handle, name) { if (y instanceof wa.Float64WasmArray) { let chandle = handle.createDataSet(name, "Float64", [ y.length ], { data: y }); try { chandle.writeAttribute("type", "String", [], ["number"]); } finally { chandle.close(); } return true; } else if (y instanceof wa.Int32WasmArray) { let chandle = handle.createDataSet(name, "Int32", [ y.length ], { data: y }); try { chandle.writeAttribute("type", "String", [], ["integer"]); } finally { chandle.close(); } return true; } else if (y instanceof wa.Uint8WasmArray) { let chandle = handle.createDataSet(name, "Uint8", [ y.length ], { data: y }); try { chandle.writeAttribute("type", "String", [], ["boolean"]); } finally { chandle.close(); } return true; } return false; } /** * Save the analysis results into a SingleCellExperiment using the [**takane**](https://github.com/ArtifactDB/takane) format. * This uses a language-agnostic format mostly based on HDF5 and JSON, which can be read into a variety of frameworks like R and Python. * The aim is to facilitate downstream analysis procedures that are not supported by **bakana** itself; * for example, a bench scientist can do a first pass with **kana** before passing the results to a computational collaborator for deeper inspection. * * @param {object} state - Existing analysis state containing results, after one or more runs of {@linkcode runAnalysis}. * @param {string} name - Name of the SingleCellExperiment. * This may also contain forward slashes, in which case it is treated as a local path. * If a local filesystem is present and `directory` is supplied, `name` defines the subdirectory within `directory` in which the SingleCellExperiment is to be saved. * @param {object} [options={}] - Optional parameters. * @param {boolean} [options.reportOneIndex=false] - Whether to report 1-based indices, for use in 1-based languages like R. * Currently, this only refers to the column indices of the custom selections reported in the SingleCellExperiment's metadata. * @param {boolean} [options.storeModalityColumnData=false] - Whether to store modality-specific per-cell statistics (e.g., QC metrics, size factors) in the column data of the associated alternative Experiment. * This can yield a cleaner SingleCellExperiment as statistics are assigned to the relevant modalities. * That said, the default is still `false` as many applications (including **bakana** itself, via the {@linkcode AbstractArtifactdbDataset} and friends) will not parse the column data of alternative Experiments. * In such cases, all modality-specific metrics are stored in the main experiment's column data with a modality-specific name, e.g., `kana::ADT::quality_control::sums`. * @param {?string} [options.directory=null] - Directory in which to save the components of the SingleCellExperiment. * If `null` or if no local file system exists, files are stored in memory as Uint8Arrays. * * @return {?Object} If `directory` is supplied and a local filesystem exists, the components are saved to disk and `null` is returned. * Otherwise, if `directory = null` or a local filesystem does not exist, an object is returned where each key is a local path to a component file and each value is a Uint8Array with the file contents. */ export async function saveSingleCellExperiment(state, name, { reportOneIndex = false, storeModalityColumnData = false, directory = null } = {}) { let my_sce = await sce.formatSingleCellExperiment(state, { reportOneIndex, storeModalityColumnData }); if (!(internal.fsexists())) { directory = null; } let files = {}; let globals = new AlabasterGlobalsInterface(directory, files); jsp.saveObjectRegistry.push([ ass.MockSparseMatrix, ass.saveSparseMatrix ]); jsp.saveObjectRegistry.push([ ass.MockNormalizedMatrix, ass.saveNormalizedMatrix ]); jsp.saveObjectRegistry.push([ rd.MockReducedDimensionMatrix, rd.saveReducedDimensionMatrix]); try { await jsp.saveObject(my_sce, name, globals, { DataFrame_saveOther: saveOtherDataFrameColumns }); } finally { jsp.saveObjectRegistry.pop(); jsp.saveObjectRegistry.pop(); jsp.saveObjectRegistry.pop(); } if (directory === null) { return files; } else { return null; } } /** * Save the per-gene analysis results as data frames in the [**takane**](https://github.com/ArtifactDB/takane) format. * This includes the marker tables for the clusters and custom selections, as well as the variance modelling statistics from the feature selection step. * Each data frames has the same order of rows as the SingleCellExperiment saved by {@linkcode saveSingleCellExperiment}; * for easier downstream use, we set the row names of each data frame to row names of the SingleCellExperiment * (or if no row names are available, we set each data frame's row names to the first column of the `rowData`). * * @param {object} state - Existing analysis state containing results, after one or more runs of {@linkcode runAnalysis}. * @param {?string} path - Local path in which to save all results. * If a local filesystem is present and `directory` is supplied, `path` defines the subdirectory within `directory` in which the SingleCellExperiment is to be saved. * If `null`, results are directly saved to `directory` itself. * @param {object} [options={}] - Optional parameters. * @param {boolean} [options.includeMarkerDetection=true] - Whether to save the marker detection results. * @param {boolean} [options.includeCustomSelections=true] - Whether to save the custom selection results. * @param {boolean} [options.includeFeatureSelection=true] - Whether to save the feature selection results. * @param {?string} [options.directory=null] - Directory in which to save the data frames. * If `null` or if no local file system exists, files are stored in memory as Uint8Arrays. * * @return {?Object} If `directory` is supplied and a local filesystem exists, the data frame components are saved to disk and `null` is returned. * Otherwise, if `directory = null` or a local filesystem does not exist, an object is returned where each key is a local path to a component file and each value is a Uint8Array with the file contents. */ export async function saveGenewiseResults(state, path, { includeMarkerDetection = true, includeCustomSelections = true, includeFeatureSelection = true, directory = null } = {}) { let modalities = {}; let anno = state.inputs.fetchFeatureAnnotations(); for (const [k, v] of Object.entries(anno)) { let rn = v.rowNames(); if (rn === null && v.numberOfColumns() > 0) { rn = v.column(0); } modalities[k] = rn; } if (!(internal.fsexists())) { directory = null; } let files = {}; let globals = new AlabasterGlobalsInterface(directory, files); if (includeMarkerDetection) { let dir = "marker_detection"; if (path !== null) { dir = jsp.joinPath(path, dir); } let all = markers.formatMarkerDetectionResults(state, modalities); for (const [k, v] of Object.entries(all)) { await jsp.saveObject(v, jsp.joinPath(dir, k), globals, { DataFrame_saveOther: saveOtherDataFrameColumns }); } } if (includeCustomSelections) { let dir = "custom_selections"; if (path !== null) { dir = jsp.joinPath(path, dir); } let all = markers.formatCustomSelectionResults(state, modalities); for (const [k, v] of Object.entries(all)) { await jsp.saveObject(v, jsp.joinPath(dir, k), globals, { DataFrame_saveOther: saveOtherDataFrameColumns }); } } if (state.feature_selection.valid() && includeFeatureSelection) { let dir = "feature_selection"; if (path !== null) { dir = jsp.joinPath(path, dir); } let df = markers.formatFeatureSelectionResults(state, modalities.RNA); await jsp.saveObject(df, dir, globals, { DataFrame_saveOther: saveOtherDataFrameColumns }); } if (directory === null) { return files; } else { return null; } }