Skip to content
Snippets Groups Projects
api.R 29.3 KiB
Newer Older
Todor Kondić's avatar
Todor Kondić committed
## Copyright (C) 2020,2021,2023 by University of Luxembourg
Todor Kondic's avatar
Todor Kondic committed

## Licensed under the Apache License, Version 2.0 (the "License");
## you may not use this file except in compliance with the License.
## You may obtain a copy of the License at

##     http://www.apache.org/licenses/LICENSE-2.0

## Unless required by applicable law or agreed to in writing, software
## distributed under the License is distributed on an "AS IS" BASIS,
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
## See the License for the specific language governing permissions and
## limitations under the License.
Todor Kondić's avatar
Todor Kondić committed
#' @title Run Shinyscreen Pipeline
#'
#' @details TODO.
#' 
#' @param envopts `envopts`, an `envopts` object.
#' @param project `character(1)`, a directory containing input data.
#' @param m `state`, a Shinyscreen state.
#' @param phases `character(n)`, a character vector of Shinyscreen
#'     phases.
#' @param help `logical(1)`, print help?
#' @export
run <- function(envopts,
                project="",
                m=NULL,
                phases=NULL,
                help=F) {
Todor Kondić's avatar
Todor Kondić committed

                        
    
    all_phases=list(setup=setup_phase,
                    comptab=mk_comp_tab,
                    extract=extr_data,
                    prescreen=prescreen,
                    sort=sort_spectra,
                    subset=subset_summary,
                    plot=create_plots)

    if (help) {
        message("(run): You can run some of the following, or all the phases:")
        message(paste(paste0("(run): ",names(all_phases)),collapse = "\n"))
        return(invisible(NULL))
    }
    the_phases <- if (is.null(phases)) all_phases else {
                                                      x <- setdiff(phases,names(all_phases))
                                                      if (length(x)>0) {
                                                          message("(run): Error. Unknown phases:")
                                                          message(paste(paste0("(run): ",x),collapse = "\n"))
                                                          stop("Aborting.")
                                                      }
                                                      all_phases[phases]
Todor Kondić's avatar
Todor Kondić committed

Todor Kondić's avatar
Todor Kondić committed

    m = if (is.null(m)) {
            ## A project from scratch.
            new_project(project,envopts=envopts) 
        } else {
            ## Regenerate the runtime environment.

            project = if (nchar(project)==0L) m$run$project else project
            m$run = new_runtime_state(project=project,
                                      envopts = envopts,
                                      conf=m$conf)
            m
        }

    m = withr::with_dir(new=m$run$paths$project,code = Reduce(function (prev,f) f(prev),
                                                              x = the_phases,
                                                              init = m))
Todor Kondic's avatar
Todor Kondic committed
    return(invisible(m))
Todor Kondic's avatar
Todor Kondic committed

Todor Kondic's avatar
Todor Kondic committed
##' @export
setup_phase <- function(m) {
Todor Kondic's avatar
Todor Kondic committed
    m <- mk_tol_funcs(m)
Todor Kondic's avatar
Todor Kondic committed
    m <- load_inputs(m)
Todor Kondic's avatar
Todor Kondic committed
    m <- concurrency(m)
##' @export
run_in_dir <- function(m) {
    m <- setup_phase(m)
Todor Kondic's avatar
Todor Kondic committed
    m <- mk_comp_tab(m)
Todor Kondic's avatar
Todor Kondic committed
    m <- extr_data(m)
    m <- sort_spectra(m)
    m <- subset_summary(m)
    m <- create_plots(m)
Todor Kondic's avatar
Todor Kondic committed
    invisible(m)
load_compound_input <- function(m) {
    fns <- file.path(m$run$paths$project,m$conf$compounds$lists)
    cmpds = join_compound_lists(fns)
    ## Process sets.
    cmpds = process_cmpd_sets(cmpds)
    
    dups <- duplicated(cmpds$ID)
    dups <- dups | duplicated(cmpds$ID,fromLast = T)
    dupIDs <- cmpds$ID[dups]
    dupfns <- cmpds$ORIG[dups]

    msg <- ""
    for (fn in unique(dupfns)) {
        inds <- which(dupfns %in% fn)
        fndupID <- paste(dupIDs[inds], collapse = ',')
        msg <- paste(paste('Duplicate IDs', fndupID,'found in',fn),msg,sep = '\n')
    }

    ## TODO: Should we just kick out the duplicates, instead of
    ## erroring?

    cmpds[,("known"):=.(the_ifelse(!is.na(SMILES),"structure",the_ifelse(!is.na(Formula),"formula","mz")))]
    m$input$tab$cmpds <- cmpds
Todor Kondic's avatar
Todor Kondic committed
##' @export
load_data_input <- function(m) {
Todor Kondic's avatar
Todor Kondic committed
    if (NROW(m$input$tab$mzml)==0L) {
        if (!file.exists(m$run$paths$datatab)) {
            stop("A CSV file with data file entries does not exist (`paths$datatab' in config).")
        }
        m$input$tab$mzml <- file2tab(m$run$paths$datatab)
    } else {
        message("Table `datatab' already loaded.")
    }
    m$input$tab$mzml <- as.data.table(m$input$tab$mzml)
    assert(all(unique(m$input$tab$mzml[,.N,by=c("adduct","tag")]$N)<=1),msg="Some rows in the data table contain multiple entries with same tag and adduct fields.")
Todor Kondic's avatar
Todor Kondic committed
    pref<-m$run$paths$data
    for (fn in m$input$tab$mzml$file) {
        if (!file.exists(file.path(pref,fn))) stop("File ",fn," does not exist.")
    }
    ## m$input$tab$mzml[,file:=fifelse(file.exists(file),file,file.path(..pref,file))]
Todor Kondic's avatar
Todor Kondic committed
    ## m$input$tab$mzml[,file:=norm_path(file)]
}

##' @export
load_inputs <- function(m) {
Todor Kondic's avatar
Todor Kondic committed
    m <- load_compound_input(m)
    m <- load_data_input(m)
    m
}

##' @export
Todor Kondic's avatar
Todor Kondic committed
mk_comp_tab <- function(m) {
Todor Kondic's avatar
Todor Kondic committed
    mzml<- m$input$tab$mzml
    setkey(mzml,set)
    cmpds<-m$input$tab$cmpds
    setkey(cmpds,set,ID)
    assert(nrow(cmpds)>0,msg="No compound lists have been provided.")
    assert(all(mzml[,unique(set)] %in% cmpds[,unique(set)]),msg="Not all set names in the `datatab' data file table match those in the provided set list.")
    assert(all(mzml[,!is.na(unique(adduct))]),msg="Some data file entries do not have selected adducts.")
    message("Begin generation of the comprehensive table.")
    comp <- cmpds[mzml,.(tag,adduct,ID,RT,set,Name,file,SMILES,Formula,mz,known),on="set",allow.cartesian=T]
    tab2file(tab=comp,file=paste0("setidmerge",".csv"))
    setkey(comp,known,set,ID)
    message("Merged all sets.")
    message("Calculate formulas from SMILES (if any). Please wait.")
    ## Get just the info needed for mz calculation.
    comp_known <- comp[known=="structure" | known=="formula"]
    ## Remove mz==NA  col from knowns.
    comp_known[,mz:=NULL]
    comp_unknown <- comp[known=="mz"]
    smiforadd <- comp_known[known=="structure" | known=="formula",unique(.SD),.SDcols=c("adduct","ID","SMILES","Formula")]
    ## Turn SMILES into formulas.
    smiles <- smiforadd[,unique(.SD),.SDcols=c("SMILES")]
    smiles[,`:=`(Formula=smiles2form(SMILES))]
    badsmiles <- as.character(smiles[Formula=="",SMILES])
    if (length(badsmiles)>0) {
        stop("Unable to create formula from SMILES:",paste(badsmiles,collapse="\n"))
    }
    smiforadd <- smiles[smiforadd,.(ID,SMILES,Formula,adduct),on=c("SMILES")]
    data.table::setkey(smiforadd,"adduct","ID")

    smiforadd[,Formula:=as.character(Formula)]
    
    ## Update the intermediate table with masses.
    message("Formulas have been calculated. Start calculating masses from formulas.")
    smiforadd[,mz:=calc_mz_from_formula(Formula,adduct,ID)]
    message("Mass calculation has been completed.")

    ## Update the whole comprehensive table with masses from
    ## formulas. Doing it in a merge leaves a mess that has to be
    ## cleaned.
    comp2 <- merge(comp_known,smiforadd,all.x = T, by= c("adduct","ID"))
    ## Take Formulas from smiforadd (y) and SMILES from comp (x).
    comp2[,`:=`(Formula=Formula.y,SMILES=SMILES.x)]
    ## Now, populate mz from smiforadd (y) if SMILES/formula known,
    ## else take what was in the comp (x).
    ## comp2[,mz:=fifelse(known=="structure" | known=="formula",mz.y,mz.x)]
    nms <- names(comp)
    comp_known<-comp2[,..nms]
    ## In case you were wondering why is this all so complicated,
    ## well, for the moment I do not want to exclude mixed knowns and
    ## unknowns in the same run. The unknowns would have masses filled
    ## already at the stage of the compound list, so thay are taken
    ## from comp_unknown. Another twist is that mz can be calculated
    ## from either SMILES, or Formula.
    
    ## Combine knowns and unknowns finally.
    comp <- rbind(comp_known,comp_unknown)
    ## Rename stuff to be renamed and reorder columns.
    setnames(comp,names(COMP_NAME_MAP),
Todor Kondic's avatar
Todor Kondic committed
             function(o) COMP_NAME_MAP[[o]])
    setcolorder(comp,COMP_NAME_FIRST)
    fn_out <- get_fn_comp(m)
    tab2file(tab=comp,file=fn_out)
    message("Generation of comprehensive table finished.")

    ## Index for fast search and access.
    setkeyv(comp,c("set","tag","mz"))
    m$out$tab$comp <- comp

    ## TODO: Not tested on cases when there are both knowns and
    ## unknowns present in the compound lists. It *should* work
    ## though.
Todor Kondic's avatar
Todor Kondic committed
#' @export
concurrency <- function(m) {
Todor Kondic's avatar
Todor Kondic committed
    ## Reads the concurrency entry in the config. It is optional, if
    ## not given, then it is up to the user to define the plan of the
    ## futures package. If present, it contains at least the `plan'
    ## specification. It can also contain `workers` entry specifying
    ## the number of workers. If that entry is absent, the default
    ## number of workers is NO_WORKERS from the resources.R.

    ## TODO: Needs a rework to be useful. But, this is not a problem,
    ## because the user controls concurrency settings from the outside
    ## using future::plan.
    
    ## workers <- m$conf$concurrency$workers
    ## plan <- m$conf$concurrency$plan
    ## if (!is.null(plan) && plan!=user) {
    ##     n <- if (!is.null(workers)) workers else NO_WORKERS
    ##     if (!is.na(n)) future::plan(plan,workers=workers) else future::plan(plan)
    ##     m$conf$concurrency$workers <- n

    ## } else {
    ##     m$conf$concurrency$workers <- NA
    ##     m$conf$concurrency$plan <- "user"
    ## }
    ## message("plan: ",m$conf$concurrency$plan)
    ## message("workers: ",m$conf$concurrency$workers)
Todor Kondic's avatar
Todor Kondic committed

    ## So we can actually debug.
    m$future <- if (is.null(m$conf$debug) || !m$conf$debug)
Todor Kondic's avatar
Todor Kondic committed
                    future::future
                else {
                    message("Debug: futures evaluate as identity")
                    function(x,...) identity(x)
                }
Todor Kondic's avatar
Todor Kondic committed
#' @export
mk_tol_funcs <- function(m) {
Todor Kondic's avatar
Todor Kondic committed
    ## Depending on units given when the user specified the errors,
    ## generate functions that calculate errors given the concrete
    ## mass.

    ## Mass errors can be either in ppm, or Da.
    ## Time errors in min, or s.

    ## The mass error calculation functions and the retention time
    ## error in minutes are in m$extr$tol.
    ## TODO make these things compatible with futures.

    m$extr$tol$coarse <- gen_mz_err_f(m$conf$tolerance[["ms1 coarse"]],
                                      "ms1 coarse error: Only ppm, or Da units allowed."
                                      )
    m$extr$tol$fine <- gen_mz_err_f(m$conf$tolerance[["ms1 fine"]],
                                    "ms1 fine error: Only ppm, or Da units allowed.")

    m$extr$tol$eic <- gen_mz_err_f(m$conf$tolerance$eic,
                                   "eic error: Only ppm, or Da units allowed.")

    m$extr$tol$rt <- gen_rt_err(m$conf$tolerance$rt,
                                "rt error: Only s(econds), or min(utes) allowed.")
##' @export
extr_data <-function(m) {

    fine = create_fine_table(m)

    dpath = m$run$paths$data

    ## Open all files.
    fns = fine[,unique(file)]
    lms = lapply(fns,function(fn) read_data_file(file.path(dpath,fn)))
    names(lms) = fns

    ## Load all feature data.
    lfdata = lapply(lms,get_fdata)
    names(lfdata) = fns

    ## Extract MS1 chromatograms using "fine" tolerance.
    cgram_ms1 = fine[,extr_cgrams_ms1(lms[[file]],.SD,lfdata[[file]]),
                     by="file",
                     .SDcols=c("iso_fine_min",
                               "iso_fine_max",
                               "rt_min",
                               "rt_max",
                               "precid")]
    setkey(cgram_ms1,file,precid)

    ## Extract MS2 chromatograms.

    ## Create the "coarse" table. Parent masses are known with
    ## "coarse". We will prefilter our ms2 results based on that...
    coarse = create_coarse_table(m)

    
    cgram_ms2 = data.table(precid=integer(0),
                           ce=numeric(0),
                           scan=character(0),
                           idx=integer(0),
                           rt=numeric(0),
                           intensity=numeric(0))
    spectra = empty_spectra_table()
    for (fn in names(lfdata)) {
        rtab = relate_ms2_to_precid(coarse=coarse[.(fn),on=.(file)],
                                    ms2=lfdata[[fn]]$ms2,
                                    cgram_ms1=cgram_ms1[.(fn),
                                                        on=.(file)])
        sptab = extract_spectra(lms[[fn]],rtab)
        spectra = rbind(spectra,sptab)
    setkey(cgram_ms1,precid,rt)
    setkey(cgram_ms2,precid,ce,rt)
    setkey(spectra,precid,scan)
    m$db$extr$cgm$ms1 = cgram_ms1
    m$db$extr$cgm$ms2 = cgram_ms2
    m$db$extr$spectra = spectra
##' @export
conf_trans <- function(conf) {
    conf$prescreen <- conf_trans_pres(conf$prescreen)
    conf
}

##' @export
prescreen <- function(m) {
    ## Top-level auto prescreening function.
Todor Kondic's avatar
Todor Kondic committed
    message("(prescreen): Start.")
    m$qa = NULL
    m$out$tab$summ = NULL
    m$qa = analyse_extracted_data(m$db,m$conf$prescreen)
    m$out$tab$summ = gen_summ(m$db,m$qa,m$out$tab$comp)
Todor Kondic's avatar
Todor Kondic committed
    message("(prescreen): End.")
##' Sets the key specified by DEF_KEY_SUMM and adds second indices,
##' either from DEF_INDEX_SUMM, or user-specified in
##' conf[["summary table"]]$order. The order entry is a list of
##' strings with names of columns in summ, optionally prefixed with a
##' minus(-) sign. Columns prefixed with the minus are going to be in
##' ascending order.
##'
##' @title Sort the Summary Table 
##' @param m 
##' @return m
##' @author Todor Kondić
Todor Kondic's avatar
Todor Kondic committed
##' @export
sort_spectra <- function(m) {
    ## Sorts the summary table (summ) in order specified either in
Todor Kondic's avatar
Todor Kondic committed
    ## `order spectra` sublist of m$conf, or if that is null, the
    ## DEF_INDEX_SUMM.

    ## Now, add secondary indexing.
    cols <- if (!is.null(m$conf[["summary table"]]$order)) m$conf[["summary table"]]$order else DEF_INDEX_SUMM
    
    idx <- gsub("^\\s*-\\s*","",cols) #We need only column names for
                                        #now, so remove minuses where
                                        #needed.
    assertthat::assert_that(all(idx %in% colnames(m$out$tab$summ)),msg = "Some column(s) in order key in conf file does not exist in the summary table.")

    data.table::setindexv(m$out$tab$summ,idx)

    ## Now we order based on either summary table order subkey, or
    ## DEF_ORDER_SUMM
    
    tmp <- quote(data.table::setorder())
    tmp[[2]] <- quote(m$out$tab$summ)
    for (n in 1:length(cols)) tmp[[2+n]] <- parse(text=cols[[n]])[[1]]
    message("Ordering expression: \n",deparse(tmp))
    eval(tmp) #Execute the setorder call
    m
}

##' Subsets the summary table by applying conditions set out in the
##' filter subkey of summary table key of the config. Each member of
##' filter is an expression that and all of them are chained together
##' using AND logical operation and applied to the summary table.
##'
##' 
##' @title Subset the Summary Table
##' @param m 
##' @return m
##' @author Todor Kondić
##' @export
subset_summary <- function(m) {
    filt <- m$conf[["summary table"]]$filter
    m$out$tab$flt_summ <- if (!is.null(filt)) {
                              tmp <- lapply(filt, function (x) parse(text = x)[[1]])
                              expr <- Reduce(function (x,y) {z<-call("&");z[[2]]<-x;z[[3]]<-y;z},x=tmp)
                              message("Filtering with: ",deparse(bquote(m$out$tab$summ[.(expr)])))
                              eval(bquote(m$out$tab$summ[.(expr)]))
                              
                              
                          } else m$out$tab$summ

#' @export
create_plots <- function(m) {
    ## Produce plots of EICs and spectra and group them acording to
    ## conf$figures$grouping.

    ## Select the data nedeed for plotting.
    ms2 <- m$db$extr$cgm$ms2
    ms1 <- m$db$extr$cgm$ms1
Todor Kondic's avatar
Todor Kondic committed
    rt_min <- rt_in_min(m$conf$figures$rt_min)
    rt_max <- rt_in_min(m$conf$figures$rt_max)
    keytab <- flt_summ[,unique(.SD),.SDcol=c("adduct","ID")]
    for (n in 1:NROW(keytab)) {
        select <- dtable(adduct=keytab$adduct[[n]],
                         ID=keytab$ID[[n]])
Todor Kondic's avatar
Todor Kondic committed
        pdata_ms1 <- data4plot_ms1_cgram(ms1,select)
        pdata_ms2 <- data4plot_ms2_cgram(ms2,select)
        pdata_spec <- data4plot_ms2_spec(ms2,flt_summ,select)

        tabl_ms1 <- table_eic(pdata_ms1)
        ## tabl_ms2 <- table_eic(pdata_ms2) # For the moment, no real
                                            # info to be had here.
        tabl_spec <- table_spec(pdata_spec)

Todor Kondic's avatar
Todor Kondic committed
        palette = plot_palette(pdata_ms1)

        p_eic <- plot_eic_w_facet(pdata_ms1 = pdata_ms1,
                                  pdata_ms2 = pdata_ms2,
                                  rt_range = c(rt_min,rt_max),
                                  palette = palette)

        p_spec <- plot_spec_w_facet(pdata_ms2 = pdata_spec,
                                    mz_range = c(NA_real_,NA_real_),
                                    palette = palette)

Todor Kondic's avatar
Todor Kondic committed
        plot_save_single(p_eic,
                         decotab = select,
                         figtag = "eic",
                         proj_path = m$run$paths$project,
                         tabl = tabl_ms1,
Todor Kondic's avatar
Todor Kondic committed
                         extension = m$conf$figures$ext)

        plot_save_single(p_spec,
                         decotab = select,
                         figtag = "spec",
                         proj_path = m$run$paths$project,
                         tabl = tabl_spec,
Todor Kondic's avatar
Todor Kondic committed
                         extension = m$conf$figures$ext)
        message("Plotting of figure ",n," out of ",NROW(keytab)," has been completed.")
        
    }
prepare_app <- function(dir_before,
Todor Kondić's avatar
Todor Kondić committed
                        envopts) {
    ## Information that needs to be availabe to the shiny server.
    init <- list()
    init$dir_before <- dir_before
Todor Kondić's avatar
Todor Kondić committed
    init$envopts = envopts

    ## Create independent starting `home' for the server.
    dir_start <- tempfile("shinyscreen")
    dir.create(dir_start, recursive = T)

    ## Get list of app document contents.
    dir_rmd = system.file("rmd",package = "shinyscreen")
    fnms_rmd = list.files(dir_rmd,pattern = r"(^app.*\.Rmd$)",full.names=T)
    
    ## Copy startup files to that location.
    dir.create(file.path(dir_start,'www'), showWarnings=F)
    saveRDS(object = init,file=file.path(dir_start,"init.rds"))
    file.copy(system.file(file.path("www","custom.css"),package = "shinyscreen"),file.path(dir_start,"www","custom.css"))
    for (fn in fnms_rmd) file.copy(fn,file.path(dir_start,basename(fn)))
    
Todor Kondić's avatar
Todor Kondić committed

#' @export
Todor Kondic's avatar
Todor Kondic committed
#' @title app
Todor Kondić's avatar
Todor Kondić committed
#' @param envopts `envopts`. Shinyscreen environment options. 
#' @param shiny_args `list`, optional list of arguments conveyed to
#'     `rmarkdown::run` `shiny_args` argument.
#' @param render_args `list`, optional list of arguments conveyed to
#'     `rmarkdown::run` `render_args` argument.
Todor Kondić's avatar
Todor Kondić committed
#' @param metfrag_runtime `character(1)`, a location on the server side
#'     of the MetFrag jar file.
Todor Kondic's avatar
Todor Kondic committed
#' @return Nada.
#' @author Todor Kondić
Todor Kondić's avatar
Todor Kondić committed
app <- function(envopts,
                shiny_args=list(launch.browser=F),
                render_args=NULL) {
    dir_before = getwd()
    message("dir_before: ", dir_before)
Todor Kondić's avatar
Todor Kondić committed
    message("top_data_dir: ", envopts$top_data_dir)
    message("projects: ", envopts$projects)
    dir_start = prepare_app(dir_before=dir_before,
Todor Kondić's avatar
Todor Kondić committed
                            envopts=envopts)
Todor Kondic's avatar
Todor Kondic committed
    on.exit(expr=setwd(dir_before))
    setwd(dir_start)
    rmarkdown::run(file = "app.Rmd", shiny_args = shiny_args, render_args = render_args)
#' @export
#' @title serve
Todor Kondić's avatar
Todor Kondić committed
#' @param envopts `envopts`, an `envopts` object.
#' @param user `character(1)`, subdir of usersdir.
#' @param host `character(1)`, optional, address where the page is
#'     served.
#' @param port `integer(1)`, optional, port at which the page is
#'     served.
Todor Kondić's avatar
Todor Kondić committed
#' @param top_data_dir `character(1)`, a location on the server side
#'     containing data directories.
#' @param metfrag_db_dir `character(1)`, a location on the server side
#'     containing MetFrag DBs.
#' @return Nada.
#' @author Todor Kondić
Todor Kondić's avatar
Todor Kondić committed
serve <- function(envopts,user,host='0.0.0.0',port=7777) {
    shiny_args <- c(list(launch.browser=F),list(host=host,port=port))
Todor Kondić's avatar
Todor Kondić committed
    projects <- file.path(envopts$users_dir,user)
    if (!dir.exists(projects)) {
        dir.create(projects)
        message('Created projects: ',projects)
        message('Using existing projects: ', projects)
    app(shiny_args=shiny_args,
Todor Kondić's avatar
Todor Kondić committed
        envopts=envopts)
#' @export
#' @title report
report <- function(m) {
    report_author <- if (!is.null(m$conf$report$author)) m$conf$report$author else REPORT_AUTHOR
    report_title <- if (!is.null(m$conf$report$title)) m$conf$report$title else REPORT_TITLE
    fn_header <- system.file(file.path('rmd','report_header.rmd'),package = "shinyscreen")
    fn_chunk <- system.file(file.path('rmd','report_chunk.rmd'), package = "shinyscreen")
    dir.create(REP_TOPDIR,recursive = T,showWarnings = F)
    header <- knitr::knit_expand(fn_header)
    flt_summ <- m$out$tab$reptab
    ms2 <- m$db$extr$cgm$ms2
    ms1 <- m$db$extr$cgm$ms1
    rt_min <- rt_in_min(m$conf$figures$rt_min)
    rt_max <- rt_in_min(m$conf$figures$rt_max)
    keytab <- flt_summ[,unique(.SD),.SDcol=c("adduct","ID")]

    repdoc <- header
    for (n in 1:NROW(keytab)) {
        select <- dtable(adduct=keytab$adduct[[n]],
                         ID=keytab$ID[[n]])
        
        pdata_ms1 <- data4plot_ms1_cgram(ms1,select)
        pdata_ms2 <- data4plot_ms2_cgram(ms2,select)
        pdata_spec <- data4plot_ms2_spec(ms2,flt_summ,select)

        tabl_ms1 <- table_eic(pdata_ms1)
        ## tabl_ms2 <- table_eic(pdata_ms2) # For the moment, no real
                                            # info to be had here.
        tabl_spec <- table_spec(pdata_spec)

        palette = plot_palette(pdata_ms1)


        p_eic <- plot_eic_w_facet(pdata_ms1 = pdata_ms1,
                                  pdata_ms2 = pdata_ms2,
                                  rt_range = c(rt_min,rt_max),
                                  palette = palette) + ggplot2::theme(legend.position = "bottom")
        p_spec <- plot_spec_w_facet(pdata_ms2 = pdata_spec,
                                    mz_range = c(NA_real_,NA_real_),
                                    palette = palette) + ggplot2::theme(legend.position = "bottom")
        eic_things <- plot_save_single(p_eic,
                                       decotab = select,
                                       figtag = "eic",
                                       proj = m$conf$project,
                                       tabl = tabl_ms1,
                                       subdir = REP_TOPDIR,
                                       extension = m$conf$figures$ext)
        

        spec_things <- plot_save_single(p_spec,
                                        decotab = select,
                                        figtag = "spec",
                                        proj = m$conf$project,
                                        tabl = tabl_spec,
                                        subdir = REP_TOPDIR,
                                        extension = m$conf$figures$ext)

        report_chunk_header <- paste0("Adduct: ",keytab$adduct[[n]],"; ",
                                      "ID: ",keytab$ID[[n]])


        report_tab_eic_cap <- sprintf("EIC for %s",keytab$ID[[n]])
        report_fn_eic <- eic_things$fn_plot
        report_tab_eic <- if (shiny::isTruthy(eic_things$tab)) knitr::kable(eic_things$tab, caption = report_tab_eic_cap) else ""

        report_tab_spec_cap <- sprintf("Spectrum for %s",keytab$ID[[n]])
        report_fn_spec <- spec_things$fn_plot
        report_tab_spec <- if (shiny::isTruthy(eic_things$tab)) knitr::kable(spec_things$tab, caption = report_tab_spec_cap) else ""
        
        repdoc <- c(repdoc,knitr::knit_expand(fn_chunk))
        message("(report) Knitting of chunk ",n," out of ",NROW(keytab)," has been completed.")
        
    }
Todor Kondic's avatar
Todor Kondic committed
    fn_rep <- file.path(m$run$paths$project,"report.Rmd")
    message("(report) Writing Rmd...")
    cat(repdoc,file=fn_rep,sep = "\n")
    message("(report) ...done.")
    message("(report) Render start ...")
Todor Kondic's avatar
Todor Kondic committed
    rmarkdown::render(fn_rep,output_dir = m$run$paths$project)
    message("(report) ...done.")
    m
}
Todor Kondić's avatar
Todor Kondić committed


#' @title Initialise Shinyscreen Configuration
#' @details This function is used to inform `shinyscreen` about the
Todor Kondić's avatar
Todor Kondić committed
#'     working environment. If argument `save` is T, the configuration
#'     will be memorised. Subsequent calls to `init` without arguments
#'     will just load the configuration. If `merge` argument is T, the
#'     resulting configuration object is going to be a merge between
#'     the new parameters and the memorised ones. Those arguments not
#'     mentioned in the argument list will be remembered from the save
#'     config.
Todor Kondić's avatar
Todor Kondić committed
#' @inheritParams empty_envopts
Todor Kondić's avatar
Todor Kondić committed
#' @param merge `logical(1)`, optional. If T, merge with saved
#'     configuration.
#' @param save `logical(1)`, optional. If T, save configuration,
#'     otherwise just return the Shinyscreen environment options.
#' @param conf_dir `character(1)`, optional. Place where the
#'     configuration resides. Changing this usually only makes sense
#'     for testing.
Todor Kondić's avatar
Todor Kondić committed
#' @return An `envopts` object.
Todor Kondić's avatar
Todor Kondić committed
#' @author Todor Kondić
Todor Kondić's avatar
Todor Kondić committed
#' @export
init <- function(projects=NULL,
                 top_data_dir=NULL,
Todor Kondić's avatar
Todor Kondić committed
                 metfrag_db_dir=NULL,
                 metfrag_jar=NULL,
                 java_bin=NULL,
                 metfrag_max_proc=NULL,
                 no_structure_plots=NULL,
Todor Kondić's avatar
Todor Kondić committed
                 merge=T,
                 save=F,
                 conf_dir=tools::R_user_dir(package="shinyscreen",
                                            which="config")) {

    ## The function will usually return a merge between the saved
    ## configuration and arguments provided by the user. This is why
    ## we need to know which arguments have been actually changed by
    ## the user. In order to do this, all `envopts'-like arguments to
    ## `init' have been created with an illegal default value, NULL.

    ## Therefore, any argument with an user-supplied _valid_ value
    ## will be non-NULL. We can browse through the function
    ## environment, pick out these non-NULLs and then override the
    ## saved config with them.

    ## So, get the function environment.
Todor Kondić's avatar
Todor Kondić committed
    env = environment()

    ## Get the list of all possible arguments from the simpletst
    ## `envopts' constructor.
    eargs = formalArgs(empty_envopts)

    ## Check which are NULLs and retain only those which are
    ## not. 
    evals = lapply(eargs,function(ca) env[[ca]])
    ennull = sapply(evals,is.null,USE.NAMES=F)
    eargs = eargs[!ennull]
    evals = evals[!ennull]
    names(evals) = eargs
    ## Now call the empty envopts constructor only with non-NULL
    ## arguments.
    enew = do.call(empty_envopts,evals)
    ## If merging should occur.
Todor Kondić's avatar
Todor Kondić committed
    if (merge) {

        ## Get saved values.
        eold = load_envopts(dir=conf_dir)
        if (length(eold)>0L) {
Todor Kondić's avatar
Todor Kondić committed

            ## First, merge on non-metfrag keys. Overwrite only NULLs.
            simplekeys = setdiff(names(enew),"metfrag")
            for (a in simplekeys) {
                if (is.null(enew[[a]])) enew[[a]] = eold[[a]]
            }
Todor Kondić's avatar
Todor Kondić committed

            ## Now, metfrag. Do the same.
            mfkeys = names(enew$metfrag)
            for (a in mfkeys) {
                if (is.null(enew$metfrag[[a]])) enew$metfrag[[a]] = eold$metfrag[[a]]
            }
        }
        
Todor Kondić's avatar
Todor Kondić committed
    }

    ## Replace the remaining NULL values with actual defaults.
    e = seal_envopts(enew)
Todor Kondić's avatar
Todor Kondić committed
    
    if (save) save_envopts(o=e,dir=conf_dir)
Todor Kondić's avatar
Todor Kondić committed
    e
Todor Kondić's avatar
Todor Kondić committed
}

#' @title Run MetFrag
#' @details
#' @param m `state`, a state object.
#' @return m
#' @author Todor Kondić
metfrag <- function(m) {
    stagtab = metfrag_get_stag_tab(m$out$tab$summ[ms2_sel==T])
    ftab = metfrag_run(param = m$run$metfrag$param,
                       path = m$run$metfrag$path,
                       subpaths = m$run$metfrag$subpaths,
Todor Kondić's avatar
Todor Kondić committed
                       db_file = m$run$metfrag$db_file,
                       stag_tab = stagtab, ms2 = m$db$extr$spectra,
                       runtime=m$run$metfrag$runtime,
                       java_bin=m$run$metfrag$java_bin,
                       nproc = m$conf$metfrag$nproc)

    tab = summarise_metfrag_results(param = m$conf$metfrag$param,
                                    path = m$run$metfrag$path,
                                    subpaths = m$run$metfrag$subpaths,
                                    cand_parameters = m$conf$metfrag$cand_parameters,
                                    db_scores = m$conf$metfrag$database_scores,
                                    int_scores = m$conf$metfrag$intrinsic_scores,
                                    collect_candidates= m$conf$metfrag$collect_candidates,
                                    file_tab = ftab)

    fn = file.path(m$run$metfrag$path,"metfrag_summary.csv")

    data.table::fwrite(x=tab,file=fn,quote=T)

    m
}

make_db <- function(m) {
    m = make_db_catalogue(m)
    m = make_db_precursors(m)
    m
}