import { SingleCellExperiment } from "bioconductor";
import { H5Group, H5DataSet } from "./h5.js";
import { readObject, readObjectFile, saveObject } from "./general.js";
import { readRangedSummarizedExperiment, saveRangedSummarizedExperiment } from "./RangedSummarizedExperiment.js";
/**
* A single-cell experiment.
* @external SingleCellExperiment
* @see {@link https://ltla.github.io/bioconductor.js/SingleCellExperiment.html}
*/
/**
* @param {string} path - Path to the takane-formatted object directory containing the {@link external:SingleCellExperiment SingleCellExperiment}.
* @param {object} metadata - Takane object metadata, typically generated by calling {@linkcode readObjectFile} on `path`.
* @param {object} globals - Object containing `fs`, an object satisfying the {@link GlobalFsInterface}; and `h5`, an object satisfying the {@link GlobalH5Interface}.
* @param {object} [options={}] - Further options.
* @param {function|boolean} [options.SingleCellExperiment_readReducedDimension=true] - How to read each dimensionality reduction result.
* If `true`, {@linkcode readObject} is used, while if `false`, the reduced dimensions will be skipped.
* If a function is provided, it should accept `ncol` (the number of columns in the SingleCellExperiment) as well as `path`, `metadata`, `globals` and `options` (as described above);
* and should return an object (possibly asynchronously) for which [`NUMBER_OF_ROWS`](https://ltla.github.io/bioconductor.js/global.html#NUMBER_OF_ROWS) is equal to `ncol`.
* @param {function|boolean} [options.SingleCellExperiment_readAlternativeExperiment=true] - How to read each alternative experiment.
* If `true`, {@linkcode readObject} is used, while if `false`, the alternative experiments will be skipped.
* If a function is provided, it should accept `ncol` (the number of columns in the SingleCellExperiment) as well as `path`, `metadata`, `globals` and `options` (as described above);
* and should return a {@link external:SummarizedExperiment SummarizedExperiment} (possibly asynchronously)
* for which [`NUMBER_OF_COLUMNS`](https://ltla.github.io/bioconductor.js/global.html#NUMBER_OF_COLUMNS) is equal to `ncol`.
*
* @return {external:SingleCellExperiment} The single-cell experiment object.
* @async
*/
export async function readSingleCellExperiment(path, metadata, globals, options = {}) {
let rse = await readRangedSummarizedExperiment(path, metadata, globals, options);
let sce = new SingleCellExperiment(
rse.assays(),
{
assayOrder: rse.assayNames(),
rowRanges: rse.rowRanges(),
rowData: rse.rowData(),
columnData: rse.columnData(),
rowNames: rse.rowNames(),
columnNames: rse.columnNames(),
metadata: rse.metadata(),
}
);
if ("main_experiment_name" in metadata.single_cell_experiment) {
sce.setMainExperimentName(metadata.single_cell_experiment.main_experiment_name, { inPlace: true });
}
let read_rd = true;
if ("SingleCellExperiment_readReducedDimension" in options) {
read_rd = options.SingleCellExperiment_readReducedDimension;
}
if (read_rd !== false) {
const rdpath = path + "/reduced_dimensions/names.json";
if (await globals.fs.exists(rdpath)) {
let names_contents = await globals.fs.get(rdpath, { asBuffer: true });
const dec = new TextDecoder;
const reddim_names = JSON.parse(dec.decode(names_contents));
for (const [i, rname] of Object.entries(reddim_names)) {
let rdpath = path + "/reduced_dimensions/" + String(i);
let rdmeta = await readObjectFile(rdpath, globals);
let currd;
if (read_rd === true) {
currd = await readObject(rdpath, rdmeta, globals, options);
} else {
currd = await read_rd(sce.numberOfColumns(), rdpath, rdmeta, globals, options);
}
sce.setReducedDimension(rname, currd, { inPlace: true });
}
}
}
let read_ae = true;
if ("SingleCellExperiment_readAlternativeExperiment" in options) {
read_ae = options.SingleCellExperiment_readAlternativeExperiment;
}
if (read_ae !== false) {
const aepath = path + "/alternative_experiments/names.json";
if (await globals.fs.exists(aepath)) {
let names_contents = await globals.fs.get(aepath, { asBuffer: true });
const dec = new TextDecoder;
const altexp_names = JSON.parse(dec.decode(names_contents));
for (const [i, aname] of Object.entries(altexp_names)) {
let aepath = path + "/alternative_experiments/" + String(i);
let aemeta = await readObjectFile(aepath, globals);
let curae;
if (read_ae === true) {
curae = await readObject(aepath, aemeta, globals, options);
} else {
curae = await read_ae(sce.numberOfColumns(), aepath, aemeta, globals, options);
}
sce.setAlternativeExperiment(aname, curae, { inPlace: true });
}
}
}
return sce;
}
/**
* @param {external:SingleCellExperiment} x - The single-cell experiment.
* @param {string} path - Path to the directory in which to save `x`.
* @param {object} globals - Object containing `fs`, an object satisfying the {@link GlobalFsInterface}; and `h5`, an object satisfying the {@link GlobalH5Interface}.
* @param {object} [options={}] - Further options.
*
* @return `x` is stored at `path`.
* @async
*/
export async function saveSingleCellExperiment(x, path, globals, options = {}) {
await saveRangedSummarizedExperiment(x, path, globals, options);
const existing = await readObjectFile(path, globals);
existing.type = "single_cell_experiment";
existing.single_cell_experiment = { "version": "1.0" };
let mexp = x.mainExperimentName();
if (mexp !== null) {
existing.single_cell_experiment.main_experiment_name = mexp;
}
await globals.fs.write(path + "/OBJECT", JSON.stringify(existing));
const reddim_names = x.reducedDimensionNames();
if (reddim_names.length > 0) {
await globals.fs.mkdir(path + "/reduced_dimensions");
await globals.fs.write(path + "/reduced_dimensions/names.json", JSON.stringify(reddim_names));
for (const [i, rname] of Object.entries(reddim_names)) {
await saveObject(x.reducedDimension(rname), path + "/reduced_dimensions/" + String(i), globals, options);
}
}
const altexp_names = x.alternativeExperimentNames();
if (altexp_names.length > 0) {
await globals.fs.mkdir(path + "/alternative_experiments");
await globals.fs.write(path + "/alternative_experiments/names.json", JSON.stringify(altexp_names));
for (const [i, aname] of Object.entries(altexp_names)) {
await saveObject(x.alternativeExperiment(aname), path + "/alternative_experiments/" + String(i), globals, options);
}
}
}