--- title: "Tutorial 17 — FAIR Metadata & Submission" subtitle: "Build a CELLxGENE-conformant metadata sheet, convert Seurat → AnnData, generate GEO + BioSample upload templates" author: "Single Cell RNA-seq Workshop" format: html: toc: true toc-depth: 3 code-fold: false code-overflow: wrap highlight-style: github embed-resources: true execute: eval: false echo: true warning: false message: false editor: visual --- ::: {.callout-note title="Running the code in this tutorial"} Every code chunk here is tagged with `#| eval: false`, so the published page shows the code **without running it**. To run it yourself, open the downloaded `.qmd` in **RStudio**: - **Run one chunk:** click the green ▶ (*Run Current Chunk*) at the chunk's top-right, or press *Ctrl/Cmd + Enter*. This runs the chunk **regardless of its `eval` setting** — the easiest way to work through the tutorial interactively. - **Run a chunk on render:** change that chunk's `#| eval: false` to `#| eval: true` (or delete the line) so it executes when the document is rendered. - **Render the whole tutorial:** click the **Render** button in RStudio (or run `quarto render` in a terminal) to execute the chunks top-to-bottom and knit a finished HTML. To run everything on render, set `eval: true` once in the YAML header at the top. ::: ## About this tutorial The hands-on companion to **[Lecture 17 — FAIR Principles & Data Sharing](../Lecture_Folder/Lecture_17_FAIR_DataSharing.html)** and the parallel of [scNotebooks Module 13](https://integrativebioinformatics.github.io/scNotebooks/modules/en/module13/module13.html). We take the annotated `ifnb` object from Tutorial 04 and walk through what it would take to **deposit it for real** in NCBI + CELLxGENE Discover: ::: callout-note **Companion book chapter:** [Chapter 17 — FAIR & Data Sharing](../Resources_Folder/Chapter_17_FAIR.html) — the long-form prose treatment of this tutorial's material, with cross-references to the prerequisite appendices. ::: 1. Audit what metadata the Seurat object actually carries 2. Build a **canonical sample sheet** with ontology-coded fields (Cell Ontology, Uberon, MONDO, EFO, HANCESTRO) 3. Generate **per-cell metadata** at the CELLxGENE schema bar 4. Convert Seurat → **AnnData** (`.h5ad`) 5. Validate the AnnData against the CELLxGENE schema 6. Generate **GEO** + **BioSample** upload TSVs from the canonical sheet 7. Write a **Data Availability** statement for the manuscript The exercise is designed so you can run it on the workshop's `ifnb` object **and** re-run it on your own data later, replacing the metadata fields. ::: {.callout-tip title="How to use this page"} 1. Download the `.qmd` source: Tutorial_17_FAIR_Metadata.qmd. If your browser saves the file as `Tutorial_17_FAIR_Metadata.qmd.txt`, **drop the trailing `.txt`** so the filename ends in `.qmd`, then open it in RStudio. 2. Make sure you have completed **[Tutorial 04 — Reference Annotation](Tutorial_04_Reference_Annotation.html)** — it writes `ifnb_annotated_final.rds`. 3. Work through the chunks, flipping `eval: true` as you go. ::: ::: callout-warning **You will not actually submit anything to NCBI / CELLxGENE in this tutorial.** Real submissions need a real NCBI account, an institutional contact, and an actual study. This tutorial generates the artifacts you'd upload — you can do the *file-preparation* part of submission at your laptop, validate everything, then go through the real submission portals when you have a real dataset. ::: ## Dataset — resuming from Tutorial 04 | File | Produced by | What it is | |---|---|---| | `../data/ifnb_annotated_final.rds` | Tutorial 04 | Seurat object with PCA/UMAP, manual labels, Azimuth labels, and the reconciled `celltype_final` per cell. | | `../Resources_Folder/metadata_templates/canonical_sample_sheet.csv` | provided | Worked example sample sheet — you'll edit this to match your real study. | ::: {.callout-warning title="Common errors / things that bite"} **`SeuratDisk::SaveH5Seurat` fails with "object too large" or "wrong assay format"** — Seurat v5's `Assay5` class isn't fully supported by older `SeuratDisk` versions. Either downgrade `SeuratDisk` to its dev branch (`remotes::install_github("mojaveazure/seurat-disk")`) or use Seurat 5's built-in `writeH5AD()` if available. **`bitr()` returns an empty data frame** — your gene symbols don't match `org.Hs.eg.db`'s symbol naming convention. Check for case mismatches (gene `Cd3d` vs `CD3D`). For mouse use `org.Mm.eg.db`; some Seurat objects mix species — confirm `rownames(seu)` looks right. **`cellxgene-schema validate` reports "ontology term not found"** — the ontology IDs (`CL:`, `UBERON:`, `MONDO:`) get updated periodically. The validator pulls fresh ontology versions; an ID that worked a year ago may be deprecated. Look up the current ID at . **Validator passes locally but submission portal rejects** — the schema version updates faster than your local validator. Always check the [CELLxGENE schema doc](https://github.com/chanzuckerberg/single-cell-curation) for the version the portal currently requires before submitting. ::: ::: {.callout-tip title="Solutions / instructor copy"} The *Think about it* prompts on this page have inline answers (click "Show answer" / "Click for answer" to expand). For the full **runnable instructor copy** with every chunk pre-evaluated and outputs shown: - **For students:** the same `.qmd` you downloaded already contains every solution inline — just expand the collapsed answers - **For instructors:** render with `quarto render --profile solutions` from the `Exercise_Folder/` directory. The fully-evaluated HTML is written to `docs/Exercise_Folder/_solutions/Tutorial_17_FAIR_Metadata.html`. See [Exercise_Folder/_quarto-solutions.yml](https://github.com/wcresko/scRNAseq_tutorial/blob/main/Exercise_Folder/_quarto-solutions.yml) for the build profile ::: ## Setup ```{r} #| label: M17-setup library(Seurat) library(tidyverse) # Packages are installed in Tutorial 00 (Setup → bonus modules) — load them here. library(SeuratDisk) # SaveH5Seurat / Convert library(jsonlite) library(yaml) set.seed(2026) # --------------------------------------------------------------------------- # Output directory for this module's figures and tables. # Every figure/table chunk below writes a file named Mod17_C_ # into ../output/Mod17/ so it can be cross-referenced from the rest of the site. # Mod17 = Module 17 (this tutorial); C = the nth code chunk. # --------------------------------------------------------------------------- out_dir <- "../output/Mod17" dir.create(out_dir, recursive = TRUE, showWarnings = FALSE) dir.create("../data", showWarnings = FALSE) # shared .rds hand-off folder (sibling of scripts/, created one level up) message("This module writes its figures & tables to: ", normalizePath(out_dir)) ifnb <- readRDS("../data/ifnb_annotated_final.rds") ifnb ``` ## Step 1 — Audit what's actually in the object ```{r} #| label: M17-audit # Per-cell metadata columns colnames(ifnb@meta.data) # Per-sample collapsed view: what unique values are in stim? table(ifnb$stim) # --- Tables out: metadata column inventory and per-sample cell counts ------- tibble::tibble(metadata_column = colnames(ifnb@meta.data)) |> readr::write_csv(file.path(out_dir, "Mod17_C2_metadata_columns.csv")) tibble::enframe(table(ifnb$stim), name = "stim", value = "n_cells") |> dplyr::mutate(n_cells = as.integer(n_cells)) |> readr::write_csv(file.path(out_dir, "Mod17_C2_cells_per_sample.csv")) ``` ::: {.callout-tip title="Reading the output"} `colnames(ifnb@meta.data)` prints the inventory of per-cell metadata columns currently in the object. You should see QC metrics (`nCount_RNA`, `nFeature_RNA`, `percent.mt`), the condition label (`stim`), doublet calls, cluster assignments, and cell-type labels from Tutorial 04. `table(ifnb$stim)` shows the cell count per condition: roughly equal numbers in CTRL and STIM is expected for this dataset. The **gap between what's present and what submission requires** is the key observation — species, tissue ontology, assay type, and donor ID are conspicuously absent, which is what the rest of this tutorial adds. ::: Most Seurat objects you'll meet have **rich per-cell** metadata (cluster, cell-type label, QC metrics) and **almost no sample-level metadata** (just `stim` here). Submission needs both. ::: {.callout-important title="Think about it"} Imagine someone in 2030 wants to reuse your dataset. From the `ifnb` object alone, can they answer: *what species is this? what tissue? what assay? what donor donated which sample?* If the answer is "you'd have to read the paper", your metadata is incomplete.
Show answers A 2030-era reanalyzer would not be able to determine species (the paper says human PBMCs but the object doesn't), tissue (PBMC ≈ blood), assay (10x 3' v1), or donor identity (the SeuratData distribution drops the per-cell donor IDs from Kang et al. 2017). All of this needs to live in machine-readable metadata, not in the methods section.
::: ## Step 2 — Build a per-sample canonical sheet Load the example sheet and adapt it for the `ifnb` study. In a real study, you'd build this **at experimental design time** and version-control it alongside your code. ```{r} #| label: M17-canonical_sheet canonical <- read_csv("../Resources_Folder/metadata_templates/canonical_sample_sheet.csv") canonical # --- Table out: the canonical sample sheet as loaded ----------------------- readr::write_csv(canonical, file.path(out_dir, "Mod17_C3_canonical_sample_sheet.csv")) ``` ::: {.callout-tip title="Reading the output"} When you run this chunk, the console prints the canonical sheet as a tibble — one row per sample, one column per metadata field. Scan across the columns: every field that appears here (organism, tissue, assay, disease, sex, treatment, donor ID, and their ontology-term-ID counterparts) is a field that some downstream database or atlas integration will need. If a column is `NA` for a real study, that's a gap to fill before submission. The `_uberon`, `_efo`, `_mondo` columns are the machine-readable ontology IDs that make the table interoperable; the plain-text columns (e.g. `tissue = "blood"`) are for human readability only. ::: The sheet contains everything a downstream consumer needs. Note the **paired columns**: `tissue` (human-readable) + `tissue_uberon` (ontology ID). Always carry both — the ontology ID is what makes the metadata interoperable. ::: callout-tip **Look up ontology terms** at [OLS at EBI](https://www.ebi.ac.uk/ols4/) or [BioPortal](https://bioportal.bioontology.org/). Search "B cell" → `CL:0000236`. Paste the ID into your sheet. ::: ## Step 3 — Per-cell metadata at the CELLxGENE bar CELLxGENE Discover requires a small set of per-cell columns to be present, with values from controlled vocabularies. The current schema (v5+) requires: | Column | Vocabulary | Example for ifnb | |---|---|---| | `cell_type_ontology_term_id` | Cell Ontology (`CL:`) | `CL:0000236` (B cell) | | `tissue_ontology_term_id` | Uberon (`UBERON:`) | `UBERON:0000178` (blood) | | `assay_ontology_term_id` | EFO (`EFO:`) | `EFO:0009922` (10x 3' v3) — for ifnb actually `EFO:0009899` (10x 3' v1) | | `disease_ontology_term_id` | MONDO / PATO | `PATO:0000461` (normal) | | `organism_ontology_term_id` | NCBITaxon | `NCBITaxon:9606` (human) | | `sex_ontology_term_id` | PATO | `PATO:0000383` / `PATO:0000384` | | `development_stage_ontology_term_id` | HsapDv | varies | | `self_reported_ethnicity_ontology_term_id` | HANCESTRO | `unknown` permitted | | `is_primary_data` | bool | `TRUE` for original; `FALSE` for re-analyzed | | `suspension_type` | enum | `cell` | | `donor_id` | study-internal | "donor1", "donor2", … | Build a per-cell metadata frame from the canonical sheet + author cell types: ```{r} #| label: M17-per_cell_metadata # Map seurat_annotations -> Cell Ontology IDs. cl_map <- tribble( ~seurat_annotations, ~cell_type_ontology_term_id, "CD14 Mono", "CL:0001054", # CD14-positive monocyte "CD16 Mono", "CL:0002397", # CD16-positive monocyte "CD4 Naive T", "CL:0000895", # naive CD4-positive alpha-beta T cell "CD4 Memory T", "CL:0000897", # CD4-positive alpha-beta memory T cell "CD8 T", "CL:0000625", # CD8-positive alpha-beta T cell "T activated", "CL:0000084", # T cell (broad) "B", "CL:0000236", # B cell "B Activated", "CL:0000236", # B cell (subtype not in CL) "NK", "CL:0000623", # natural killer cell "DC", "CL:0000451", # dendritic cell "pDC", "CL:0000784", # plasmacytoid dendritic cell "Mk", "CL:0000556", # megakaryocyte "Eryth", "CL:0000232" # erythrocyte ) # Pull in study-level metadata from canonical_sheet study_meta <- tibble( organism_ontology_term_id = "NCBITaxon:9606", tissue_ontology_term_id = "UBERON:0000178", # blood assay_ontology_term_id = "EFO:0009899", # 10x 3' v1 is_primary_data = TRUE, suspension_type = "cell", development_stage_ontology_term_id = "unknown", self_reported_ethnicity_ontology_term_id = "unknown" ) # Disease + treatment differ between CTRL and STIM sample_specific <- tibble( stim = c("CTRL", "STIM"), disease_ontology_term_id = c("PATO:0000461", "PATO:0000461"), # both normal treatment = c("untreated", "IFN-beta 100U/mL 6h") ) per_cell <- ifnb@meta.data |> rownames_to_column("cell_barcode") |> select(cell_barcode, stim, seurat_annotations, celltype_final) |> left_join(cl_map, by = "seurat_annotations") |> left_join(sample_specific, by = "stim") |> bind_cols(study_meta[rep(1, nrow(ifnb@meta.data)), ]) head(per_cell) sum(is.na(per_cell$cell_type_ontology_term_id)) # any unmapped cell types? # --- Table out: the per-cell CELLxGENE-schema metadata frame --------------- readr::write_csv(per_cell, file.path(out_dir, "Mod17_C4_per_cell_metadata.csv")) ``` ::: {.callout-tip title="Reading the output"} `head(per_cell)` prints the first six rows of the schema-conformant per-cell table: each row is one cell barcode, and the columns should now include `cell_type_ontology_term_id`, `tissue_ontology_term_id`, `assay_ontology_term_id`, `disease_ontology_term_id`, and the other required CELLxGENE fields. Check that the `CL:` IDs look plausible for the cell types in that first handful of rows. The `sum(is.na(...))` line is the critical quality check: if it returns anything other than `0`, at least one `seurat_annotations` label did not match any row in `cl_map` and you need to add it to the look-up table before proceeding. ::: ::: {.callout-important title="Think about it"} The CELLxGENE schema does NOT have a `condition` field — it has `disease`. How would you encode "IFN-β stimulation" in a strictly schema-conformant way?
Show answers CELLxGENE distinguishes between **disease state** (a clinical condition) and **experimental treatment** (an *in vitro* perturbation). For `ifnb`, both CTRL and STIM cells are biologically *normal* (`PATO:0000461`) — the IFN-β stimulation is an experimental perturbation, not a disease. Encode it in a free-text `treatment` column (CELLxGENE allows custom columns alongside the required ones) or as part of the description metadata. Don't shoehorn an experimental treatment into the `disease` field — it breaks atlas integration.
::: ## Step 4 — Attach the new metadata back to the Seurat object ```{r} #| label: M17-attach # Make sure cell ordering matches stopifnot(identical(per_cell$cell_barcode, colnames(ifnb))) new_cols <- per_cell |> select(-cell_barcode, -stim, -seurat_annotations, -celltype_final) for (cn in colnames(new_cols)) { ifnb[[cn]] <- new_cols[[cn]] } # Sanity-check table(ifnb$cell_type_ontology_term_id) # --- Table out: cell counts per Cell Ontology term ------------------------- tibble::enframe(table(ifnb$cell_type_ontology_term_id), name = "cell_type_ontology_term_id", value = "n_cells") |> dplyr::mutate(n_cells = as.integer(n_cells)) |> dplyr::arrange(dplyr::desc(n_cells)) |> readr::write_csv(file.path(out_dir, "Mod17_C5_cells_per_ontology_term.csv")) ``` ::: {.callout-tip title="Reading the output"} `table(ifnb$cell_type_ontology_term_id)` prints a frequency table of `CL:` term IDs across all cells. Cross-check it against what you'd expect from the original `seurat_annotations` distribution: if `CL:0000236` (B cell) has roughly the same count as `B` + `B Activated` combined, the join worked. Any `NA` entries here mean the ontology column didn't attach cleanly — re-run the `stopifnot()` check and confirm cell ordering. If the `stopifnot` silently passes (no error), the new metadata is in the right cell slots. ::: ## Step 5 — Convert Seurat → AnnData (`.h5ad`) ```{r} #| label: M17-to_h5ad # SeuratDisk's two-step Seurat -> H5Seurat -> H5AD bridge # (Seurat 5 has built-in conversion via writeH5AD too if SeuratDisk is unavailable) SaveH5Seurat(ifnb, filename = "../data/ifnb_for_submission.h5Seurat", overwrite = TRUE) Convert("../data/ifnb_for_submission.h5Seurat", dest = "h5ad", overwrite = TRUE) file.info("../data/ifnb_for_submission.h5ad")$size / 1e6 # MB ``` ## Step 6 — Validate against the CELLxGENE schema CELLxGENE provides a **Python** validator (`cellxgene-schema`) that you can run from the command line on the resulting `.h5ad`. From a shell: ```{bash} #| label: M17-pip_install_cellxgene_schema #| eval: false # pip install cellxgene-schema cellxgene-schema validate data/ifnb_for_submission.h5ad ``` The validator reports every missing or out-of-vocabulary field. Iterate until the validator returns no errors. ::: callout-tip You can install Python and the validator inside an R session via [`reticulate`](https://rstudio.github.io/reticulate/) and call it as `system("cellxgene-schema validate ...")`. For a real lab workflow, keep the validation as a `Makefile` rule so it runs on every metadata update. ::: ## Step 7 — Generate BioSample + SRA + GEO upload TSVs Real submissions to NCBI use the portal's online forms or batch-upload TSV templates. Your **canonical sheet** is the single source of truth that you transform into each portal's required TSV format. ### 7a. BioSample TSV (one row per sample) ```{r} #| label: M17-biosample_tsv biosample <- canonical |> transmute( sample_name = sample_id, sample_title = paste(sample_label_for_publication, "from", donor_id), bioproject_accession = "PRJNA", # fill in after BioProject is created organism, isolate = donor_id, age, biomaterial_provider = "", # your institution sex = sex, tissue, cell_type_ontology = "CL:0000084 (T cell)", # broad — the per-cell file has the fine-grained labels disease, treatment, collection_date = library_prep_date ) write_tsv(biosample, "../data/submission_biosample.tsv") biosample # --- Table out: BioSample upload template ---------------------------------- readr::write_csv(biosample, file.path(out_dir, "Mod17_C7_biosample_template.csv")) ``` ::: {.callout-tip title="Reading the output"} The tibble printed by `biosample` is the BioSample upload template — one row per sequencing sample. Check that `sample_name` is unique and consistently named (it becomes the join key that links BioSample, SRA, and GEO records), and that `bioproject_accession` is filled in (or flagged as `PRJNA` for now). Any `NA` in a required field will cause a portal error at submission time. For the SRA and GEO templates in the next two sub-steps the column names change but the logic is the same: all rows derive from the same canonical sheet, so inconsistencies must be fixed there first. ::: ### 7b. SRA metadata TSV (one row per sequencing run) ```{r} #| label: M17-sra_tsv sra <- canonical |> transmute( sample_name = sample_id, library_ID = sample_id, title = paste("scRNA-seq of", sample_label_for_publication, "PBMCs", "from", donor_id), library_strategy = "RNA-Seq", library_source = "TRANSCRIPTOMIC SINGLE CELL", library_selection = "cDNA", library_layout = "PAIRED", platform = "ILLUMINA", instrument_model = sequencer, design_description = "10x Chromium 3' Single Cell v3, processed with Cell Ranger 7.x", filetype = "fastq", filename = basename(fastq_R1), filename2 = basename(fastq_R2) ) write_tsv(sra, "../data/submission_sra.tsv") sra # --- Table out: SRA metadata upload template ------------------------------- readr::write_csv(sra, file.path(out_dir, "Mod17_C8_sra_template.csv")) ``` ### 7c. GEO sample sheet (processed data) GEO's `seq_template_v2.1.xlsx` has a sample-section row per biological sample plus a processed-data row per output file. We'll generate the sample-section rows: ```{r} #| label: M17-geo_tsv geo_samples <- canonical |> transmute( `Sample name` = sample_id, title = sample_label_for_publication, `source name` = tissue, organism, `characteristics: donor_id` = donor_id, `characteristics: tissue` = tissue, `characteristics: disease` = disease, `characteristics: treatment` = treatment, `characteristics: time_point` = time_point, `characteristics: sex` = sex, molecule = "polyA RNA", `single or paired-end` = "paired-end", instrument_model = sequencer, description = paste("10x Chromium 3' Single Cell v3 of PBMCs.", treatment), `processed data file` = paste0(sample_id, "_filtered_feature_bc_matrix.h5"), `raw file` = paste0(basename(fastq_R1), ", ", basename(fastq_R2)) ) write_tsv(geo_samples, "../data/submission_geo_samples.tsv") geo_samples # --- Table out: GEO sample-section upload template ------------------------- readr::write_csv(geo_samples, file.path(out_dir, "Mod17_C9_geo_samples_template.csv")) ``` ::: {.callout-important title="Think about it"} 1. Notice that BioSample, SRA, and GEO TSVs all share `sample_id` as the join key. What goes wrong if a sample's name is "donor1_CTRL" in the BioSample sheet but "Donor1_CTRL" in the GEO sheet? 2. Why generate three sheets from the canonical sheet rather than three separately?
Show answers 1. NCBI's accession-linking is **case-sensitive** and **literal**. The two sheets won't link, the BioSample accession won't appear on the GEO record, and a downstream user querying SRA from a GEO record will hit a dead link. Always derive all sheets programmatically from one source of truth. 2. Same source = same names = automatic links. Manual transcription accumulates errors that compound across submissions; the more granular the metadata (cell type ontology IDs, ancestry codes), the worse manual transcription gets.
::: ## Step 8 — Write the manuscript Data Availability section Generate a draft from the metadata so it's ready to drop into the methods: ```{r} #| label: M17-data_availability cat(glue::glue( "## Data Availability Raw sequencing reads have been deposited in the Sequence Read Archive (SRA) under BioProject accession PRJNA. Processed gene-expression matrices and per-cell metadata are available at the Gene Expression Omnibus (GEO) under accession GSE. An annotated AnnData object conforming to the CELLxGENE Discover schema (v5) is available at https://cellxgene.cziscience.com under dataset slug . Analysis code and the canonical sample sheet are available at . The dataset comprises {n_samples} samples from {n_donors} donors, totalling {n_cells} cells across two conditions (CTRL, IFN-β-stimulated). All samples were processed with 10x Chromium 3' Single Cell v3 chemistry on {sequencer}.", n_samples = nrow(canonical), n_donors = n_distinct(canonical$donor_id), n_cells = ncol(ifnb), sequencer = unique(canonical$sequencer) )) ``` ## Wrap-up You now have: - A **canonical sample sheet** (versionable, single-source-of-truth) — `Resources_Folder/metadata_templates/canonical_sample_sheet.csv` - A **CELLxGENE-schema-conformant** Seurat object with all per-cell ontology fields - An **AnnData (`.h5ad`)** ready for CELLxGENE Discover - Three NCBI **upload TSVs** for BioSample, SRA, and GEO derived from the same canonical sheet - A draft **Data Availability** statement For your own work, replace the canonical sheet at experimental design time, then re-run this entire tutorial against your own annotated `.rds`. The ontology mapping in Step 3 is the part that takes the most thought — keep your `cl_map` table in version control and update it as your study expands. ::: {.callout-tip title="Going further"} - **Automate the validation.** Add `cellxgene-schema validate` to your project's `Makefile` or CI so every metadata change is checked. - **Use a controlled workflow.** Tools like [`hcatools`](https://github.com/HumanCellAtlas/hca-data-wrangling) and [`scbase`](https://github.com/scverse) automate parts of this for HCA submissions. - **For controlled-access human genomic data**, the same canonical sheet feeds **dbGaP** (US) or **EGA** (Europe) submissions; only the data-deposit endpoint changes, the metadata structure is the same. ::: ## See also - [Lecture 17 — FAIR Principles & Data Sharing](../Lecture_Folder/Lecture_17_FAIR_DataSharing.html) - [scNotebooks Module 13](https://integrativebioinformatics.github.io/scNotebooks/modules/en/module13/module13.html) — covers the same workflow with extra detail on ArrayExpress + HCA Data Portal - [CELLxGENE Discover schema documentation](https://github.com/chanzuckerberg/single-cell-curation/blob/main/schema/5.0.0/schema.md) - [Wilkinson et al. 2016, *Sci Data* — the original FAIR paper](https://doi.org/10.1038/sdata.2016.18) - [OLS — EBI's Ontology Lookup Service](https://www.ebi.ac.uk/ols4/) — find ontology IDs for any term - [Resources_Folder/metadata_templates/](../Resources_Folder/metadata_templates/) — copy-and-edit metadata templates