--- output: html_document runtime: shiny_prerendered author: Environmental Cheminformatics Group, LCSB, University of Luxembourg title: "`r paste('Shinyscreen', packageVersion('shinyscreen'))`" --- ```{r, context='setup', include='false'} def_state <- new_state() def_datafiles <- shinyscreen:::dtable(File=character(0), tag=character(0)) def_datatab <- shinyscreen:::dtable("tag"=factor(), "adduct"=factor(levels=shinyscreen:::DISP_ADDUCTS), "set"=factor()) ## def_state$input$tab$tags <- def_datatab rv_state <- list2rev(def_state) compl_sets <- eventReactive(rv_state$input$tab$setid, rv_state$input$tab$setid[,unique(set)]) ## Reactive values to support some of the UI elements. ## rv_ui <- reactiveValues(datatab=def_tags) ``` # Configuration {.tabset} ## Inputs <details> <summary>Specify the project directory</summary> This is where the output files and the state of the analysis will be saved. </details> ```{r, echo=FALSE} actionButton(inputId = "project_b", label= "Project") ``` Current project directory is `r textOutput("project", inline=T)` Load the config file if needed. ```{r, echo=FALSE} actionButton(inputId = "conf_file_b", label= "Load config") ``` <details><summary>Load the compound list(s)</summary> A compound list is composed of entries describing compounds. This description is used to search for its spectrum in the data file. The list is a table in the ***CSV*** format and contains these columns, * ***ID*** : required column, must be filled; this is a user-defined ID, uniquely associated with a compound * ***Name*** : this column can be left blank; if not, it should contain the names of the compounds * ***SMILES*** : a _SMILES_ string, describing the structure of the compound; this entry can be left empty only if one of either ***Formula***, or ***mz*** entries are not * ***Formula*** : a chemical formula of a compound; this field can be empty only if one of either ***SMILES***, or ***mz*** entries are not * ***mz*** : mass of the ionised compound; this field can be left empty only if one of either ***SMILES***, or ***Formula*** is not * ***CAS*** : the CAS number of the compound; it can be left empty * ***RT*** : retention time of the MS1 peak in minutes, if known; can be left empty. Only ***ID*** and one of ***SMILES***, ***Formula*** or ***mz*** must be filled. When structure, or a formula of a compound is known, it is also possible to look for various adducts in the sample. Of course, scanning for completely unknown compounds is also supported by the ***mz*** column. In this case, ***mz*** is the mass of the ion. It is strongly recommended to quote SMILES, names and formulas in the CSV file used with Shinyscreen. </details> ```{r, echo=FALSE} actionButton(inputId = "comp_list_b", label= "Compound list(s)") ``` `r htmlOutput("comp_lists")` <details><summary>Load compound set list (_setid_ table)</summary> The compound lists can contain more entries than is necessary. Using the _setid_ lists, it is possible to create _compound sets_ which contain only those compounds that will actually be searched for in the data files. A _setid table_ is a _CSV_ containing at least two columns, * ***ID*** : the ID entry from the compound list * ***set*** : an user-defined set name. </details> ```{r, echo=FALSE} actionButton(inputId = "setid_b", label= "Load the setid table") ``` `r htmlOutput("setids", inline=T)` ## Data files <details><summary>Load data files</summary> Shinyscreen currently supports only the **mzML** file format. After loading the files, set file tags in the file table (column **tag**). Additionally, specify a set of compounds that is supposed to be extracted from the file using the **set** column. Finally, specify the **adduct** in the adduct column. In case of compounds with unknown structure and formula, the adduct is ignored for obvious reasons. </details> ```{r, echo=FALSE} actionButton(inputId = "datafiles_b", label= "Load data files.") ``` <details><summary>Assign tags to data files.</summary> Each tag designates an unique file. Use the table below to assign tags. </details> ```{r, echo=FALSE} rhandsontable::rHandsontableOutput("datafiles") ``` <details><summary>Assign sets to tags.</summary> For each tag, assign a set and an adduct (if the structure information exists, otherwise _adduct_ column is ignored). </details> ```{r, echo=F} rhandsontable::rHandsontableOutput("datatab") ``` ## Extraction ### Spectra extraction based settings <details><summary>MS1 coarse error</summary> Extract all entries matching the target mass within this error in the precursor table. </details> ```{r, echo=F} shinyscreen::mz_input(input_mz = "ms1_coarse", input_unit = "ms1_coarse_unit", def_mz = def_state$conf$tolerance[["ms1 coarse"]], def_unit = "Da") ``` <details><summary>MS1 fine error</summary> The precursor table masses can be of lower accuracy. Once there is a match within the coarse error, it can be further checked versus the fine error bounds directly in the mass spectrum. </details> ```{r, echo=F} shinyscreen::mz_input(input_mz = "ms1_fine", input_unit = "ms1_fine_unit", def_mz = def_state$conf$tolerance[["ms1 fine"]], def_unit = "ppm") ``` <details><summary>MS1 EIC window</summary> The mz interval over which the intensities are aggregated to generate a chromatogram. </details> ```{r, echo=F} shinyscreen::mz_input(input_mz = "ms1_eic", input_unit = "ms1_eic_unit", def_mz = def_state$conf$tolerance[["eic"]], def_unit = "Da") ``` <details><summary>Retention time window</summary> If the expected retention time has been specified for the compound, then search for the MS1 signature inside the window defined by this range. </details> ```{r, echo=F} shinyscreen::rt_input(input_rt = "ms1_rt_win", input_unit = "ms1_rt_win_unit", def_rt = def_state$conf$tolerance[["rt"]], def_unit = "min") ``` ## Prescreening <details><summary>MS1 intensity threshold</summary> Ignore MS1 signal below the threshold. </details> ```{r, echo=F} numericInput(inputId = "ms1_int_thresh", label = NULL, value = def_state$conf$prescreen$ms1_int_thresh) ``` <details><summary>MS2 intensity threshold</summary> Ignore MS2 signal below the threshold. </details> ```{r, echo=F} numericInput(inputId = "ms2_int_thresh", label = NULL, value = def_state$conf$prescreen$ms2_int_thresh) ``` MS1 signal-to-noise ratio. ```{r, echo=F} numericInput(inputId = "s2n", label = NULL, value = def_state$conf$prescreen$s2n) ``` <details><summary>MS1/MS2 retention delay.</summary> Look for associated MS2 spectrum within this window around the MS1 peak. </details> ```{r, echo=F} shinyscreen::rt_input(input_rt = "ret_time_shift_tol", input_unit = "ret_time_shift_tol_unit", def_rt = def_state$conf$tolerance[["ret_time_shift_tol"]], def_unit = "min") ``` ## Filter and order the summary table <div style= "display: flex; vertical-align:top; "> <div> <details><summary>Filter summary table</summary> Selection criteria for filtering entries in the summary table according to the QA criteria. * **qa_pass** : entries that passed all checks * **qa_ms1_exists** : MS1 intensity is above the MS1 threshold * **qa_ms2_exists** : those entries for which some MS2 spectra have been found * **qa_ms1_above_noise** : MS1 is intense enough and above the noise level * **qa_ms2_good_int** : MS2 intensity is above the MS2 threshold * **qa_ms2_near** : MS2 spectrum is close enough to the MS1 peak </details> ```{r, echo=F} checkboxGroupInput("summ_subset", label=NULL, choiceNames = shinyscreen:::QA_FLAGS, choiceValues = shinyscreen:::QA_FLAGS) ``` </div> <div> <details><summary>Ordering by columns</summary> It is possible to order the summary table using columns (keys): *`r paste(gsub("^-(.+)","\\1",shinyscreen:::DEF_INDEX_SUMM), collapse = ',')`*. The sequence of columns in the table below describes the sequence of ordering steps -- the key in the first row sorts the entire summary table and subsequent keys break the ties. </details> ```{r, echo=F} rhandsontable::rHandsontableOutput("order_summ") ``` </div> </div> <!-- <details><summary>Order entries</summary> --> <!-- Sequence of column a --> <!-- </details> --> <!-- ```{r, echo=F} --> <!-- checkboxGroupInput("summ_subset", --> <!-- label=NULL, --> <!-- choiceNames = shinyscreen:::QA_FLAGS, --> <!-- choiceValues = 1:length(shinyscreen:::QA_FLAGS)) --> <!-- ``` --> ## Plots ### Logarithmic axis ```{r, echo=F} checkboxGroupInput("plot_log", label=NULL, choiceNames = c("MS1 EIC","MS2 EIC","MS2 Spectrum"), choiceValues = c(F,F,F)) ``` ### Global retention time range ```{r, echo=F} shinyscreen::rt_input(input_rt = "plot_rt_min", input_unit = "plot_rt_min_unit", def_rt = NA_real_, def_unit = "min", pref = "min:") shinyscreen::rt_input(input_rt = "plot_rt_max", input_unit = "plot_rt_max_unit", def_rt = NA_real_, def_unit = "min", pref = "max:") ``` ## Report ```{r, echo=F} shiny::textInput(inputId = "rep_aut", label = "Report author", value = def_state$conf$report$author) shiny::textInput(inputId = "rep_tit", label = "Report title", value = def_state$conf$report$title) ``` # View compound Lists and Sets {.tabset} ## Compound List ```{r, echo=F} DT::dataTableOutput("comp_table") ``` ## Setid Table ```{r, echo=F} DT::dataTableOutput("setid_table") ``` # Extract Data and Prescreen <details><summary>Extract spectra from data files.</summary> After Shinyscreen is configured, the compound and setid lists loaded, it is possible to proceed with extracting the data. This is potentially a time-intensive step, so some patience might be needed. Once the data is extracted, it will be possible to quality check the spectra associated with the compounds specified in the _setid_ list, to subset that data, look at the plots and publish a report. </details> ```{r, echo=FALSE} actionButton(inputId = "extract", label = "Extract") ``` # Browse Results <!-- Setup is here --> ```{r, include="false", context='setup'} ord_nms <- gsub("^-(.+)","\\1",shinyscreen:::DEF_INDEX_SUMM) ord_asc <- grepl("^-.+",shinyscreen:::DEF_INDEX_SUMM) ord_asc <- factor(ifelse(ord_asc, "descending", "ascending"),levels = c("ascending","descending")) def_ord_summ <- shinyscreen:::dtable("Column names"=ord_nms,"Direction"=ord_asc) ``` ```{r, include="false", context='server'} ## reactive functions rf_compound_input_state <- reactive({ sets <- rv_state$conf$compounds$sets lst <- as.list(rv_state$conf$compounds$lists) validate(need(length(lst)>0, message = "Load the compound lists(s) first.")) validate(need(nchar(sets)>0, message = "Load the setid table first.")) isolate({ state <- rev2list(rv_state) m <- load_compound_input(state) ## Side effect! This is because my pipeline logic does not ## work nicely with reactive stuff. rv_state$input$tab$cmpds <- list2rev(m$input$tab$cmpds) rv_state$input$tab$setid <- m$input$tab$setid m }) }) rf_get_dfiles <- reactive({ input$datafiles_b if (input$datafiles_b > 0) { filters <- matrix(c("mzML files", ".mzML", "All files", "*"), 2, 2, byrow = TRUE) mzMLs <- tcltk::tk_choose.files(filters=filters) message("(config) Selected data files: ", paste(mzMLs,collapse = ",")) mzMLs } else character(0) }) rf_dfiles_tab <- reactive({ mzMLs <- rf_get_dfiles() isolate({oldtab <- data.table::as.data.table(rhandsontable::hot_to_r(input$datafiles))}) newf <- setdiff(mzMLs,oldtab$File) nr <- NROW(oldtab) tmp <- if (length(newf)>0) shinyscreen:::dtable(File=newf,tag=paste0('F',(nr+1):(nr + length(newf)))) else shinyscreen:::dtable(File=character(),tag=character()) rbind(oldtab, tmp) }) rf_tag_tab <- reactive({ state <- rf_compound_input_state() isolate({oldtab <- rhandsontable::hot_to_r(input$datatab)}) oldt <- oldtab$tag sets <- compl_sets() sets <- if (length(sets)==1) sets <- c(sets,"invalid") #Just #because #when one #level, #rhandsontable #has issues #displaying #it. otagch <- as.character(oldt) df_tab <- rhandsontable::hot_to_r(input$datafiles) tagl <- df_tab$tag diff <- setdiff(tagl, otagch) if (length(diff)!=0) { ## Only change the tag names in the old ones. pos_tag <- 1:length(tagl) pos_old <- 1:NROW(oldtab) pos_mod <- intersect(pos_tag,pos_old) new_tag <- tagl[pos_mod] if (NROW(oldtab)>0) oldtab[pos_mod,tag := ..new_tag] ## Now add tags for completely new files, if any. rest_new <- if (NROW(oldtab) > 0) setdiff(diff,new_tag) else diff tmp <- shinyscreen:::dtable(tag=factor(rest_new,levels=tagl), adduct=factor(levels = shinyscreen:::DISP_ADDUCTS), set=factor(levels = sets)) dt <-data.table::as.data.table(rbind(as.data.frame(oldtab), as.data.frame(tmp))) dt[tag %in% df_tab$tag,] } else oldtab }) ``` ```{r, include="false", context='server'} observeEvent(input$project_b,{ wd <- tcltk::tk_choose.dir(default = getwd(), caption = "Choose project directory") message("Set project dir to ", wd) rv_state$conf$project <- wd }) observeEvent(input$comp_list_b, { filters <- matrix(c("CSV files", ".csv", "All files", "*"), 2, 2, byrow = TRUE) compfiles <- tcltk::tk_choose.files(filters=filters) message("(config) Selected compound lists: ", paste(compfiles,collapse = ",")) rv_state$conf$compounds$lists <- if (length(compfiles)>0 && nchar(compfiles[[1]])>0) compfiles else "Nothing selected." }) observeEvent(input$setid_b, { filters <- matrix(c("CSV files", ".csv", "All files", "*"), 2, 2, byrow = TRUE) setids <- tcltk::tk_choose.files(filters=filters) message("(config) Selected compound sets (setid): ", paste(setids,collapse = ",")) rv_state$conf$compounds$sets <- if (length(setids)>0 && nchar(setids[[1]])>0) setids else "Nothing selected." }) observeEvent(input$extract,{ tmp <- rev2list(rv_state) fn_c_state <- file.path(tmp$conf$project, shinyscreen:::FN_CONF) yaml::write_yaml(x=tmp$conf,file=fn_c_state) message("(extract) Config written to ", fn_c_state) }) ``` <!-- Render --> ```{r, include="false", context="server"} output$project <- renderText(rv_state$conf$project) output$comp_lists <- renderText({ lsts <- rev2list(rv_state$conf$compounds$lists) if (length(lsts) > 0 && isTruthy(lsts) && lsts != "Nothing selected.") { paste(c("<ul>", sapply(lsts, function (x) paste("<li>",x,"</li>")), "</ul>")) } else "No compound list selected yet." }) output$setids <- renderText({ sets <- rv_state$conf$compounds$sets if (isTruthy(sets) && sets != "Nothing selected.") paste("selected <em>setid</em> table:", sets) else "No <em>setid</em> table selected." }) output$order_summ <- rhandsontable::renderRHandsontable(rhandsontable::rhandsontable(def_ord_summ, manualRowMove = T)) output$datafiles <- rhandsontable::renderRHandsontable( { res <- if (length(rf_get_dfiles())>0) { rf_dfiles_tab() } else def_datafiles rhandsontable::rhandsontable(as.data.frame(res), width = "50%", height = "25%", allowInvalid=F) }) output$datatab <- rhandsontable::renderRHandsontable( { df <- rhandsontable::hot_to_r(input$datafiles) res <- if (NROW(rv_state$input$tab$setid) > 0 && NROW(df) > 0) rf_tag_tab() else def_datatab rhandsontable::rhandsontable(res,stretchH="all", allowInvalid=F) }) output$comp_table <- DT::renderDataTable({ state <- rf_compound_input_state() DT::datatable(state$input$tab$cmpds, style = 'bootstrap', class = 'table-condensed', extensions = 'Scroller', options = list(scrollX = T, scrollY = 200, deferRender = T, scroller = T)) }) output$setid_table <- DT::renderDataTable({ state <- rf_compound_input_state() DT::datatable(state$input$tab$setid, style = 'bootstrap', class = 'table-condensed', extensions = 'Scroller', options = list(scrollX = T, scrollY = 200, deferRender = T, scroller = T)) }) ``` ```{r, echo=F, context = 'server'} session$onSessionEnded(function () stopApp()) ```