labelCells.js

  1. import * as gc from "./gc.js";
  2. import * as wasm from "./wasm.js";
  3. import * as utils from "./utils.js";
  4. import { ScranMatrix } from "./ScranMatrix.js";
  5. import * as wa from "wasmarrays.js";
  6. import * as init from "./initializeSparseMatrixFromArrays.js";
  7. /**************************************************
  8. **************************************************/
  9. /**
  10. * Wrapper around a labelled reference dataset on the Wasm heap, typically produced by {@linkcode loadLabelCellsReferenceFromBuffers}.
  11. * @hideconstructor
  12. */
  13. class LoadedLabelCellsReference {
  14. #id;
  15. #reference;
  16. constructor(id, raw) {
  17. this.#id = id;
  18. this.#reference = raw;
  19. return;
  20. }
  21. // Internal use only, not documented.
  22. get reference() {
  23. return this.#reference;
  24. }
  25. /**
  26. * @return {number} Number of samples in this dataset.
  27. */
  28. numberOfSamples() {
  29. return this.#reference.num_samples();
  30. }
  31. /**
  32. * @return {number} Number of features in this dataset.
  33. */
  34. numberOfFeatures() {
  35. return this.#reference.num_features();
  36. }
  37. /**
  38. * @return {number} Number of labels in this dataset.
  39. */
  40. numberOfLabels() {
  41. return this.#reference.num_labels();
  42. }
  43. /**
  44. * @return Frees the memory allocated on the Wasm heap for this object.
  45. * This invalidates this object and all references to it.
  46. */
  47. free() {
  48. if (this.#reference !== null) {
  49. gc.release(this.#id);
  50. this.#reference = null;
  51. }
  52. }
  53. }
  54. /**
  55. * Load a reference dataset for annotation in {@linkecode labelCells}.
  56. * The reference should be represented by several files, the contents of which are described in the [**singlepp_loaders** documentation](https://github.com/SingleR-inc/singlepp_loaders).
  57. *
  58. * @param {Uint8Array|Uint8WasmArray} ranks - Buffer containing the Gzipped CSV file containing a matrix of ranks.
  59. * Each line corresponds to a sample and contains a comma-separated vector of ranks across all features.
  60. * All lines should contain the same number of entries.
  61. * This is effectively a row-major matrix where rows are samples and columns are features.
  62. * (Advanced users may note that this is transposed in C++.)
  63. * @param {Uint8Array|Uint8WasmArray} markers - Buffer containing the Gzipped GMT file containing the markers for each pairwise comparison between labels.
  64. * For `markers`, the GMT format is a tab-separated file with possibly variable numbers of fields for each line.
  65. * Each line corresponds to a pairwise comparison between labels, defined by the first two fields.
  66. * The remaining fields should contain indices of marker features (referring to columns of `matrix`) that are upregulated in the first label when compared to the second.
  67. * Markers should be sorted in order of decreasing strength.
  68. * @param {Uint8Array|Uint8WasmArray} labels - Buffer containing the Gzipped text file containing the label for each sample.
  69. * Each line should contain an integer representing a particular label, from `[0, N)` where `N` is the number of unique labels.
  70. * The number of lines should be equal to the number of rows in `matrix`.
  71. * The actual names of the labels are usually held elsewhere.
  72. *
  73. * @return {LoadedLabelCellsReference} Object containing the reference dataset.
  74. */
  75. export function loadLabelCellsReferenceFromBuffers(ranks, markers, labels) {
  76. var output;
  77. var matbuf;
  78. var markbuf;
  79. var labbuf;
  80. try {
  81. matbuf = utils.wasmifyArray(ranks, "Uint8WasmArray");
  82. markbuf = utils.wasmifyArray(markers, "Uint8WasmArray");
  83. labbuf = utils.wasmifyArray(labels, "Uint8WasmArray");
  84. output = gc.call(
  85. module => module.load_singlepp_reference(labbuf.offset, labbuf.length, markbuf.offset, markbuf.length, matbuf.offset, matbuf.length),
  86. LoadedLabelCellsReference
  87. );
  88. } catch (e) {
  89. utils.free(output);
  90. throw e;
  91. } finally {
  92. utils.free(matbuf);
  93. utils.free(markbuf);
  94. utils.free(labbuf);
  95. }
  96. return output;
  97. }
  98. /**************************************************
  99. **************************************************/
  100. /**
  101. * Wrapper around a built labelled reference dataset on the Wasm heap, typically produced by {@linkcode trainLabelCellsReference}.
  102. * @hideconstructor
  103. */
  104. class TrainedLabelCellsReference {
  105. #id;
  106. #reference;
  107. constructor(id, raw, expected_features) {
  108. this.#id = id;
  109. this.#reference = raw;
  110. this.expectedNumberOfFeatures = expected_features;
  111. return;
  112. }
  113. // internal use only.
  114. get reference() {
  115. return this.#reference;
  116. }
  117. /**
  118. * @return {number} Number of shared features between the test and reference datasets.
  119. */
  120. numberOfFeatures() {
  121. return this.#reference.num_features();
  122. }
  123. /**
  124. * @return {number} Number of labels in this dataset.
  125. */
  126. numberOfLabels() {
  127. return this.#reference.num_labels();
  128. }
  129. /**
  130. * @return Frees the memory allocated on the Wasm heap for this object.
  131. * This invalidates this object and all references to it.
  132. */
  133. free() {
  134. if (this.#reference !== null) {
  135. gc.release(this.#id);
  136. this.#reference = null;
  137. }
  138. }
  139. }
  140. /**
  141. * @ignore
  142. */
  143. export function intersectFeatures(testFeatures, referenceFeatures) { // exported only for testing purposes.
  144. let registry = new Map;
  145. for (var i = 0; i < testFeatures.length; i++) {
  146. let id = testFeatures[i];
  147. if (id !== null && !registry.has(id)) { // first hit gets the preference.
  148. registry.set(id, i);
  149. }
  150. }
  151. let tkeep = [], rkeep = [];
  152. for (var i = 0; i < referenceFeatures.length; i++) {
  153. let id = referenceFeatures[i];
  154. if (id == null) {
  155. continue;
  156. }
  157. if (!Array.isArray(id)) {
  158. if (registry.has(id)) {
  159. tkeep.push(registry.get(id));
  160. registry.delete(id); // deleting to avoid a future match to the same ID, as the intersection must be unique in its first/second hits.
  161. rkeep.push(i);
  162. }
  163. } else { // otherwise, it's an array of multiple synonymous gene names.
  164. for (const xid of id) {
  165. if (registry.has(xid)) {
  166. tkeep.push(registry.get(xid));
  167. registry.delete(xid);
  168. rkeep.push(i);
  169. break;
  170. }
  171. }
  172. }
  173. }
  174. return { "test": tkeep, "reference": rkeep };
  175. }
  176. /**
  177. * Train a reference dataset for annotation in {@linkcode labelCells}.
  178. * The build process involves harmonizing the identities of the features available in the test dataset compared to the reference.
  179. * Specifically, a feature must be present in both datasets in order to be retained.
  180. * Of those features in the intersection, only the `top` markers from each pairwise comparison are ultimately used for classification.
  181. *
  182. * Needless to say, `testFeatures` should match up to the rows of the {@linkplain ScranMatrix} that is actually used for annotation in {@linkcode labelCells}.
  183. *
  184. * @param {Array} testFeatures - An array of feature identifiers (usually strings) of length equal to the number of rows in the test matrix.
  185. * Each entry should contain the identifier for the corresponding row of the test matrix.
  186. * Any `null` entries are considered to be incomparable.
  187. * @param {LoadedLabelCellsReference} loadedReference - A reference dataset, typically loaded with {@linkcode loadLabelCellsReferenceFromBuffers}.
  188. * @param {Array} referenceFeatures - An array of feature identifiers (usually strings) of length equal to the number of features in `reference`.
  189. * Each entry may also be an array of synonymous identifiers, in which case the first identifier that matches to an entry of `features` is used.
  190. * Contents of `referenceFeatures` are expected to exhibit some overlap with identifiers in `testFeatures`.
  191. * Any `null` entries are considered to be incomparable.
  192. * If multiple entries of `referenceFeatures` match to the same feature in `features`, only the first matching entry is used and the rest are ignored.
  193. * @param {object} [options={}] - Optional parameters.
  194. * @param {number} [options.top=20] - Number of top marker features to use.
  195. * These features are taken from each pairwise comparison between labels.
  196. * @param {?number} [options.numberOfThreads=null] - Number of threads to use.
  197. * If `null`, defaults to {@linkcode maximumThreads}.
  198. *
  199. * @return {TrainedLabelCellsReference} Object containing the built reference dataset.
  200. */
  201. export function trainLabelCellsReference(testFeatures, loadedReference, referenceFeatures, options = {}) {
  202. const { top = 20, numberOfThreads = null, ...others } = options;
  203. utils.checkOtherOptions(others);
  204. var test_id_buffer;
  205. var ref_id_buffer;
  206. var output;
  207. let nthreads = utils.chooseNumberOfThreads(numberOfThreads);
  208. if (referenceFeatures.length != loadedReference.numberOfFeatures()) {
  209. throw new Error("length of 'referenceFeatures' should be equal to the number of features in 'loadedReference'");
  210. }
  211. const intersection = intersectFeatures(testFeatures, referenceFeatures);
  212. try {
  213. test_id_buffer = utils.wasmifyArray(intersection.test, "Int32WasmArray");
  214. ref_id_buffer = utils.wasmifyArray(intersection.reference, "Int32WasmArray");
  215. output = gc.call(
  216. module => module.train_singlepp_reference(
  217. test_id_buffer.length,
  218. test_id_buffer.offset,
  219. ref_id_buffer.offset,
  220. loadedReference.reference,
  221. top,
  222. nthreads
  223. ),
  224. TrainedLabelCellsReference,
  225. testFeatures.length
  226. );
  227. } catch (e) {
  228. utils.free(output);
  229. throw e;
  230. } finally {
  231. utils.free(test_id_buffer);
  232. utils.free(ref_id_buffer);
  233. }
  234. return output;
  235. }
  236. /**************************************************
  237. **************************************************/
  238. /**
  239. * Wrapper around the cell labelling results on the Wasm heap, typically produced by {@linkcode labelCells}.
  240. * @hideconstructor
  241. */
  242. class LabelCellsResults {
  243. #id;
  244. #results;
  245. constructor(id, raw) {
  246. this.#id = id;
  247. this.#results = raw;
  248. return;
  249. }
  250. /**
  251. * @return {number} Number of labels used in {@linkcode labelCells}.
  252. */
  253. numberOfLabels() {
  254. return this.#results.num_labels();
  255. }
  256. /**
  257. * @return {number} Number of cells that were labelled.
  258. */
  259. numberOfCells() {
  260. return this.#results.num_samples();
  261. }
  262. /**
  263. * @param {object} [options={}] - Optional parameters.
  264. * @param {boolean|string} [options.copy=true] - Copying mode, see {@linkcode possibleCopy} for details.
  265. * @return {Int32Array|Int32WasmArray} Array of length equal to the number of cells,
  266. * containing the index of the best label for each cell.
  267. */
  268. predicted(options = {}) {
  269. const { copy = true, ...others } = options;
  270. utils.checkOtherOptions(others);
  271. return utils.possibleCopy(this.#results.best(), copy);
  272. }
  273. /**
  274. * @param {number} i - Index of the cell of interest.
  275. * @param {object} [options={}] - Optional parameters.
  276. * @param {boolean} [options.asTypedArray=true] - Whether to return a Float64Array.
  277. * If `false`, a Float64WasmArray is returned instead.
  278. * @param {?Float64WasmArray} [options.buffer=null] - Buffer in which to store the output.
  279. * This should have the same length as the {@linkcode LabelCellsResults#numberOfLabels numberOfLabels}.
  280. *
  281. * @return {Float64Array|Float64WasmArray} Array containing the scores for this cell across all labels.
  282. * If `buffer` is supplied, the function returns `buffer` if `asTypedArray = false`, or a view on `buffer` if `asTypedArray = true`.
  283. */
  284. scoreForCell(i, options = {}) {
  285. let { asTypedArray = true, buffer = null, ...others } = options;
  286. utils.checkOtherOptions(others);
  287. let tmp = null;
  288. try {
  289. if (buffer == null) {
  290. tmp = utils.createFloat64WasmArray(this.#results.num_labels());
  291. buffer = tmp;
  292. }
  293. this.#results.score_for_sample(i, buffer.offset);
  294. } catch (e) {
  295. utils.free(tmp);
  296. throw e;
  297. }
  298. return utils.toTypedArray(buffer, tmp == null, asTypedArray);
  299. }
  300. /**
  301. * @param {number} i - Index of the label of interest.
  302. * @param {object} [options={}] - Optional parameters.
  303. * @param {boolean|string} [options.copy=true] - Copying mode, see {@linkcode possibleCopy} for details.
  304. * Only used if `buffer` is not supplied.
  305. * @return {Float64Array|Float64WasmArray} Array containing the scores across all cells for this label.
  306. */
  307. scoreForLabel(i, options = {}) {
  308. const { copy = true, ...others } = options;
  309. utils.checkOtherOptions(others);
  310. return utils.possibleCopy(this.#results.score_for_label(i), copy);
  311. }
  312. /**
  313. * @param {object} [options={}] - Optional parameters.
  314. * @param {boolean|string} [options.copy=true] - Copying mode, see {@linkcode possibleCopy} for details.
  315. * @return {Float64Array|Float64WasmArray} Array of length equal to the number of cells,
  316. * containing the difference in scores between the best and second-best labels.
  317. */
  318. delta(options = {}) {
  319. const { copy = true, ...others } = options;
  320. utils.checkOtherOptions(others);
  321. return utils.possibleCopy(this.#results.delta(), copy);
  322. }
  323. /**
  324. * @return Frees the memory allocated on the Wasm heap for this object.
  325. * This invalidates this object and all references to it.
  326. */
  327. free() {
  328. if (this.#results !== null) {
  329. gc.release(this.#id);
  330. this.#results = null;
  331. }
  332. }
  333. }
  334. /**
  335. * Label cells based on similarity in expression to a reference dataset.
  336. * This uses the [**SingleR** algorithm](https://github.com/SingleR-inc/singlepp) for cell type annotation.
  337. *
  338. * @param {ScranMatrix|Float64WasmArray} x - The count matrix, or log-normalized matrix, containing features in the rows and cells in the columns.
  339. * If a Float64WasmArray is supplied, it is assumed to contain a column-major dense matrix.
  340. * @param {BuildLabelledReferenceResults} reference - A built reference dataset, typically generated by {@linkcode buildLabelledReference}.
  341. * @param {object} [options={}] - Optional parameters.
  342. * @param {?number} [options.numberOfFeatures=null] - Number of features, used when `x` is a Float64WasmArray.
  343. * @param {?number} [options.numberOfCells=null] - Number of cells, used when `x` is a Float64WasmArray.
  344. * @param {number} [options.quantile=0.8] - Quantile on the correlations to use to compute the score for each label.
  345. * @param {?number} [options.numberOfThreads=null] - Number of threads to use.
  346. * If `null`, defaults to {@linkcode maximumThreads}.
  347. *
  348. * @return {LabelCellsResults} Labelling results for each cell in `x`.
  349. */
  350. export function labelCells(x, reference, options = {}) {
  351. const { numberOfFeatures = null, numberOfCells = null, quantile = 0.8, numberOfThreads = null, ...others } = options;
  352. utils.checkOtherOptions(others);
  353. var output = null;
  354. var matbuf;
  355. var tempmat;
  356. let nthreads = utils.chooseNumberOfThreads(numberOfThreads);
  357. try {
  358. let target;
  359. if (x instanceof ScranMatrix) {
  360. target = x.matrix;
  361. } else if (x instanceof wa.Float64WasmArray) {
  362. tempmat = init.initializeDenseMatrixFromDenseArray(numberOfFeatures, numberOfCells, x, { forceInteger: false });
  363. target = tempmat.matrix;
  364. } else {
  365. throw new Error("unknown type for 'x'");
  366. }
  367. if (target.nrow() != reference.expectedNumberOfFeatures) {
  368. throw new Error("number of rows in 'x' should be equal to length of 'features' used to build 'reference'");
  369. }
  370. output = gc.call(
  371. module => module.run_singlepp(target, reference.reference, quantile, nthreads),
  372. LabelCellsResults
  373. );
  374. } finally {
  375. utils.free(matbuf);
  376. utils.free(tempmat);
  377. }
  378. return output;
  379. }
  380. /**************************************************
  381. **************************************************/
  382. /**
  383. * Wrapper around integrated reference datasets on the Wasm heap, typically produced by {@linkcode integrateLabelledReferences}.
  384. * @hideconstructor
  385. */
  386. class IntegratedLabelCellsReferences {
  387. #id;
  388. #integrated;
  389. constructor(id, raw, expected_features) {
  390. this.#id = id;
  391. this.#integrated = raw;
  392. this.expectedNumberOfFeatures = expected_features;
  393. return;
  394. }
  395. // Internal use only, not documented.
  396. get integrated() {
  397. return this.#integrated;
  398. }
  399. /**
  400. * @return {number} Number of reference datasets.
  401. */
  402. numberOfReferences() {
  403. return this.#integrated.num_references();
  404. }
  405. /**
  406. * @return Frees the memory allocated on the Wasm heap for this object.
  407. * This invalidates this object and all references to it.
  408. */
  409. free() {
  410. if (this.#integrated !== null) {
  411. gc.release(this.#id);
  412. this.#integrated = null;
  413. }
  414. }
  415. }
  416. /**
  417. * Prepare a classifier that integrates multiple reference datasets.
  418. * This allows users to choose the best label for a test cell based on its classifications in multiple references.
  419. *
  420. * @param {Array} testFeatures - An array of feature identifiers (usually strings) of length equal to the number of rows in the test matrix.
  421. * Each entry should contain a single identifier for the corresponding row of the test matrix.
  422. * Any `null` entries are considered to be incomparable.
  423. * If any entries are duplicated, only the first occurrence is used and the rest are ignored.
  424. * @param {Array} loadedReferences - Array of {@linkplain LoadedLabelCellsReference} objects, typically created with {@linkcode loadLabelCellsReferenceFromBuffers}.
  425. * @param {Array} referenceFeatures - Array of length equal to `loadedReferences`,
  426. * containing arrays of feature identifiers (usually strings) of length equal to the number of features the corresponding entry of `loadedReferences`.
  427. * Each entry may also be an array of synonymous identifiers, in which case the first identifier that matches to an entry of `testFeatures` is used.
  428. * Contents of `referenceFeatures` are expected to exhibit some overlap with identifiers in `testFeatures`.
  429. * Any `null` entries are considered to be incomparable.
  430. * If multiple entries of `referenceFeatures` match to the same feature in `testFeatures`, only the first matching entry is used and the rest are ignored.
  431. * @param {Array} trainedReferences - Array of {@linkplain TrainedLabelCellsReference} objects, typically generated by calling {@linkcode trainLabelCellsReference}
  432. * on the same `testFeatures` and the corresponding entries of `loadedReferences` and `referenceFeatures`.
  433. * This should have length equal to that of `loadedReferences`.
  434. * @param {object} [options={}] - Optional parameters.
  435. * @param {?number} [options.numberOfThreads=null] - Number of threads to use.
  436. * If `null`, defaults to {@linkcode maximumThreads}.
  437. *
  438. * @return {IntegratedLabelCellsReference} Object containing the integrated references.
  439. */
  440. export function integrateLabelCellsReferences(testFeatures, loadedReferences, referenceFeatures, trainedReferences, options = {}) {
  441. const { numberOfThreads = null, ...others } = options;
  442. utils.checkOtherOptions(others);
  443. let interlen_arr;
  444. let test_id_arr = [];
  445. let test_id_ptr_arr;
  446. let ref_id_arr = [];
  447. let ref_id_ptr_arr;
  448. let loaded_arr;
  449. let trained_arr;
  450. let output;
  451. let nthreads = utils.chooseNumberOfThreads(numberOfThreads);
  452. // Checking the inputs.
  453. let nrefs = loadedReferences.length;
  454. if (referenceFeatures.length != nrefs) {
  455. throw new Error("'loadedReferences' and 'referenceFeatures' should be of the same length");
  456. }
  457. if (trainedReferences.length != nrefs) {
  458. throw new Error("'loadedReferences' and 'trainedReferences' should be of the same length");
  459. }
  460. for (var i = 0; i < nrefs; i++) {
  461. if (loadedReferences[i].numberOfFeatures() != referenceFeatures[i].length) {
  462. throw new Error("length of each 'referenceFeatures' should be equal to the number of features in the corresponding 'loadedReferences'");
  463. }
  464. }
  465. try {
  466. for (var i = 0; i < nrefs; i++) {
  467. const intersection = intersectFeatures(testFeatures, referenceFeatures[i]);
  468. test_id_arr.push(utils.wasmifyArray(intersection.test, "Int32WasmArray"));
  469. ref_id_arr.push(utils.wasmifyArray(intersection.reference, "Int32WasmArray"));
  470. }
  471. loaded_arr = utils.createBigUint64WasmArray(nrefs);
  472. trained_arr = utils.createBigUint64WasmArray(nrefs);
  473. test_id_ptr_arr = utils.createBigUint64WasmArray(nrefs);
  474. ref_id_ptr_arr = utils.createBigUint64WasmArray(nrefs);
  475. interlen_arr = utils.createInt32WasmArray(nrefs);
  476. {
  477. let la = loaded_arr.array();
  478. let ta = trained_arr.array();
  479. let tia = test_id_ptr_arr.array();
  480. let ria = ref_id_ptr_arr.array();
  481. let ia = interlen_arr.array();
  482. for (var i = 0; i < nrefs; i++) {
  483. la[i] = BigInt(loadedReferences[i].reference.$$.ptr);
  484. ta[i] = BigInt(trainedReferences[i].reference.$$.ptr);
  485. tia[i] = BigInt(test_id_arr[i].offset);
  486. ria[i] = BigInt(ref_id_arr[i].offset);
  487. ia[i] = test_id_arr[i].length;
  488. }
  489. }
  490. output = gc.call(
  491. module => module.integrate_singlepp_references(
  492. nrefs,
  493. interlen_arr.offset,
  494. test_id_ptr_arr.offset,
  495. ref_id_ptr_arr.offset,
  496. loaded_arr.offset,
  497. trained_arr.offset,
  498. nthreads
  499. ),
  500. IntegratedLabelCellsReferences,
  501. testFeatures.length
  502. );
  503. } catch (e) {
  504. utils.free(output);
  505. throw e;
  506. } finally {
  507. for (const x of test_id_arr) {
  508. utils.free(x);
  509. }
  510. for (const x of ref_id_arr) {
  511. utils.free(x);
  512. }
  513. utils.free(test_id_ptr_arr);
  514. utils.free(ref_id_ptr_arr);
  515. utils.free(loaded_arr);
  516. utils.free(trained_arr);
  517. }
  518. return output;
  519. }
  520. /**************************************************
  521. **************************************************/
  522. /**
  523. * Wrapper around the integrated cell labelling results on the Wasm heap, typically produced by {@linkcode integrateLabelCells}.
  524. * @hideconstructor
  525. */
  526. class IntegrateLabelCellsResults {
  527. #id
  528. #results;
  529. constructor(id, raw) {
  530. this.#id = id;
  531. this.#results = raw;
  532. return;
  533. }
  534. /**
  535. * @return {number} Number of labels used in {@linkcode integratedLabelCells}.
  536. */
  537. numberOfReferences() {
  538. return this.#results.num_references();
  539. }
  540. /**
  541. * @return {number} Number of cells that were labelled.
  542. */
  543. numberOfCells() {
  544. return this.#results.num_samples();
  545. }
  546. /**
  547. * @param {object} [options={}] - Optional parameters.
  548. * @param {boolean|string} [options.copy=true] - Copying mode, see {@linkcode possibleCopy} for details.
  549. *
  550. * @return {Int32Array|Int32WasmArray} Array of length equal to the number of cells,
  551. * containing the index of the best reference for each cell.
  552. */
  553. predicted(options = {}) {
  554. const { copy = true, ...others } = options;
  555. utils.checkOtherOptions(others);
  556. return utils.possibleCopy(this.#results.best(), copy);
  557. }
  558. /**
  559. * @param {number} i - Index of the cell of interest.
  560. * @param {object} [options={}] - Optional parameters.
  561. * @param {boolean} [options.asTypedArray=true] - Whether to return a Float64Array.
  562. * If `false`, a Float64WasmArray is returned instead.
  563. * @param {?Float64WasmArray} [options.buffer=null] - Buffer in which to store the output.
  564. * This should have the same length as the {@linkcode IntegrateLabelCellsResults#numberOfReferences numberOfReferences}.
  565. *
  566. * @return {Float64Array|Float64WasmArray} Array containing the scores for this cell across all labels.
  567. * If `buffer` is supplied, the function returns `buffer` if `asTypedArray = false`, or a view on `buffer` if `asTypedArray = true`.
  568. */
  569. scoreForCell(i, options = {}) {
  570. let { asTypedArray = true, buffer = null, ...others } = options;
  571. utils.checkOtherOptions(others);
  572. let tmp;
  573. try {
  574. if (buffer == null) {
  575. tmp = utils.createFloat64WasmArray(this.#results.num_references());
  576. buffer = tmp;
  577. }
  578. this.#results.score_for_sample(i, buffer.offset);
  579. } catch (e) {
  580. utils.free(tmp);
  581. throw e;
  582. }
  583. return utils.toTypedArray(buffer, tmp == null, asTypedArray);
  584. }
  585. /**
  586. * @param {number} i - Index of the reference of interest.
  587. * @param {object} [options={}] - Optional parameters.
  588. * @param {boolean|string} [options.copy=true] - Copying mode, see {@linkcode possibleCopy} for details.
  589. *
  590. * @return {Float64Array|Float64WasmArray} Array containing the scores across all cells for this label.
  591. */
  592. scoreForReference(i, options = {}) {
  593. const { copy = true, ...others } = options;
  594. utils.checkOtherOptions(others);
  595. return utils.possibleCopy(this.#results.score_for_reference(i), copy);
  596. }
  597. /**
  598. * @param {object} [options={}] - Optional parameters.
  599. * @param {boolean|string} [options.copy=true] - Copying mode, see {@linkcode possibleCopy} for details.
  600. *
  601. * @return {Float64Array|Float64WasmArray} Array of length equal to the number of cells,
  602. * containing the difference in scores between the best and second-best references.
  603. */
  604. delta(options = {}) {
  605. const { copy = true, ...others } = options;
  606. utils.checkOtherOptions(others);
  607. return utils.possibleCopy(this.#results.delta(), copy);
  608. }
  609. /**
  610. * @return Frees the memory allocated on the Wasm heap for this object.
  611. * This invalidates this object and all references to it.
  612. */
  613. free() {
  614. if (this.#results !== null) {
  615. gc.release(this.#id);
  616. this.#results = null;
  617. }
  618. }
  619. }
  620. /**
  621. * Integrate cell labels across multiple reference datasets.
  622. *
  623. * @param {ScranMatrix|Float64WasmArray} x - The count matrix, or log-normalized matrix, containing features in the rows and cells in the columns.
  624. * If a Float64WasmArray is supplied, it is assumed to contain a column-major dense matrix.
  625. * @param {IntegratedLabelCellsReferences} integrated - An integrated set of reference datasets, typically generated by {@linkcode integrateLabelCellsReferences}.
  626. * @param {Array} assigned - An array of length equal to the number of references in `integrated`.
  627. * This should contain the result of classification of `x` with each individual reference via {@linkcode labelCells}.
  628. * Each element should be a {@linkplain LabelCellsResults} object; or an Array, TypedArray or Int32WasmArray of length equal to the number of cells in `x`.
  629. * @param {object} [options={}] - Optional parameters.
  630. * @param {?number} [options.numberOfFeatures=null] - Number of features, used when `x` is a Float64WasmArray.
  631. * @param {?number} [options.numberOfCells=null] - Number of cells, used when `x` is a Float64WasmArray.
  632. * @param {number} [options.quantile=0.8] - Quantile on the correlations to use to compute the score for each label.
  633. * @param {?number} [options.numberOfThreads=null] - Number of threads to use.
  634. * If `null`, defaults to {@linkcode maximumThreads}.
  635. *
  636. * @return {IntegrateLabelCellsResults} Integrated labelling results for each cell in `x`.
  637. */
  638. export function integrateLabelCells(x, assigned, integrated, options = {}) {
  639. const { numberOfFeatures = null, numberOfCells = null, quantile = 0.8, numberOfThreads = null, ...others } = options;
  640. utils.checkOtherOptions(others);
  641. let nrefs = integrated.numberOfReferences();
  642. if (assigned.length != nrefs) {
  643. throw new Error("length of 'assigned' should be equal to the number of references in 'integrated'");
  644. }
  645. let output;
  646. var matbuf;
  647. var tempmat;
  648. let assigned_ptrs;
  649. let assigned_arrs = new Array(nrefs);
  650. let nthreads = utils.chooseNumberOfThreads(numberOfThreads);
  651. try {
  652. let target;
  653. if (x instanceof ScranMatrix) {
  654. target = x.matrix;
  655. } else if (x instanceof wa.Float64WasmArray) {
  656. tempmat = init.initializeScranMatrixFromDenseArray(numberOfFeatures, numberOfCells, x, { sparse: false, forceInteger: false });
  657. target = tempmat.matrix;
  658. } else {
  659. throw new Error("unknown type for 'x'");
  660. }
  661. if (target.nrow() != integrated.expectedNumberOfFeatures) {
  662. throw new Error("number of rows in 'x' should be equal to length of 'features' used to build 'reference'");
  663. }
  664. assigned_ptrs = utils.createBigUint64WasmArray(nrefs);
  665. let assigned_ptr_arr = assigned_ptrs.array();
  666. for (var i = 0; i < assigned.length; i++) {
  667. let current = assigned[i];
  668. if (current instanceof LabelCellsResults) {
  669. current = current.predicted({ copy: "view" });
  670. }
  671. if (current.length != x.numberOfColumns()) {
  672. throw new Error("length of each element in 'assigned' should be equal to number of columns in 'x'");
  673. }
  674. assigned_arrs[i] = utils.wasmifyArray(current, "Int32WasmArray");
  675. assigned_ptr_arr[i] = BigInt(assigned_arrs[i].offset);
  676. }
  677. output = gc.call(
  678. module => module.integrate_singlepp(
  679. target,
  680. assigned_ptrs.offset,
  681. integrated.integrated,
  682. quantile,
  683. nthreads
  684. ),
  685. IntegrateLabelCellsResults
  686. );
  687. } catch (e) {
  688. utils.free(output);
  689. throw e;
  690. } finally{
  691. utils.free(assigned_ptrs);
  692. for (const x of assigned_arrs) {
  693. utils.free(x);
  694. }
  695. utils.free(matbuf);
  696. utils.free(tempmat);
  697. }
  698. return output;
  699. }