643 lines
38 KiB
R
643 lines
38 KiB
R
#Copyright 2012 The Board of Regents of the University of Wisconsin System.
|
|
#Contributors: Jason Shao, James McCurdy, Enhai Xie, Adam G.W. Halstead,
|
|
#Michael H. Whitney, Nathan DiPiazza, Minh Bui, Trey K. Sato and Yury V. Bukhman
|
|
#
|
|
#This file is part of GCAT.
|
|
#
|
|
#GCAT is free software: you can redistribute it and/or modify
|
|
#it under the terms of the GNU Lesser General Public License as published by
|
|
#the Free Software Foundation, either version 3 of the License, or
|
|
#(at your option) any later version.
|
|
#
|
|
#GCAT is distributed in the hope that it will be useful,
|
|
#but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
#GNU Lesser General Public License for more details.
|
|
#
|
|
#You should have received a copy of the GNU Lesser General Public License
|
|
#along with GCAT. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
########################################################################
|
|
# #
|
|
# Package-level documentation and imports #
|
|
# #
|
|
########################################################################
|
|
#' GCAT: Growth Curve Analysis Tool
|
|
#'
|
|
#' Mathematical modeling and parameter estimation of high volume microbial growth data.
|
|
#'
|
|
#' @details
|
|
#' GCAT input is in .csv format. GCAT analysis is accessed using \code{\link{gcat.analysis.main}}
|
|
#'
|
|
#' GCAT utilizes the \code{\link[stats]{nls}} function in the R stats package to fit logistic, Gompertz and Richards models to growth curve
|
|
#' data. Best model is selected automatically. Alternatively, the user may choose LOESS local regression fits, implemented using
|
|
#' \code{\link[stats]{loess}} function in the R stats package
|
|
#'
|
|
#' Internally, the data are stored in an array of \linkS4class{well} objects
|
|
#'
|
|
#' @import pheatmap gplots methods
|
|
#' @docType package
|
|
#' @name GCAT
|
|
NULL
|
|
|
|
# Initialization
|
|
PLATE.LETTERS = paste(rep(c("", LETTERS), each = 26), rep(LETTERS, 26), sep="")
|
|
global.version.number = packageDescription(pkg="GCAT")$Version
|
|
|
|
########################################################################
|
|
# #
|
|
# Top-level functions for analysis of screening data from .csv files. #
|
|
# #
|
|
########################################################################
|
|
# This functions is called directly by the user interface.
|
|
# They in turn call the main function <gcat.fit.main> (below) multiple times for each data file provided in <file.list>.
|
|
|
|
# Arguments:
|
|
# file.list - a list of full paths to .csv files. all files must be in the same format (see <single.plate>)
|
|
# single.plate - are the file in the single plate (wide) format vs. the multi-plate (long) format?
|
|
# layout.file - (optional) provide full path to a layout file with strain and media definitions (applies to all files in list)
|
|
|
|
# out.dir - name a directory to output the table of curve parameters to (defaults to working directory)
|
|
# graphic.dir - name a directory to output the images of the fitted curves to (defaults to subdirectory "pics" of <out.dir> above)
|
|
|
|
|
|
# add.constant- should be a numeric constant that will be added to each curve before the log transform (defaults to 1)
|
|
# blank.value - user can enter a blank OD measurement for uninoculated wells. if NULL, defaults to the value of the first OD measurement of each well.
|
|
# start.index - which timepoint should be used as the first one after inoculation (defaults to the 2th one)
|
|
# growth.cutoff - minimum threshold for curve growth.
|
|
# points.to.remove - a list of numbers referring to troublesome points that should be removed across all wells.
|
|
# remove.jumps - should the slope checking function be on the lookout for large jumps in OD?
|
|
|
|
# silent - should messages be returned to the console?
|
|
# verbose - should sub-functions return messages to console? (when I say verbose, I mean it!)
|
|
|
|
# Returns:
|
|
# if <return.fit> = F (default), avector of full paths to all the files generated by the function.
|
|
# otherwise, the fitted array of well objects.
|
|
|
|
# Use this function to analyze any set of .csv files using the same plate layout info.
|
|
|
|
#' Analyze screening growth data from the given .csv files.
|
|
#'
|
|
#' Top-level GCAT function
|
|
#'
|
|
#' @param file.list A list of full paths to .csv files. all files must be in the same format (see <single.plate>)
|
|
#' @param single.plate The file in the single plate (wide) format vs. the multi-plate (long) format?
|
|
#' @param layout.file Full path to a layout file with strain and media definitions (applies to all files in list)
|
|
#' @param out.dir A directory to output the table of curve parameters to (defaults to working directory)
|
|
#' @param graphic.dir A directory to output the images of the fitted curves to (defaults to subdirectory "pics" of <out.dir> above)
|
|
#' @param use.linear.param Whether to use linear parameters or not?
|
|
#' @param use.loess Whether to use LOESS model or not?
|
|
#' @param smooth.param Smoothing parameter for LOESS model.
|
|
#' @param add.constant A numeric constant that will be added to each curve before the log transform (defaults to 1)
|
|
#' @param blank.value User can enter a blank OD measurement for uninoculated wells. if NULL, assumes the first OD measurement of each well to be blank.
|
|
#' @param start.index Which timepoint should be used as the first one after inoculation (defaults to the 2th one)
|
|
#' @param growth.cutoff Minimum threshold for curve growth.
|
|
#' @param points.to.remove A list of numbers referring to troublesome points that should be removed across all wells.
|
|
#' @param remove.jumps Should the slope checking function be on the lookout for large jumps in OD?
|
|
#' @param time.input The time setting in which the current system is running?
|
|
#' @param plate.nrow The number of rows in a plate.
|
|
#' @param plate.ncol The number of columns in a plate.
|
|
#' @param input.skip.lines If specified, this number of lines shall be skipped from the top when reading the input file with read.csv
|
|
#' @param multi.column.headers The headers of the result tabular data when analyzing multiple plates at once.
|
|
#' @param single.column.headers The headers of the result tebaular data when analyzaing a single plate.
|
|
#' @param layout.sheet.headers The headers of the layout file?
|
|
#' @param silent Shoulde messages be returned to the console?
|
|
#' @param verbose Should sub-functions return messages to console? (when I say verbose, I mean it!)
|
|
#' @param overview.jpgs Should GCAT enable an overview image?
|
|
#' @param return.fit Whether should a fit well object is returned or not.
|
|
#' @param lagRange The heatmap specific range for lag time.
|
|
#' @param totalRange The heatmap specific range for the achieved growth on log scale.
|
|
#' @param totalODRange The heatmap specific range for the achieved growth on linear (OD) scale.
|
|
#' @param specRange The heatmap specific range for spec growth rate.
|
|
#'
|
|
#' @return Depending on return.fit setting, an array of fitted well objects or a list of output files
|
|
#'
|
|
#' @export
|
|
gcat.analysis.main = function(file.list, single.plate, layout.file = NULL,
|
|
out.dir = getwd(), graphic.dir = paste(out.dir, "/pics", sep = ""),
|
|
add.constant = 0, blank.value, start.index, growth.cutoff = 0.05,
|
|
use.linear.param = F, use.loess = F, smooth.param=0.1,
|
|
lagRange = NA, totalRange = NA, totalODRange = NA, specRange = NA,
|
|
points.to.remove = 0, remove.jumps = F, time.input = NA,
|
|
plate.nrow = 8, plate.ncol = 12, input.skip.lines = 0,
|
|
multi.column.headers = c("Plate.ID", "Well", "OD", "Time"), single.column.headers = c("","A1"),
|
|
layout.sheet.headers = c("Strain", "Media Definition"),
|
|
silent = T, verbose = F, return.fit = F, overview.jpgs = T){
|
|
|
|
# Capture the starting environment for debugging
|
|
main.envir = c(as.list(environment()))
|
|
|
|
# Check blank value and start index
|
|
if (is.null(blank.value) && start.index==1) {
|
|
exception("", "If inoculation time point is 1, the user must specify a blank value")
|
|
}
|
|
|
|
# MB: Not the best solution.
|
|
if (is.na(time.input)) {
|
|
if (single.plate)
|
|
time.input = 1/3600
|
|
else
|
|
exception("", "time.input is NA.")
|
|
}
|
|
|
|
# MB: Now add.constant will always be 0.
|
|
# No need to check.
|
|
#if (add.constant < 0)
|
|
# exception("", "The constant r should not be negative.")
|
|
# End prototyping temporary solution.
|
|
|
|
# YB: seem to need this to avoid spurious discarding of some wells in example multiplate dataset: Trac ticket 1780.
|
|
# however, this causes an error in Rails
|
|
if(length(points.to.remove)==0) points.to.remove = 0
|
|
|
|
upload.timestamp = strftime(Sys.time(), format="%Y-%m-%d %H:%M:%S") # Get a timestamp for the time of upload.
|
|
fitted.well.array.master = list()
|
|
source.file.list = c()
|
|
|
|
dim(fitted.well.array.master) = c(plate.nrow,plate.ncol,0)
|
|
dimnames(fitted.well.array.master) = list(PLATE.LETTERS[1:plate.nrow], 1:plate.ncol, c())
|
|
|
|
for(file.name in file.list){
|
|
|
|
# Call <gcat.fit.main> on the file with single plate options
|
|
fitted.well.array = try(gcat.fit.main(file.name = file.name, load.type = "csv",
|
|
single.plate = single.plate, layout.file = layout.file, start.index = start.index,
|
|
time.input = time.input, add.constant = add.constant, blank.value = blank.value,
|
|
growth.cutoff = growth.cutoff, points.to.remove = points.to.remove, remove.jumps = remove.jumps,
|
|
use.linear.param=use.linear.param, use.loess=use.loess, smooth.param=smooth.param,
|
|
plate.nrow = plate.nrow, plate.ncol = plate.ncol, multi.column.headers = multi.column.headers,
|
|
single.column.headers = single.column.headers, layout.sheet.headers = layout.sheet.headers,
|
|
input.skip.lines = input.skip.lines, silent = silent, verbose = verbose), silent = T)
|
|
|
|
# Return error message if the function fails.
|
|
if(class(fitted.well.array) == "try-error")
|
|
return(as.character(fitted.well.array))
|
|
}
|
|
|
|
# Add fitted well array onto existing fitted wells
|
|
fitted.well.array.master = gcat.append.arrays(fitted.well.array.master, fitted.well.array, plate.ncol, plate.nrow)
|
|
|
|
# Remove the "processed_" tag from file names and add to the list of source files.
|
|
source.file.list = c(source.file.list, basename(paste(strsplit(file.name, "processed_")[[1]],collapse="/")))
|
|
|
|
out.files = try(gcat.output.main(fitted.well.array.master, out.prefix = "output",
|
|
source.file.list = source.file.list, upload.timestamp = upload.timestamp,
|
|
growth.cutoff = growth.cutoff, add.constant = add.constant, blank.value = blank.value, start.index = start.index,
|
|
points.to.remove = points.to.remove, remove.jumps = remove.jumps,
|
|
lagRange = lagRange, specRange = specRange, totalRange = totalRange, totalODRange = totalODRange,
|
|
out.dir = out.dir, graphic.dir = graphic.dir, overview.jpgs=overview.jpgs,
|
|
use.linear.param=use.linear.param, use.loess=use.loess, plate.ncol = plate.ncol, plate.nrow = plate.nrow,
|
|
silent = silent, main.envir = main.envir), silent = T)
|
|
|
|
# Return file list or error message otherwise return "successful analysis" message (?)
|
|
|
|
# file.list = c("Data was successfully analyzed.", file.list) # <--- yet to be implemented. causes errors downstream right now
|
|
if(class(out.files) == "try-error") return(as.character(out.files))
|
|
|
|
if(return.fit) return(fitted.well.array.master)
|
|
else return(out.files)
|
|
}
|
|
|
|
########################################################################
|
|
# #
|
|
# Main function for analysis of screening data from input tables. #
|
|
# #
|
|
########################################################################
|
|
# This is the main function that handles all the analyses for files in both single and multiple plate formats.
|
|
# It is called by the top level function <analysis.main>
|
|
#
|
|
# It then calls the following functions on each member of the array:
|
|
# - curve normalization and standardization: <gcat.start.times>, <remove.points>, <normalize.ODs>, <transform.ODs>,
|
|
# - curve shape analysis before model fitting: <fill.slopes>, <check.curve>, <classify.curve>
|
|
# - to fit a nonlinear model to the growth data: <fit.model>
|
|
# Finally, it returns the fitted array of well objects.
|
|
|
|
#' Main analysis function for GCAT
|
|
#'
|
|
#' This is the main function that handles all the analyses for data files in both single and multiple plate formats.
|
|
#' It is called by the top level function \code{gcat.analysis.main} along with \code{gcat.output.main}.
|
|
#'
|
|
#' @param file.name Complete path and file name of a comma-separated values (.csv) file containing growth curve data
|
|
#' in the multiple-plate (long) format.
|
|
#' @param input.data A list of tables representing input files read with \code{read.table}. Used to save time in cases
|
|
#' of running multiple analyses on the same dataset. If used, the function will ignore \code{file.name} entirely.
|
|
#' @param load.type .csv by default.
|
|
#' @param layout.file Specifies the location of a layout file containing identifying information.
|
|
#' @param single.plate Whether the GCAT is analyzing a single plate or not.
|
|
#' @param blank.value Blank OD measurement for uninoculated wells. By default(NULL), the value of the first OD
|
|
#'measurement in each well is used.
|
|
#' @param start.index Which timepoint should be used as the first one after inoculation?
|
|
#' @param time.input Either a character describing the format used to convert timestamps in the input to numbers
|
|
#' representing number of seconds (see \code{strptime}), or a factor to divide entries in the Time column by to get the
|
|
#' numbers of hours.
|
|
#' @param normalize.method Describes the method used by \code{normalize.ODs} to normalize cell density values using blank reads.
|
|
#' @param add.constant A value for r in the log(OD + r) transformation.
|
|
#' @param use.log Should the analysis use log on all values.
|
|
#' @param points.to.remove A vector of integers specifying which timepoints should be removed across all wells.
|
|
#' By default(0) none are marked for removal.
|
|
#' @param use.linear.param Should the linear parameter be used or not.
|
|
#' @param use.loess Should the loess model be used or not.
|
|
#' @param smooth.param If loess model is used, this parameter define the smoothing parameter for the loess model.
|
|
#' @param fall.cutoff A cutoff used by \code{check.slopes} to decide on thresholds for jumps and tanking.
|
|
#' @param growth.cutoff A threshold used by check.growth to decide whether a well displays growth.
|
|
#' @param remove.jumps Should jumps in OD detected by the subfunction \code{check.slopes}?
|
|
#' @param plate.nrow The number of rows in the input files.
|
|
#' @param plate.ncol The number of columns in the input files.
|
|
#' @param input.skip.lines If specified, this number of lines shall be skipped from the top when reading the input file with read.csv
|
|
#' @param multi.column.headers The headers of the column when analyzing multiple plates.
|
|
#' @param single.column.headers The headers of the column when analyzing a single plate.
|
|
#' @param layout.sheet.headers The headers of the layout file.
|
|
#' @param growth.model What growth model should be used?
|
|
#' @param backup.growth.model If the main growth model fails, the back up model will be used.
|
|
#' @param silent Surpress all messages.
|
|
#' @param verbose Display all messages when analyzing each well.
|
|
#'
|
|
#' @return An array of well objects
|
|
gcat.fit.main = function(file.name, input.data = NULL, load.type = "csv", layout.file = NULL,
|
|
single.plate = F, blank.value = NULL, start.index = 2, time.input = NA,
|
|
normalize.method = "default", add.constant = 1, use.log = T, points.to.remove = 0,
|
|
use.linear.param=F, use.loess=F, smooth.param=0.1,
|
|
fall.cutoff = -0.0025, growth.cutoff = 0.05, remove.jumps = F,
|
|
plate.nrow = 8, plate.ncol = 12, input.skip.lines = 0,
|
|
multi.column.headers = c("Plate.ID", "Well", "OD", "Time"), single.column.headers = c("","A1"),
|
|
layout.sheet.headers = c("Strain", "Media Definition"),
|
|
growth.model = NA, backup.growth.model = NA,
|
|
silent = F, verbose = F){
|
|
|
|
# Explanation of arguments:
|
|
|
|
# ---File Handling---
|
|
# file.name - full path to an excel spreadsheet, .csv or tab-delimited text file, in either the single or multiple-plate format
|
|
# input.data - use pre-loaded data set (output from <gcat.load.data> function only). will override <file.name> if not NULL
|
|
# load.type - supports "csv."
|
|
# layout.file - full path to a file containing the plate layout in the same format as <file.name>. will not be used if <load.type> is "xlsx"
|
|
|
|
# ---Input file format---
|
|
# single.plate - true denotes data in single-plate format, i.e. simple OD output. false denotes multiple-plate robotic screening output.
|
|
# note: reading directly from excel to R results in timestamps being converted to days.
|
|
|
|
# ---Normalization and Transforms---
|
|
# blank.value - user can enter a blank OD measurement for uninoculated wells. if NULL, defaults to the value of the first OD measurement of each well.
|
|
# start.index - which timepoint should be used as the first one after inoculation (defaults to the 2th one)
|
|
# normalize.method - how should each growth curve be normalized? allowed values are:
|
|
# "first": subtracts the first OD, assumed to be the blank, from all ODs
|
|
# "none": does nothing, assumes no blank. highly recommend log(OD+1) transform in this case.
|
|
# "average.first": forces all filled wells on each plate to match the average value at <start.index> (after subtracting the first OD)
|
|
# add.constant - a numeric constant that will be added to each curve before the log transform (default is 0)
|
|
# use.log - should a log transform be applied to the data after normalization?
|
|
# points.to.remove - a list of numbers referring to troublesome points that should be removed across all wells.
|
|
|
|
# ---Pre-fitting processing---
|
|
# fall.cutoff - a cutoff value for determining whether OD falls significantly between two timepoints. see <check.slopes> in prefit.processing.R for details.
|
|
# growth.cutoff - a cutoff value for determining whether a well contains a successfully growing culture or not.
|
|
# remove.jumps - should the slope checking function be on the lookout for large jumps in OD?
|
|
|
|
# ---Model fitting---
|
|
# model - which parametrized growth model to use? can be richards, gompertz, or logistic. models are defined as objects of class model, see "model.class.R"
|
|
# backup.model - which model should be used if fitting using <model> fails? should ideally be simpler than the main model (less parameters)
|
|
|
|
# ---Miscellanous input/output preferences---
|
|
# silent - should messages be returned to the console?
|
|
# verbose - should sub-functions return messages to console? (when I say verbose, I mean it!)
|
|
# unlog - should exported graphics be transformed back to the OD scale?
|
|
# return.fit - should the function return an array of wells? if not, it will return a list of generated files.
|
|
|
|
|
|
########################################################################
|
|
# Read from .csv file #
|
|
########################################################################
|
|
#
|
|
# The functions used here are found in table2well.R
|
|
|
|
if(!silent) cat("\nReading input files...")
|
|
# Read from .csv or tab-delimited text file using <gcat.load.data> (in load.R)
|
|
# if <layout.file> is provided, it will be used here.
|
|
|
|
plate.layout = NULL
|
|
# Read layout file if it is specified.
|
|
if(!is.null(layout.file)){
|
|
if(load.type=="csv") plate.layout = read.csv(layout.file,header=T,stringsAsFactors=F)
|
|
else plate.layout = read.table(layout.file,header=T,sep="\t",stringsAsFactors=F)
|
|
if(!silent) cat("\n\tAdded plate layout information from", layout.file, "\n")
|
|
}
|
|
|
|
# Load the data
|
|
well.array = try(gcat.load.data(file.name = file.name, input.data = input.data,
|
|
plate.layout = plate.layout, plate.nrow = plate.nrow, plate.ncol = plate.ncol,
|
|
input.skip.lines = input.skip.lines, multi.column.headers = multi.column.headers,
|
|
single.column.headers = single.column.headers, layout.sheet.headers = layout.sheet.headers,
|
|
blank.value = blank.value, start.index = start.index, single.plate = single.plate,
|
|
load.type = load.type, silent=silent),silent=silent)
|
|
|
|
# Return an error if there is a problem with file loading.
|
|
if (class(well.array) == "try-error")
|
|
stop("Error in <gcat.load.data>: ", well.array)
|
|
|
|
|
|
# !---At this point, <well.array> is an array of well objects, each containing raw data and media/strain information if provided---
|
|
|
|
# Attempt to apply time formatting to all wells in array
|
|
well.array = try(aapply(well.array, gcat.start.times, start.index = start.index, time.input = time.input),silent=silent)
|
|
|
|
# Return an error if there is a problem with time formatting
|
|
if (class(well.array) == "try-error")
|
|
stop("Error in <gcat.start.times>: ", well.array)
|
|
|
|
########################################################################
|
|
# Perform normalization and transformation of raw data #
|
|
########################################################################
|
|
#
|
|
# The functions used here are found in normalize.and.transform.R
|
|
|
|
if(!silent) cat("\nProcessing raw data...")
|
|
|
|
# Set all timepoints to active for now using "points.to.remove=0" argument with <remove.points>
|
|
# adds an extra column to the "well.array" slot of each well specifying which points to remove when data is retrieved from the well
|
|
well.array = aapply(well.array, remove.points, points = 0)
|
|
|
|
# Normalize ODs using specified method and adding a constant if desired.
|
|
# sets the "norm" slot of each well to a value to be subtracted from OD values whenever data is retrieved from the well
|
|
well.array = try(normalize.ODs(well.array, normalize.method = normalize.method,
|
|
start.index = start.index, blank.value = blank.value, add.constant = add.constant),silent=silent)
|
|
|
|
# Return an error if there is a problem with normalization
|
|
if (class(well.array) == "try-error")
|
|
stop("Error in <normalize.ODs>: ", well.array)
|
|
#well.array = try(subtract.blank(well.array, blank.value), silent = silent)
|
|
|
|
# Transform ODs on the logarithmic scale, regardless of whether <use.log> is true
|
|
# an extra column of log-transformed values is added to the "well.array" slot of each well
|
|
# the "use.log" slot of each well is set instead to determine whether the transformed values will be returned when data is retrieved from the well.
|
|
well.array = try(aapply(well.array, transform.ODs, start.index = start.index, blank.value = blank.value, use.log = use.log, constant.added = add.constant),silent=silent)
|
|
|
|
# Return an error if there is a problem with transformation
|
|
if (class(well.array) == "try-error")
|
|
stop("Error in <transform.ODs>: ", well.array)
|
|
|
|
# Remove specified timepoints across wells (use "points.to.remove=NULL" if no points to remove)
|
|
well.array = try(aapply(well.array, remove.points, points = points.to.remove),silent=silent)
|
|
|
|
# Return an error if there is a problem with point removal
|
|
if (class(well.array) == "try-error")
|
|
stop("Error in <remove.points>: ", well.array)
|
|
|
|
|
|
|
|
########################################################################
|
|
# Pre-fitting data processing (analysis of curve shapes) #
|
|
########################################################################
|
|
#
|
|
# The functions used here are found in slope.analysis.R
|
|
|
|
# Estimate slope at each timepoint
|
|
# add a column to the "well.array" slot of each well with the local slope at each timepoint
|
|
|
|
well.array = try(aapply(well.array, calculate.slopes, silent=!verbose),silent=silent)
|
|
|
|
# Return an error if there is a problem with slope calculation
|
|
if (class(well.array) == "try-error")
|
|
stop("Error in <calculate.slopes>: ", well.array)
|
|
|
|
# Check slopes for tanking and/or jumping behavior
|
|
# fills the "curve.par" slot of each well with <tanking.start>, denoting the timepoint at which tanking starts (if none, value is NA)
|
|
# uses <remove.points> to remove all points after <tanking.start>
|
|
# It will also fill the "jump.error" slot with a status message, and try to use an automated process to remove the
|
|
# erroneous points if <remove.jumps> is true (default false).
|
|
|
|
well.array = try(aapply(well.array, check.slopes, fall.cutoff = fall.cutoff, remove.jumps = remove.jumps, silent=!verbose, draw = F),silent=silent)
|
|
|
|
# Return an error if there is a problem with slope analysis
|
|
if (class(well.array) == "try-error")
|
|
stop("Error in <check.slopes>: ", well.array)
|
|
|
|
|
|
# Check curves for growth above cutoff
|
|
# fills the "curve.par" slot of each well with <no.growth>, denoting whether the well has no detectable growth.
|
|
well.array = try(aapply(well.array, check.growth, growth.cutoff = growth.cutoff, start.index = start.index),silent=silent)
|
|
|
|
# Return an error if there is a problem with growth.check
|
|
if (class(well.array) == "try-error")
|
|
stop("Error in <check.growth>: ", well.array)
|
|
|
|
########################################################################
|
|
# Fit parameterized models to data #
|
|
########################################################################
|
|
#
|
|
# The functions used here are found in fit.model.R
|
|
|
|
# Fit each well with the selected model and attempt to catch failed fittings with the backup model
|
|
# skips wells designated as <no.growth> above
|
|
# fills the "fit.info" slot of each well with "success," "failed," or "skipped"
|
|
# if fit was successful:
|
|
# fills the "equation" and "model.name" slots with the relevant info for the successful model
|
|
# fills the "fit.par" slot with fitted parameters if fit is successful
|
|
|
|
if(!silent) cat("\nFitting models to data...")
|
|
well.array = aapply(well.array, fit.model, growth.model=growth.model,
|
|
backup.growth.model = backup.growth.model, use.linear.param=use.linear.param,
|
|
use.loess=use.loess, smooth.param=smooth.param, silent=!verbose)
|
|
|
|
# Return an error if there is a problem with model fitting
|
|
if (class(well.array) == "try-error")
|
|
stop("Error in <fit.model>: ", well.array)
|
|
|
|
if(!silent) cat("\ndone!\n")
|
|
return(well.array)
|
|
}
|
|
|
|
|
|
########################################################################
|
|
# #
|
|
# Output function for generating files from fitted data. #
|
|
# #
|
|
########################################################################
|
|
#' Output function for generating files from fitted data.
|
|
#'
|
|
#' Handles files and directories, calls \code{table.out}, \code{plate.overview} and \code{view.fit}
|
|
#' to generate output tables and graphics.
|
|
#'
|
|
#' @param fitted.well.array A list of fitted well objects.
|
|
#' @param out.prefix Prefix that is in the name of output files.
|
|
#' @param blank.value User can enter a blank OD measurement for uninoculated wells.
|
|
#' If NULL, defaults to the value of the first OD measurement of each well.
|
|
#' @param start.index Which timepoint should be used as the first one after inoculation (defaults to the 2th one)
|
|
#' @param growth.cutoff Minimum threshold for curve growth.
|
|
#' @param points.to.remove A list of numbers referring to troublesome points that should be removed across all wells.
|
|
#' @param remove.jumps Should the slope checking function be on the lookout for large jumps in OD?
|
|
#' @param out.dir name a directory to output the table of curve parameters to (defaults to working directory)
|
|
#' @param graphic.dir name a directory to output the images of the fitted curves to
|
|
#' (defaults to subdirectory "pics" of <out.dir> above)
|
|
#' @param overview.jpgs should jpgs be generated for each plate with the overview graphic?
|
|
#' This is for backwards compatibility with the old web server.
|
|
#' @param silent should messages be returned to the console?
|
|
#' @param unlog should exported graphics be transformed back to the OD scale?
|
|
#' @param source.file.list A list of the source files' names.
|
|
#' @param upload.timestamp The time format indicated by the user.
|
|
#' @param add.constant used to readjust for the constant added during the log transform when plotting ODs.
|
|
#' @param use.linear.param linear parameter is used or not?
|
|
#' @param use.loess Is LOESS model going to be used?
|
|
#' @param plate.nrow The number of rows for a plate.
|
|
#' @param plate.ncol The number of columns for a plate
|
|
#' @param lagRange The heatmap specific range for lag time.
|
|
#' @param totalRange The heatmap specific range for the achieved growth.
|
|
#' @param totalODRange The heatmap specific range for the achieved growth on linear (OD) scale.
|
|
#' @param specRange The heatmap specific range for spec growth rate.
|
|
#' @param main.envir starting environment of gcat.analysis.main(), captured as a list, printed out for debugging
|
|
#'
|
|
#' @return A list of output files if success.
|
|
gcat.output.main = function(fitted.well.array, out.prefix = "", source.file.list, upload.timestamp = NULL,
|
|
add.constant, blank.value, start.index, growth.cutoff, points.to.remove, remove.jumps,
|
|
out.dir = getwd(), graphic.dir = paste(out.dir,"/pics",sep = ""), overview.jpgs = T,
|
|
use.linear.param=F, use.loess=F, lagRange = NA, totalRange = NA, totalODRange = NA, specRange = NA,
|
|
plate.nrow = 8, plate.ncol = 12, unlog = F, silent = T, main.envir){
|
|
|
|
# Prepare timestamp for addition to output file names.
|
|
filename.timestamp = strftime(upload.timestamp, format="_%Y-%m-%d_%H.%M.%S")
|
|
|
|
########################################################################
|
|
# Prepare to write to output files #
|
|
########################################################################
|
|
if(is.null(blank.value)) blank.value = "First timepoint in well"
|
|
|
|
if(!silent) cat("\nFinding/creating new output directories...")
|
|
|
|
old.wd = getwd()
|
|
# Create output directory if it doesn't exist
|
|
if(class(try(setwd(out.dir), silent = T)) == "try-error"){
|
|
if(!silent) cat("\ncreating new output directory")
|
|
if (class(try(dir.create(out.dir))) == "try-error")
|
|
stop("Error creating new output directory!")
|
|
}
|
|
|
|
# Create graphics directory if it doesn't exist
|
|
if(class(try(setwd(graphic.dir), silent = T)) == "try-error"){
|
|
if(!silent) cat("\ncreating new graphics directory")
|
|
if (class(try(dir.create(graphic.dir))) == "try-error")
|
|
stop("Error creating new graphics directory!")
|
|
}
|
|
|
|
########################################################################
|
|
# Populate a data table with fit results and write to file #
|
|
########################################################################
|
|
#
|
|
# The functions used here are found in table.output.R
|
|
|
|
# Creates a table with a row for each well and a column for each of various identifiers and fitted and calculated parameters.
|
|
|
|
if(!silent) cat("\nPopulating data table...")
|
|
table.fit = try(table.out(fitted.well.array, filename.timestamp=filename.timestamp,use.linear.param=use.linear.param, use.loess=use.loess, constant.added=add.constant))
|
|
|
|
# Return an error if there is a problem with returning the table
|
|
if (class(fitted.well.array) == "try-error")
|
|
stop("Error in <table.out>: ", fitted.well.array)
|
|
|
|
|
|
# Set working directory to <out.dir>
|
|
if (class(try(setwd(out.dir))) == "try-error")
|
|
stop("Error setting directory for table output")
|
|
|
|
# Write output table to file in <out.dir>
|
|
table.filename = paste(out.dir, "/", out.prefix, "_gcat.fit", filename.timestamp, ".txt", sep = "")
|
|
if (class(try(write.table(table.fit, table.filename, sep = "\t", row.names = F))) == "try-error")
|
|
stop("Error writing tabular output")
|
|
|
|
# ---If successfully written, add postscript and start a list of generated files.
|
|
generated.files = table.filename
|
|
|
|
########################################################################
|
|
# Write individual fit and overview graphics to file #
|
|
########################################################################
|
|
#
|
|
# The functions used here are found in graphic.output.R
|
|
|
|
if(!silent) cat("\nDrawing graphics...")
|
|
|
|
# Set working directory to <graphic.dir>
|
|
if (class(try(setwd(graphic.dir))) == "try-error")
|
|
stop("Error setting directory for graphic output")
|
|
# Use function <pdf.by.plate> to write fit graphics to file.
|
|
|
|
graphic.files = try(pdf.by.plate(fitted.well.array, out.prefix=out.prefix, upload.timestamp = upload.timestamp,
|
|
unlog=unlog,constant.added=add.constant,overview.jpgs=overview.jpgs, lagRange = lagRange, specRange = specRange, totalRange = totalRange,
|
|
totalODRange = totalODRange, plate.ncol = plate.ncol, plate.nrow = plate.nrow),silent=silent)
|
|
|
|
if (class(graphic.files) == "try-error")
|
|
stop("Error in <pdf.by.plate>: ", graphic.files)
|
|
|
|
# If successfully written, add to the list of generated files.
|
|
generated.files = c(generated.files, graphic.files)
|
|
|
|
########################################################################
|
|
# Add a postscript to the output table with legend and file info. #
|
|
########################################################################
|
|
#
|
|
sink(table.filename, append = T)
|
|
analysis.timestamp = strftime(Sys.time(), format="%Y-%m-%d %H:%M:%S")
|
|
cat("\n# Raw OD values are adjusted and log-transformed before fitting a growth curve as follows: log.OD = log(OD - blank + const) where blank is OD of blank medium and const is specified by the user (1 by default)",
|
|
"\n# Values are reported on the above 'log.OD' scale unless otherwise specified.",
|
|
"\n# .SE columns report standard errors of those values that are estimated directly as parameters of global sigmoid models.",
|
|
"\n# .OD columns report values back-transformed to the linear 'OD - blank' scale.",
|
|
"\n")
|
|
|
|
cat("\n# -- Explanation of columns --",
|
|
"\n# - model: Name of the model the well was successfully fit with (if any)",
|
|
"\n# - lag.time: Lag time estimate inferred from the fitted model",
|
|
"\n# - inflection.time: inflection time point of the growth curve when drawn on the log scale",
|
|
"\n# - max.spec.growth.rate: maximum specific growth rate estimate inferred from the fitted model. Estimated as the first derivative of the growth curve at inflection time point",
|
|
"\n# - baseline: growth curve baseline. Global sigmoid model: baseline is parameter 'b' of the model. LOESS: baseline is the same as the lowest predicted log.OD value",
|
|
"\n# - amplitude: difference between upper plateau and baseline values. Global sigmoid model: amplitude is parameter 'A' of the model. LOESS: amplitude = max.log.OD - min.log.OD",
|
|
"\n# - plateau: upper asymptote value of the fitted model. Global sigmoid model: plateau = b + A. LOESS: plateau = max.log.OD",
|
|
"\n# - inoc.log.OD: log.OD value at inoculation. Estimated value from the fitted model is used, rather than the actual measurement",
|
|
"\n# - max.log.OD: maximal log.OD value reached during the experiment. Estimated value from the fitted model is used rather than the actual measurement",
|
|
"\n# - projected.growth: maximal projected growth over inoculation value. Global sigmoid model: projected.growth = plateau - inoc.log.OD. LOESS: not reported",
|
|
"\n# - achieved.growth: maximal growth over inoculation value actually achieved during the experiment. achieved.growth = max.log.OD - inoc.log.OD",
|
|
"\n# - shape.par: shape parameter of the Richard equation",
|
|
"\n# - R.squared: goodness of fit metric. Also known as coefficient of determination. R.squared is usually between 0 and 1. A value close to 1 indicates good fit.",
|
|
"\n# - RSS: residual sum of squares. Another goodness of fit metric. Smaller values indicate better fits.",
|
|
"\n# - empty: (Well indicator)",
|
|
"\n# - an 'E' indicates that the well was empty and no growth was detected. ",
|
|
"\n# - an 'I' indicates that the well was inoculated and growth was detected above the threshold. ",
|
|
"\n# - an 'E*' indicates that the well was empty and growth was detected (possible contamination). ",
|
|
"\n# - an '!' indicates that the well was inoculated and no growth was detected. ",
|
|
"\n# - asymp.not.reached: shows \"L\" if the bottom asymptote (baseline) was not reached and \"U\" if the upper asymptote (plateau) was not reached.",
|
|
"\n# - tank: (Tanking indicator) If a number is present then the growth trend was determined to tank at that timepoint index.",
|
|
"\n# - other: Additional flag column. Displays information about whether jumps in OD were detected and what was done about them.",
|
|
"\n# - pdf.file and page.no: location of the figure for this well in the output .pdf files."
|
|
)
|
|
|
|
# Analysis information
|
|
|
|
cat("\n#\n# -- Source file information--",
|
|
"\n# ", paste(source.file.list, collapse = "\n# "),
|
|
"\n# analyzed using GCAT v", global.version.number,
|
|
"\n# request sent: ", upload.timestamp,
|
|
"\n# completed: ", analysis.timestamp,
|
|
"\n#\n# -- Parameters used in current analysis --",
|
|
"\n# - Constant added to log(OD + n) transformation:", add.constant,
|
|
"\n# - Blank OD value: ", blank.value,
|
|
"\n# - Index of inoculation timepoint", start.index,
|
|
"\n# - Minimum growth threshold:", growth.cutoff,
|
|
"\n# - Removed points:", paste(points.to.remove, collapse = " "),
|
|
"\n# - Jump detection:", remove.jumps
|
|
)
|
|
|
|
# gcat.analysis.main() starting environment
|
|
cat("\n#\n# -- gcat.analysis.main() starting environment --\n#")
|
|
print(main.envir)
|
|
|
|
# Done with text file output
|
|
sink()
|
|
|
|
########################################################################
|
|
# Return values to R #
|
|
########################################################################
|
|
#
|
|
|
|
if(!silent) cat("\ndone!")
|
|
setwd(old.wd)
|
|
# Return list of generated files
|
|
return(generated.files)
|
|
}
|
|
|