diff --git a/package.json b/package.json index 4f702ea1..84c1888f 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", "bakana": "~0.6.2", + "bakana-remotes": "~0.1.1", "d3": "^7.1.1", "epiviz.gl": "^1.0.2", "epiviz.scatter.gl": "^0.0.5", diff --git a/src/App.js b/src/App.js index 15e72f1d..ef3e11c5 100644 --- a/src/App.js +++ b/src/App.js @@ -158,7 +158,7 @@ const App = () => { setGeneColSel, setLoadParams, setInitLoadState, inputFiles, annotationCols, setAnnotationCols, annotationObj, setAnnotationObj, preInputFiles, - setPreInputFilesStatus, geneColSel } = useContext(AppContext); + setPreInputFilesStatus, geneColSel, setEhubDatasets } = useContext(AppContext); // initializes various things on the worker side useEffect(() => { @@ -469,6 +469,11 @@ const App = () => { setKanaIDBRecs(resp); } setIndexedDBState(false); + } else if (payload.type === "ExperimentHub_store") { + const { resp } = payload; + if (resp !== undefined && Array.isArray(resp)) { + setEhubDatasets(resp); + } } else if (payload.type === "inputs_DATA") { var info = []; if ("RNA" in payload.resp.num_genes) { @@ -488,7 +493,6 @@ const App = () => { let pmods = Object.keys(payload.resp.genes); setModality(pmods); - } else if (payload.type === "quality_control_DATA") { const { resp } = payload; diff --git a/src/components/Analysis/index.js b/src/components/Analysis/index.js index e3d9dd26..e0efab4d 100644 --- a/src/components/Analysis/index.js +++ b/src/components/Analysis/index.js @@ -25,7 +25,8 @@ const AnalysisDialog = ({ tabSelected, setTabSelected, loadParams, setLoadParamsFor, loadParamsFor, setDatasetName, - setPreInputFiles, preInputFilesStatus, setPreInputFilesStatus } = useContext(AppContext); + setPreInputFiles, preInputFilesStatus, setPreInputFilesStatus, + ehubDatasets } = useContext(AppContext); const [inputText, setInputText] = useState([]); @@ -46,7 +47,8 @@ const AnalysisDialog = ({ genes: "Choose feature/gene file", annotations: "Choose barcode/annotation file", file: "Choose file...", - rds: "Choose file..." + rds: "Choose file...", + id: "Choose id..." }); let [stmpInputFiles, ssetTmpInputFiles] = useState({ @@ -227,11 +229,21 @@ const AnalysisDialog = ({ ) { all_valid = false; } + + if ( + x?.id && !( + ehubDatasets.indexOf(inputText[ix]?.id) !== -1 + ) + ) { + all_valid = false; + } if (x.format === "MatrixMarket") { if (!x.mtx) all_valid = false; } else if (x.format === "SummarizedExperiment") { if (!x.rds) all_valid = false; + } else if (x.format === "ExperimentHub") { + if (!x.id) all_valid = false; } else { if (!x.h5) all_valid = false; } @@ -344,6 +356,16 @@ const AnalysisDialog = ({ } if (!x.rds && (sinputText?.file !== "Choose file...")) all_valid = false; + } else if ( + x.format === "ExperimentHub") { + if (x?.id && !( + ehubDatasets.indexOf(sinputText?.id) !== -1 + ) + ) { + all_valid = false; + } + + if (!x.id && (sinputText?.id !== "Choose id...")) all_valid = false; } // setTmpInputValid(all_valid); @@ -1197,12 +1219,41 @@ const AnalysisDialog = ({ onInputChange={(msg) => { if (msg.target.files) { ssetInputText({ ...sinputText, "rds": msg.target.files[0].name }); - ssetTmpInputFiles({ ...stmpInputFiles, "rds": msg.target.files[0] }) + ssetTmpInputFiles({ ...stmpInputFiles, "rds": msg.target.files[0] }); } }} /> } /> + + + + } /> @@ -1529,6 +1580,8 @@ const AnalysisDialog = ({ } } else if (row["format"] === "SummarizedExperiment") { tname += ` file: ${row.rds.name} `; + } else if (row["format"] === "ExperimentHub") { + tname += ` file: ${row.id} `; } else { tname += ` file: ${row.h5.name} `; } @@ -1652,7 +1705,8 @@ const AnalysisDialog = ({ mtx: "Choose Matrix Market file", genes: "Choose feature/gene file", annotations: "Choose barcode/annotation file", - file: "Choose file..." + file: "Choose file...", + id: "Choose id..." }); ssetTmpInputFiles({ @@ -1988,7 +2042,8 @@ const AnalysisDialog = ({
  • Matrix Market - *.mtx or *.mtx.gz
  • features or genes, *.tsv or *.tsv.gz
  • HDF5 (10X or H5AD) - *.h5 or *.hdf5 or *.h5ad
  • -
  • RDS - *.rds
  • +
  • RDS - *.rds or
  • +
  • ExperimentHub ID
  • Note: Names of dataset must be unique! @@ -2024,6 +2079,13 @@ const AnalysisDialog = ({ For a SingleCellExperiment, any alternative experiment with name starting with "hto", "adt" or "antibody" is assumed to represent CITE-seq data.

    +

    + A Dataset saved to ExperimentHub. + We support any SummarizedExperiment subclass containing a dense or sparse count matrix + (identified as any assay with name starting with "counts", or if none exist, just the first assay). + For a SingleCellExperiment, any alternative experiment with name starting with "hto", "adt" or "antibody" is assumed to represent CITE-seq data. +

    +

    Batch correction: you can now import more than one file to integrate and analyze datasets. If you only import a single dataset, specify the annotation column that contains the batch information.

    diff --git a/src/context/AppContext.js b/src/context/AppContext.js index 19c37d35..79bf6f8a 100644 --- a/src/context/AppContext.js +++ b/src/context/AppContext.js @@ -15,6 +15,9 @@ const AppContextProvider = ({ children }) => { // Pre flight Input Status const [preInputFilesStatus, setPreInputFilesStatus] = useState(null); + // Ehub datasets + const [ehubDatasets, setEhubDatasets] = useState(null); + // default params const [params, setParams] = useState({ qc: { @@ -129,7 +132,8 @@ const AppContextProvider = ({ children }) => { annotationCols, setAnnotationCols, annotationObj, setAnnotationObj, preInputFiles, setPreInputFiles, - preInputFilesStatus, setPreInputFilesStatus + preInputFilesStatus, setPreInputFilesStatus, + ehubDatasets, setEhubDatasets }} > {children} diff --git a/src/workers/scran.worker.js b/src/workers/scran.worker.js index 4d35b255..f371e916 100644 --- a/src/workers/scran.worker.js +++ b/src/workers/scran.worker.js @@ -4,12 +4,19 @@ import * as downloads from "./DownloadsDBHandler.js"; import * as hashwasm from "hash-wasm"; import * as translate from "./translate.js"; import { extractBuffers, postAttempt, postSuccess, postError } from "./helpers.js"; +import * as remotes from "bakana-remotes"; +import * as ehub from "bakana-remotes/ExperimentHub"; /***************************************/ let superstate = null; +const proxy = "https://cors-proxy.aaron-lun.workers.dev"; +// TODO: consolidate all bakana-related download functions into a single getter/setter. bakana.setCellLabellingDownload(downloads.get); +remotes.setDownloadFun(url => downloads.get(proxy + "/" + encodeURIComponent(url))); + +bakana.availableReaders["ExperimentHub"] = ehub; bakana.setVisualizationAnimate((type, x, y, iter) => { postMessage({ @@ -149,6 +156,20 @@ onmessage = function (msg) { }); }); + try { + let ehub_ids = ehub.availableDatasets(); + postMessage({ + type: "ExperimentHub_store", + resp: ehub_ids, + msg: "Success: ExperimentHub initialized" + }); + } catch { + postMessage({ + type: "ExperimentHub_ERROR", + msg: "Error: Cannot access datasets in ExperimentHub" + }); + } + loaded = Promise.all([ back_init,