steps/rna_pca.js

  1. import * as scran from "scran.js";
  2. import * as utils from "./utils/general.js";
  3. import * as filter_module from "./cell_filtering.js";
  4. import * as norm_module from "./rna_normalization.js";
  5. import * as feat_module from "./feature_selection.js";
  6. export const step_name = "rna_pca";
  7. /**
  8. * Results of running PCA on some input matrix,
  9. * see [here](https://kanaverse.github.io/scran.js/RunPCAResults.html) for details.
  10. *
  11. * @external RunPCAResults
  12. */
  13. /**
  14. * This step performs a principal components analysis (PCA) to compact and denoise the data.
  15. * The resulting PCs can be used as input to various per-cell analyses like clustering and dimensionality reduction.
  16. * It wraps the [`runPca`](https://kanaverse.github.io/scran.js/global.html#runPca) function
  17. * from [**scran.js**](https://github.com/kanaverse/scran.js).
  18. *
  19. * Methods not documented here are not part of the stable API and should not be used by applications.
  20. * @hideconstructor
  21. */
  22. export class RnaPcaState {
  23. #filter;
  24. #norm;
  25. #feat;
  26. #cache;
  27. #parameters;
  28. constructor(filter, norm, feat, parameters = null, cache = null) {
  29. if (!(filter instanceof filter_module.CellFilteringState)) {
  30. throw new Error("'filter' should be a CellFilteringState object");
  31. }
  32. this.#filter = filter;
  33. if (!(norm instanceof norm_module.RnaNormalizationState)) {
  34. throw new Error("'norm' should be an RnaNormalizationState object");
  35. }
  36. this.#norm = norm;
  37. if (!(feat instanceof feat_module.FeatureSelectionState)) {
  38. throw new Error("'feat' should be a FeatureSelectionState object");
  39. }
  40. this.#feat = feat;
  41. this.#parameters = (parameters === null ? {} : parameters);
  42. this.#cache = (cache === null ? {} : cache);
  43. this.changed = false;
  44. }
  45. free() {
  46. utils.freeCache(this.#cache.hvg_buffer);
  47. utils.freeCache(this.#cache.pcs);
  48. }
  49. /***************************
  50. ******** Getters **********
  51. ***************************/
  52. valid() {
  53. return this.#norm.valid();
  54. }
  55. /**
  56. * @return {external:RunPCAResults} Results of the PCA on the normalized gene expression values.
  57. */
  58. fetchPCs() {
  59. return this.#cache.pcs;
  60. }
  61. /**
  62. * @return {object} Object containing the parameters.
  63. */
  64. fetchParameters() {
  65. return { ...this.#parameters }; // avoid pass-by-reference links.
  66. }
  67. /***************************
  68. ******** Compute **********
  69. ***************************/
  70. /**
  71. * This method should not be called directly by users, but is instead invoked by {@linkcode runAnalysis}.
  72. *
  73. * @param {object} parameters - Parameter object, equivalent to the `rna_pca` property of the `parameters` of {@linkcode runAnalysis}.
  74. * @param {number} parameters.num_pcs - Number of PCs to return.
  75. * @param {number} parameters.num_hvgs - Number of highly variable genes (see {@linkplain FeatureSelectionState}) to use in the PCA.
  76. * @param {string} parameters.block_method - Blocking method to use when dealing with multiple samples.
  77. * This can be one of:
  78. *
  79. * - `"none"`, in which case nothing is done using the sample information.
  80. * - `"regress"`, where linear regression is applied to remove mean differences between samples.
  81. * - `"project"`, where samples are weighted so that they contribute equally regardless of the number of cells.
  82. *
  83. * @return The object is updated with the new results.
  84. */
  85. compute(parameters) {
  86. let { num_hvgs, num_pcs, block_method } = parameters;
  87. if (block_method == "weight") {
  88. block_method = "project";
  89. }
  90. this.changed = false;
  91. if (this.#feat.changed || num_hvgs !== this.#parameters.num_hvgs) {
  92. if (this.valid()) {
  93. choose_hvgs(num_hvgs, this.#feat, this.#cache);
  94. this.changed = true;
  95. }
  96. this.#parameters.num_hvgs = num_hvgs;
  97. }
  98. if (this.changed || this.#norm.changed || num_pcs !== this.#parameters.num_pcs || block_method !== this.#parameters.block_method) {
  99. utils.freeCache(this.#cache.pcs);
  100. if (this.valid()) {
  101. let sub = this.#cache.hvg_buffer;
  102. let block = this.#filter.fetchFilteredBlock();
  103. var mat = this.#norm.fetchNormalizedMatrix();
  104. this.#cache.pcs = scran.runPca(mat, { features: sub, numberOfPCs: num_pcs, block: block, blockMethod: block_method });
  105. this.changed = true;
  106. }
  107. this.#parameters.num_pcs = num_pcs;
  108. this.#parameters.block_method = block_method;
  109. }
  110. return;
  111. }
  112. static defaults() {
  113. return {
  114. num_hvgs: 2000,
  115. num_pcs: 20,
  116. block_method: "none"
  117. };
  118. }
  119. }
  120. /**************************
  121. ******* Internals ********
  122. **************************/
  123. function choose_hvgs(num_hvgs, feat, cache) {
  124. var sorted_resids = feat.fetchSortedResiduals();
  125. var sub = utils.allocateCachedArray(sorted_resids.length, "Uint8Array", cache, "hvg_buffer");
  126. if (num_hvgs < sorted_resids.length) {
  127. var threshold_at = sorted_resids[sorted_resids.length - num_hvgs];
  128. var unsorted_resids = feat.fetchResults().residuals({ copy: false });
  129. sub.array().forEach((element, index, array) => {
  130. array[index] = unsorted_resids[index] >= threshold_at;
  131. });
  132. } else {
  133. sub.fill(1);
  134. }
  135. return sub;
  136. }