diff --git a/.Rbuildignore b/.Rbuildignore index 53aaa071..5af5293d 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -6,10 +6,11 @@ ^\.httr-oauth$ ^doc$ ^Meta$ -google-analytics.js -droptoken.rds ^cran-comments\.md$ ^CRAN-RELEASE$ ^_pkgdown\.yml$ ^docs$ ^pkgdown$ +^codecov\.yml$ +^vignettes/spectragryph.* +^vignettes/advanced.* diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index f8220209..4b6f8a42 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -1,4 +1,4 @@ -# Workflow derived from https://github.com/r-lib/actions/tree/master/examples +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help on: push: @@ -18,7 +18,7 @@ jobs: fail-fast: false matrix: config: - - {os: macOS-latest, r: 'release'} + - {os: macos-latest, r: 'release'} - {os: windows-latest, r: 'release'} - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} - {os: ubuntu-latest, r: 'release'} @@ -29,30 +29,31 @@ jobs: R_KEEP_PKG_SOURCE: yes steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - - uses: r-lib/actions/setup-pandoc@v1 + - uses: r-lib/actions/setup-pandoc@v2 - - uses: r-lib/actions/setup-r@v1 + - name: Install XQuartz on macOS + if: runner.os == 'macOS' + run: brew install xquartz --cask + + - uses: r-lib/actions/setup-r@v2 with: r-version: ${{ matrix.config.r }} http-user-agent: ${{ matrix.config.http-user-agent }} use-public-rspm: true - - uses: r-lib/actions/setup-r-dependencies@v1 + - uses: r-lib/actions/setup-r-dependencies@v2 with: - extra-packages: rcmdcheck - - - uses: r-lib/actions/check-r-package@v1 + extra-packages: any::rcmdcheck + needs: check - - name: Show testthat output - if: always() - run: find check -name 'testthat.Rout*' -exec cat '{}' \; || true + - name: Populate .Renviron with GitHub Secrets + run: | + echo AWS_ACCESS_KEY_ID="${{ secrets.AWS_ACCESS_KEY_ID }}" >> ~/.Renviron + echo AWS_SECRET_ACCESS_KEY="${{ secrets.AWS_SECRET_ACCESS_KEY }}" >> ~/.Renviron shell: bash - - name: Upload check results - if: failure() - uses: actions/upload-artifact@main + - uses: r-lib/actions/check-r-package@v2 with: - name: ${{ runner.os }}-r${{ matrix.config.r }}-results - path: check + upload-snapshots: true diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml new file mode 100644 index 00000000..6cf3ff8f --- /dev/null +++ b/.github/workflows/test-coverage.yaml @@ -0,0 +1,50 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, release] + pull_request: + branches: [main, release] + +name: test-coverage + +jobs: + test-coverage: + runs-on: ubuntu-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v3 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::covr + needs: coverage + + - name: Test coverage + run: | + covr::codecov( + quiet = FALSE, + clean = FALSE, + install_path = file.path(Sys.getenv("RUNNER_TEMP"), "package") + ) + shell: Rscript {0} + + - name: Show testthat output + if: always() + run: | + ## -------------------------------------------------------------------- + find ${{ runner.temp }}/package -name 'testthat.Rout*' -exec cat '{}' \; || true + shell: bash + + - name: Upload test results + if: failure() + uses: actions/upload-artifact@v3 + with: + name: coverage-test-failures + path: ${{ runner.temp }}/package diff --git a/.gitignore b/.gitignore index b9533613..1650843a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,6 @@ .Rhistory .Rapp.history -# Desktop drive files -.ini - # Session Data files .RData @@ -27,8 +24,11 @@ # produced PDF vignettes and docs vignettes/*.pdf inst/doc -doc -Meta +doc/ +Meta/ + +# Shiny app +inst/OpenSpecy-shiny-main/ # OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 .httr-oauth @@ -48,7 +48,7 @@ Meta docs/ # System files -*.ini +.ini *.swp .DS_Store -docs +delete.bat diff --git a/DESCRIPTION b/DESCRIPTION index 4600f4fe..d13ebc54 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,15 +1,19 @@ Package: OpenSpecy Type: Package Title: Analyze, Process, Identify, and Share Raman and (FT)IR Spectra -Version: 0.9.5 -Date: 2022-07-06 -Authors@R: c(person("Win", "Cowger", role = c("cre", "aut"), +Version: 1.0.0 +Date: 2023-08-29 +Authors@R: c(person("Win", "Cowger", role = c("cre", "aut", "dtc"), email = "wincowger@gmail.com", comment = c(ORCID = "0000-0001-9226-3104")), person("Zacharias", "Steinmetz", role = c("aut"), - email = "steinmetz-z@uni-landau.de", + email = "z.steinmetz@rptu.de", comment = c(ORCID = "0000-0001-6675-5033")), - person("Andrew", "Gray", role = c("ctb"), + person("Nick", "Leong", role = c("aut"), + comment = c(ORCID = "0009-0008-3313-4132")), + person("Andrea","Faltynkova", role = c("aut", "dtc"), + comment = c(ORCID = "0000-0003-2523-3137")), + person("Andrew B", "Gray", role = c("ctb"), comment = c(ORCID = "0000-0003-2252-7367")), person("Hannah", "Hapich", role = c("ctb"), comment = c(ORCID = "0000-0003-0000-6632")), @@ -23,18 +27,28 @@ Authors@R: c(person("Win", "Cowger", role = c("cre", "aut"), comment = c(ORCID = "0000-0002-7624-711X")), person("Sebastian", "Primpke", role = c("ctb", "dtc"), comment = c(ORCID = "0000-0001-7633-8524")), - person("Orestis", "Herodotou", role = c("ctb", "dtc"))) + person("Orestis", "Herodotou", role = c("ctb")), + person("Mary C", "Norris", role = c("ctb")), + person("Christine M", "Knauss", role = c("ctb"), + comment = c(ORCID = "0000-0003-4404-8922")), + person("Aleksandra","Karapetrova", role = c("ctb", "dtc", "rev"), + comment = c(ORCID = "0000-0002-9856-1644")), + person("Vesna","Teofilovic", role = c("ctb"), + comment = c(ORCID = "0000-0002-3557-1482")), + person("Laura A. T.","Markley", role = c("ctb"), + comment = c(ORCID = "0000-0003-0620-8366")), + person("Shreyas","Patankar", role = c("ctb", "dtc"))) Description: Raman and (FT)IR spectral analysis tool for plastic particles and other environmental samples (Cowger et al. 2021, - ). Supported features include reading - spectral data files (.asp, .csv, .jdx, .spc, .spa, .0), Savitzky-Golay - smoothing of spectral intensities with smooth_intens(), correcting - background noise with subtr_bg() in accordance with Zhao et al. (2007) - , and identifying spectra using an onboard - reference library (Cowger et al. 2020, ). - Analyzed spectra can be shared with the Open Specy community. A Shiny app is - available via run_app() or online at - . + ). With read_any(), Open Specy provides a + single function for reading individual, batch, or map spectral data files + like .asp, .csv, .jdx, .spc, .spa, .0, and .zip. process_spec() simplifies + preprocessing spectra, including smoothing, baseline correction, + range restriction and flattening, intensity conversions, wavenumber + alignment, and min-max normalization. Spectra can be identified in batch + using an onboard reference library (Cowger et al. 2020, + ) using match_spec(). A Shiny app is available + via run_app() or online at . URL: https://github.com/wincowgerDEV/OpenSpecy-package/, http://wincowger.com/OpenSpecy-package/ BugReports: https://github.com/wincowgerDEV/OpenSpecy-package/issues/ @@ -44,32 +58,37 @@ LazyLoad: true LazyData: true VignetteBuilder: knitr Depends: - R (>= 4.0.0) + R (>= 4.1.0) Imports: - dplyr, - rlang, + methods, + data.table, + jsonlite, + yaml, osfr, + caTools, hyperSpec, - hexView, + imager, + plotly, digest, signal, + glmnet, shiny Suggests: knitr, rmarkdown, - testthat (>= 3.0.0), + testthat (>= 3.1.9), config, + qs, shinyjs, - shinythemes, - shinyBS, shinyWidgets, + bs4Dash, + dplyr, ggplot2, - plotly, - data.table, DT, curl, - rdrop2, + aws.s3, mongolite, loggit RoxygenNote: 7.2.3 +Roxygen: list(markdown = TRUE) Config/testthat/edition: 3 diff --git a/NAMESPACE b/NAMESPACE index b9b657ac..e62d5632 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,73 +1,175 @@ # Generated by roxygen2: do not edit by hand -S3method(adj_intens,data.frame) +S3method(adj_intens,OpenSpecy) S3method(adj_intens,default) -S3method(adj_intens,formula) -S3method(match_spec,data.frame) +S3method(ai_classify,OpenSpecy) +S3method(ai_classify,default) +S3method(as_OpenSpecy,OpenSpecy) +S3method(as_OpenSpecy,data.frame) +S3method(as_OpenSpecy,default) +S3method(as_OpenSpecy,hyperSpec) +S3method(as_OpenSpecy,list) +S3method(c_spec,OpenSpecy) +S3method(c_spec,default) +S3method(c_spec,list) +S3method(collapse_spec,OpenSpecy) +S3method(collapse_spec,default) +S3method(conform_spec,OpenSpecy) +S3method(conform_spec,default) +S3method(cor_spec,OpenSpecy) +S3method(cor_spec,default) +S3method(def_features,OpenSpecy) +S3method(def_features,default) +S3method(filter_spec,OpenSpecy) +S3method(filter_spec,default) +S3method(flatten_range,OpenSpecy) +S3method(flatten_range,default) +S3method(get_metadata,OpenSpecy) +S3method(get_metadata,default) +S3method(head,OpenSpecy) +S3method(heatmap_spec,OpenSpecy) +S3method(heatmap_spec,default) +S3method(interactive_plot,OpenSpecy) +S3method(interactive_plot,default) +S3method(lines,OpenSpecy) +S3method(match_spec,OpenSpecy) S3method(match_spec,default) -S3method(match_spec,formula) -S3method(share_spec,data.frame) +S3method(plot,OpenSpecy) +S3method(plotly_spec,OpenSpecy) +S3method(plotly_spec,default) +S3method(print,OpenSpecy) +S3method(process_spec,OpenSpecy) +S3method(process_spec,default) +S3method(restrict_range,OpenSpecy) +S3method(restrict_range,default) +S3method(sample_spec,OpenSpecy) +S3method(sample_spec,default) +S3method(share_spec,OpenSpecy) S3method(share_spec,default) -S3method(smooth_intens,data.frame) +S3method(sig_noise,OpenSpecy) +S3method(sig_noise,default) +S3method(smooth_intens,OpenSpecy) S3method(smooth_intens,default) -S3method(smooth_intens,formula) -S3method(subtr_bg,data.frame) -S3method(subtr_bg,default) -S3method(subtr_bg,formula) +S3method(spec_res,OpenSpecy) +S3method(spec_res,default) +S3method(subtr_baseline,OpenSpecy) +S3method(subtr_baseline,default) +S3method(summary,OpenSpecy) +S3method(write_spec,OpenSpecy) +S3method(write_spec,default) +export(OpenSpecy) export(adj_intens) export(adj_neg) +export(adj_res) +export(ai_classify) +export(as_OpenSpecy) +export(as_hyperSpec) +export(c_spec) +export(check_OpenSpecy) export(check_lib) -export(find_spec) +export(collapse_spec) +export(conform_res) +export(conform_spec) +export(cor_spec) +export(def_features) +export(filter_spec) +export(flatten_range) +export(gen_grid) export(get_lib) +export(get_metadata) +export(heatmap_spec) export(human_ts) +export(ident_spec) +export(interactive_plot) +export(is_OpenSpecy) +export(is_empty_vector) export(load_lib) export(make_rel) export(match_spec) -export(read_0) +export(max_cor_named) +export(mean_replace) +export(plotly_spec) +export(process_spec) +export(read_any) export(read_asp) +export(read_envi) export(read_extdata) export(read_jdx) +export(read_opus) +export(read_opus_raw) export(read_spa) export(read_spc) +export(read_spec) export(read_text) +export(read_zip) +export(restrict_range) +export(rm_lib) export(run_app) +export(sample_spec) export(share_spec) +export(sig_noise) export(smooth_intens) export(spec_res) -export(subtr_bg) +export(subtr_baseline) +export(write_spec) +importFrom(caTools,read.ENVI) +importFrom(data.table,":=") +importFrom(data.table,.SD) +importFrom(data.table,as.data.table) +importFrom(data.table,data.table) +importFrom(data.table,dcast) +importFrom(data.table,fifelse) +importFrom(data.table,fread) +importFrom(data.table,frollapply) +importFrom(data.table,is.data.table) +importFrom(data.table,melt) +importFrom(data.table,rbindlist) +importFrom(data.table,setDT) +importFrom(data.table,setorder) +importFrom(data.table,transpose) importFrom(digest,digest) -importFrom(dplyr,"%>%") -importFrom(dplyr,arrange) -importFrom(dplyr,desc) -importFrom(dplyr,group_by) -importFrom(dplyr,inner_join) -importFrom(dplyr,mutate) -importFrom(dplyr,select) -importFrom(dplyr,summarize) -importFrom(dplyr,top_n) -importFrom(dplyr,ungroup) -importFrom(hexView,blockString) -importFrom(hexView,readRaw) +importFrom(glmnet,predict.glmnet) +importFrom(grDevices,chull) +importFrom(graphics,matlines) +importFrom(graphics,matplot) importFrom(hyperSpec,read.jdx) importFrom(hyperSpec,read.spc) +importFrom(imager,as.cimg) +importFrom(imager,label) +importFrom(jsonlite,read_json) +importFrom(jsonlite,write_json) +importFrom(methods,new) importFrom(osfr,osf_download) importFrom(osfr,osf_ls_files) importFrom(osfr,osf_retrieve_node) -importFrom(rlang,.data) -importFrom(shiny,runApp) +importFrom(plotly,add_markers) +importFrom(plotly,add_trace) +importFrom(plotly,layout) +importFrom(plotly,plot_ly) +importFrom(plotly,subplot) +importFrom(shiny,runGitHub) importFrom(shiny,shinyOptions) importFrom(signal,filter) importFrom(signal,sgolay) importFrom(stats,approx) importFrom(stats,cor) +importFrom(stats,dist) importFrom(stats,lm) +importFrom(stats,median) importFrom(stats,model.frame) importFrom(stats,poly) +importFrom(stats,predict) importFrom(stats,sd) +importFrom(stats,setNames) importFrom(stats,terms) +importFrom(utils,head) importFrom(utils,installed.packages) +importFrom(utils,modifyList) importFrom(utils,packageVersion) importFrom(utils,read.csv) importFrom(utils,read.table) importFrom(utils,sessionInfo) +importFrom(utils,unzip) importFrom(utils,write.csv) +importFrom(yaml,read_yaml) +importFrom(yaml,write_yaml) diff --git a/NEWS.md b/NEWS.md index d1652ac1..12f37b71 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,23 @@ +# OpenSpecy 1.0.0 + +## New Features + +- Complete package, app, and SOP overhaul! +- The Shiny app has been outsourced to an own GitHub repository: + https://github.com/wincowgerDEV/OpenSpecy-shiny +- Spectra are now stored in dedicated `OpenSpecy` objects, which can be managed + with a set of new functions including `c_spec()` for concatenating spectra +- Various functions have been renamed and improved, for instance, to facilitate + reading (and writing) spectral files +- New functions include `def_features()` to identify microplastics in spectral + maps and `ai_classify()` to use AI for matching/identifying spectra + +## Minor Improvements + +- Added pkgdown documentation +- Added code coverage tests + + # OpenSpecy 0.9.5 ## Bug Fixes diff --git a/R/adj_intens.R b/R/adj_intens.R index 4bf1d067..a1ded912 100644 --- a/R/adj_intens.R +++ b/R/adj_intens.R @@ -1,12 +1,12 @@ -#' @title Adjust spectral intensities to absorbance units +#' @rdname adj_intens +#' @title Adjust spectral intensities to absorbance units. #' #' @description #' Converts reflectance or transmittance intensity units to absorbance units. #' #' @details #' Many of the Open Specy functions will assume that the spectrum is in -#' absorbance units. For example, see \code{\link{match_spec}()} and -#' \code{\link{subtr_bg}()}. +#' absorbance units. For example, see \code{\link{subtr_baseline}()}. #' To run those functions properly, you will need to first convert any spectra #' from transmittance or reflectance to absorbance using this function. #' The transmittance adjustment uses the \eqn{log10(1 / T)} @@ -16,19 +16,15 @@ #' is a percent from 1-100 and first correct the intensity by dividing by 100 #' so that it fits the form expected by the equation. #' -#' @param x a numeric vector containing the spectral wavenumbers; alternatively -#' a data frame containing spectral data as \code{"wavenumber"} and -#' \code{"intensity"} can be supplied. -#' @param y a numeric vector containing the spectral intensities. -#' @param formula an object of class '\code{\link[stats]{formula}}' of the form -#' \code{intensity ~ wavenumber}. -#' @param data a data frame containing the variables in \code{formula}. +#' @param x a list object of class \code{OpenSpecy}. #' @param type a character string specifying whether the input spectrum is #' in absorbance units (\code{"none"}, default) or needs additional conversion #' from \code{"reflectance"} or \code{"transmittance"} data. #' @param make_rel logical; if \code{TRUE} spectra are automatically normalized #' with \code{\link{make_rel}()}. -#' @param \ldots further arguments passed to the submethods. +#' @param \ldots further arguments passed to submethods; this is +#' to \code{\link{adj_neg}()} for \code{adj_intens()} and +#' to \code{\link{conform_res}()} for \code{conform_intens()}. #' #' @return #' \code{adj_intens()} returns a data frame containing two columns @@ -43,11 +39,9 @@ #' Win Cowger, Zacharias Steinmetz #' #' @seealso -#' \code{\link{subtr_bg}()} for spectral background correction; -#' \code{\link{match_spec}()} matches spectra with the Open Specy or other -#' reference libraries +#' \code{\link{subtr_baseline}()} for spectral background correction. #' -#' @importFrom dplyr %>% +#' @importFrom data.table .SD #' @export adj_intens <- function(x, ...) { UseMethod("adj_intens") @@ -56,39 +50,23 @@ adj_intens <- function(x, ...) { #' @rdname adj_intens #' #' @export -adj_intens.formula <- function(formula, data = NULL, ...) { - if (missing(formula) || (length(formula) != 3L) || - (length(attr(terms(formula[-2L]), "term.labels")) != 1L)) - stop("'formula' missing or incorrect") - - mf <- model.frame(formula, data) - lst <- as.list(mf) - names(lst) <- c("y", "x") - - do.call("adj_intens", c(lst, list(...))) +adj_intens.default <- function(x, ...) { + stop("object 'x' needs to be of class 'OpenSpecy'", call. = F) } #' @rdname adj_intens #' #' @export -adj_intens.data.frame <- function(x, ...) { - if (!all(c("wavenumber", "intensity") %in% names(x))) - stop("'data' must contain 2 columns named 'wavenumber' and 'intensity'") - - do.call("adj_intens", list(x$wavenumber, x$intensity, ...)) -} +adj_intens.OpenSpecy <- function(x, type = "none", make_rel = TRUE, ...) { + spec <- x$spectra -#' @rdname adj_intens -#' -#' @export -adj_intens.default <- function(x, y, type = "none", make_rel = TRUE, - ...) { - yadj <- switch(type, - "reflectance" = (1 - y/100)^2 / (2 * y/100), - "transmittance" = log10(1/adj_neg(y)), - "none" = adj_neg(y) + adj <- switch(type, + "reflectance" = (1 - spec/100)^2 / (2 * spec/100), + "transmittance" = log10(1/adj_neg(spec, ...)), + "none" = adj_neg(spec, ...) ) - if (make_rel) yout <- make_rel(yadj) else yout <- yadj - data.frame(wavenumber = x, intensity = yout) + if (make_rel) x$spectra <- adj[, lapply(.SD, make_rel)] else x$spectra <- adj + + return(x) } diff --git a/R/adj_range.R b/R/adj_range.R new file mode 100644 index 00000000..9621c9e3 --- /dev/null +++ b/R/adj_range.R @@ -0,0 +1,121 @@ +#' @rdname adj_range +#' @title Range restriction and flattening for spectra +#' +#' @description +#' \code{restrict_range()} restricts wavenumber ranges to user specified values. +#' Multiple ranges can be specified by inputting a series of max and min +#' values in order. +#' \code{flatten_range()} will flatten ranges of the spectra that should have no +#' peaks. +#' Multiple ranges can be specified by inputting the series of max and min +#' values in order. +#' +#' @param x an \code{OpenSpecy} object. +#' @param min a vector of minimum values for the range to be flattened. +#' @param max a vector of maximum values for the range to be flattened. +#' @param make_rel logical; should the output intensities be normalized to the +#' range \[0, 1\] using `make_rel()` function? +#' @param \ldots additional arguments passed to subfunctions; currently not +#' in use. +#' +#' @return +#' An \code{OpenSpecy} object with the spectral intensities within specified +#' ranges restricted or flattened. +#' +#' @examples +#' test_noise <- as_OpenSpecy(x = seq(400,4000, by = 10), +#' spectra = data.frame(intensity = rnorm(361))) +#' plot(test_noise) +#' +#' restrict_range(test_noise, min = 1000, max = 2000) +#' +#' flattened_intensities <- flatten_range(test_noise, min = c(1000, 2000), +#' max = c(1500, 2500)) +#' plot(flattened_intensities) +#' +#' @author +#' Win Cowger, Zacharias Steinmetz +#' +#' @seealso +#' \code{\link{conform_spec}()} for conforming wavenumbers to be matched with +#' a reference library; +#' \code{\link{adj_intens}()} for log transformation functions; +#' \code{\link[base]{min}()} and \code{\link[base]{round}()} +#' +#' @importFrom data.table as.data.table .SD +#' @export +restrict_range <- function(x, ...) { + UseMethod("restrict_range") +} + +#' @rdname adj_range +#' +#' @export +restrict_range.default <- function(x, ...) { + stop("object 'x' needs to be of class 'OpenSpecy'") +} + +#' @rdname adj_range +#' +#' @export +restrict_range.OpenSpecy <- function(x, min, max, make_rel = TRUE, + ...) { + test <- as.data.table(lapply(1:length(min), function(y){ + x$wavenumber >= min[y] & x$wavenumber <= max[y]}) + ) + + vals <- rowSums(test) > 0 + filt <- x$spectra[vals,] + x$wavenumber <- x$wavenumber[vals] + + if (make_rel) x$spectra <- filt[, lapply(.SD, make_rel)] else x$spectra <- filt + + return(x) +} + +#' @rdname adj_range +#' +#' @export +flatten_range <- function(x, ...) { + UseMethod("flatten_range") +} + +#' @rdname adj_range +#' +#' @export +flatten_range.default <- function(x, ...) { + stop("object 'x' needs to be of class 'OpenSpecy'") +} + +#' @rdname adj_range +#' +#' @export +flatten_range.OpenSpecy <- function(x, min = 2200, max = 2400, make_rel = TRUE, + ...) { + if(length(min) != length(max)) { + stop("min and max need to be the same length", call. = F) + } + if(any(vapply(1:length(min), function(y) { + min[y] > max[y] + }, FUN.VALUE = logical(1)))) { + stop("all min values must be lower than corresponding max", call. = F) + } + flat <- x$spectra[, lapply(.SD, .flatten_range, x = x$wavenumber, + min = min, max = max)] + + if (make_rel) x$spectra <- flat[, lapply(.SD, make_rel)] else x$spectra <- flat + + return(x) +} + +.flatten_range <- function(y, x, min, max) { + if(all(min > max(x)) || all(max < min(x))) + stop("'min' or 'max' out of range") + + for(i in 1:length(min)) { + y[x >= min[i] & x <= max[i]] <- + mean(c(y[min(which(x >= min[i]))], + y[max(which(x <= max[i]))])) + } + return(y) +} diff --git a/R/as_OpenSpecy.R b/R/as_OpenSpecy.R new file mode 100644 index 00000000..542648c3 --- /dev/null +++ b/R/as_OpenSpecy.R @@ -0,0 +1,368 @@ +#' @rdname as_OpenSpecy +#' +#' @title Create \code{OpenSpecy} objects +#' +#' @description +#' Functions to check if an object is an OpenSpecy, or coerce it if +#' possible. +#' +#' @param x depending on the method, a list with all OpenSpecy parameters, +#' a vector with the wavenumbers for all spectra, or a data.frame with a full +#' spectrum in the classic Open Specy format. +#' @param spectra spectral intensities formatted as a data.table with one column +#' per spectrum. +#' @param metadata metadata for each spectrum with one row per spectrum, +#' see details. +#' @param coords spatial coordinates for the spectra. +#' @param session_id logical. Whether to add a session ID to the metadata. +#' The session ID is based on current session info so metadata of the same +#' spectra will not return equal if session info changes. Sometimes that is +#' desirable. +#' @param colnames names of the wavenumber column and spectra column, makes +#' assumptions based on column names or placement if \code{NULL}. +#' @param n number of spectra to generate the spatial coordinate grid with. +#' @param \ldots additional arguments passed to submethods. +#' +#' @details +#' \code{as_OpenSpecy()} converts spectral datasets to a three part list; +#' the first with a vector of the wavenumbers of the spectra, +#' the second with a \code{data.table} of all spectral intensities ordered as +#' columns, +#' the third item is another \code{data.table} with any metadata the user +#' provides or is harvested from the files themselves. +#' +#' The \code{metadata} argument may contain a named list with the following +#' details (\code{*} = minimum recommended): +#' +#' \tabular{ll}{ +#' \code{file_name*}: \tab The file name, defaults to +#' \code{\link[base]{basename}()} if not specified\cr +#' \code{user_name*}: \tab User name, e.g. "Win Cowger"\cr +#' \code{contact_info}: \tab Contact information, e.g. "1-513-673-8956, +#' wincowger@@gmail.com"\cr +#' \code{organization}: \tab Affiliation, e.g. "University of California, +#' Riverside"\cr +#' \code{citation}: \tab Data citation, e.g. "Primpke, S., Wirth, M., Lorenz, +#' C., & Gerdts, G. (2018). Reference database design for the automated analysis +#' of microplastic samples based on Fourier transform infrared (FTIR) +#' spectroscopy. \emph{Analytical and Bioanalytical Chemistry}. +#' \doi{10.1007/s00216-018-1156-x}"\cr +#' \code{spectrum_type*}: \tab Raman or FTIR\cr +#' \code{spectrum_identity*}: \tab Material/polymer analyzed, e.g. +#' "Polystyrene"\cr +#' \code{material_form}: \tab Form of the material analyzed, e.g. textile fiber, +#' rubber band, sphere, granule \cr +#' \code{material_phase}: \tab Phase of the material analyzed (liquid, gas, +#' solid) \cr +#' \code{material_producer}: \tab Producer of the material analyzed, +#' e.g. Dow \cr +#' \code{material_purity}: \tab Purity of the material analyzed, e.g. 99.98% +#' \cr +#' \code{material_quality}: \tab Quality of the material analyzed, e.g. +#' consumer product, manufacturer material, analytical standard, +#' environmental sample \cr +#' \code{material_color}: \tab Color of the material analyzed, +#' e.g. blue, #0000ff, (0, 0, 255) \cr +#' \code{material_other}: \tab Other material description, e.g. 5 µm diameter +#' fibers, 1 mm spherical particles \cr +#' \code{cas_number}: \tab CAS number, e.g. 9003-53-6 \cr +#' \code{instrument_used}: \tab Instrument used, e.g. Horiba LabRam \cr +#' \code{instrument_accessories}: \tab Instrument accessories, e.g. +#' Focal Plane Array, CCD\cr +#' \code{instrument_mode}: \tab Instrument modes/settings, e.g. +#' transmission, reflectance \cr +#' \code{intensity_units*}: \tab Units of the intensity values for the spectrum, options +#' transmittance, reflectance, absorbance \cr +#' \code{spectral_resolution}: \tab Spectral resolution, e.g. 4/cm \cr +#' \code{laser_light_used}: \tab Wavelength of the laser/light used, e.g. +#' 785 nm \cr +#' \code{number_of_accumulations}: \tab Number of accumulations, e.g 5 \cr +#' \code{total_acquisition_time_s}: \tab Total acquisition time (s), e.g. 10 s +#' \cr +#' \code{data_processing_procedure}: \tab Data processing procedure, +#' e.g. spikefilter, baseline correction, none \cr +#' \code{level_of_confidence_in_identification}: \tab Level of confidence in +#' identification, e.g. 99% \cr +#' \code{other_info}: \tab Other information \cr +#' \code{license}: \tab The license of the shared spectrum; defaults to +#' \code{"CC BY-NC"} (see +#' \url{https://creativecommons.org/licenses/by-nc/4.0/} for details). Any other +#' creative commons license is allowed, for example, CC0 or CC BY \cr +#' \code{session_id}: \tab A unique user and session identifier; populated +#' automatically with \code{paste(digest(Sys.info()), digest(sessionInfo()), +#' sep = "/")}\cr +#' \code{file_id}: \tab A unique file identifier; populated automatically +#' with \code{digest(object[c("wavenumber", "spectra")])}\cr +#' } +#' +#' @return +#' \code{as_OpenSpecy()} and \code{OpenSpecy()} returns three part lists +#' described in details. +#' \code{is_OpenSpecy()} returns \code{TRUE} if the object is an OpenSpecy and +#' \code{FALSE} if not. +#' \code{gen_grid()} returns a \code{data.table} with \code{x} and \code{y} +#' coordinates to use for generating a spatial grid for the spectra if one is +#' not specified in the data. +#' +#' @examples +#' data("raman_hdpe") +#' +#' # Inspect the spectra +#' raman_hdpe # See how OpenSpecy objects print. +#' raman_hdpe$wavenumber # Look at just the wavenumbers of the spectra. +#' raman_hdpe$spectra # Look at just the spectral intensities data.table. +#' raman_hdpe$metadata # Look at just the metadata of the spectra. +#' +#' # Creating a list and transforming to OpenSpecy +#' as_OpenSpecy(list(wavenumber = raman_hdpe$wavenumber, +#' spectra = raman_hdpe$spectra, +#' metadata = raman_hdpe$metadata[,-c("x", "y")])) +#' +#' # If you try to produce an OpenSpecy using an OpenSpecy it will just return +#' # the same object. +#' as_OpenSpecy(raman_hdpe) +#' +#' # Creating an OpenSpecy from a data.frame +#' as_OpenSpecy(x = data.frame(wavenumber = raman_hdpe$wavenumber, +#' spectra = raman_hdpe$spectra$intensity)) +#' +#' # Test that the spectrum is formatted as an OpenSpecy object. +#' is_OpenSpecy(raman_hdpe) #should be TRUE +#' is_OpenSpecy(raman_hdpe$spectra) #should be FALSE +#' +#' @author +#' Zacharias Steinmetz, Win Cowger +#' +#' @seealso +#' \code{\link{read_spec}()} for reading \code{OpenSpecy} objects. +#' +#' @importFrom data.table as.data.table +#' @export +as_OpenSpecy <- function(x, ...) { + UseMethod("as_OpenSpecy") +} + +#' @rdname as_OpenSpecy +#' +#' @export +as_OpenSpecy.OpenSpecy <- function(x, session_id = FALSE, ...) { + if(session_id) + x$metadata$session_id <- paste(digest(Sys.info()), + digest(sessionInfo()), + sep = "/") + if(!c("file_id") %in% names(x$metadata)) + x$metadata$file_id = digest(x[c("wavenumber", "spectra")]) + + return(x) +} + +#' @rdname as_OpenSpecy +#' +#' @export +as_OpenSpecy.list <- function(x, ...) { + do.call("as_OpenSpecy", unname(x)) +} + +#' @rdname as_OpenSpecy +#' +#' @export +as_OpenSpecy.hyperSpec <- function(x, ...) { + do.call("as_OpenSpecy", list(x = x@wavelength, + spectra = as.data.table(t(x$spc)), ...)) +} + +#' @rdname as_OpenSpecy +#' +#' @export +as_OpenSpecy.data.frame <- function(x, colnames = list(wavenumber = NULL, + spectra = NULL), ...) { + x <- as.data.table(x) + + # Try to find spectral data + if (is.null(colnames$wavenumber)) { + if (any(grepl("wav", ignore.case = T, names(x)))) { + if (length(grep("wav", ignore.case = T, names(x))) > 1L) + warning("Ambiguous column names: taking 'wavenumber' data from the", + " first column; use 'colnames' to supply user-defined columns", + call. = F) + wavenumber <- x[[grep("wav", ignore.case = T, names(x))[1L]]] + wn <- names(x)[grep("wav", ignore.case = T, names(x))] + } else { + warning("Ambiguous column names: taking 'wavenumber' data from the", + " first column; use 'colnames' to supply user-defined columns", + call. = F) + wavenumber <- x[[1L]] + wn <- names(x)[1L] + } + } else { + wavenumber <- x[[colnames$wavenumber]] + wn <- colnames$wavenumber + } + + if (is.null(colnames$spectra)) { + if (any(grep("(transmit.*)|(reflect.*)|(abs.*)|(intens.*)", ignore.case = T, + names(x)))) { + spectra <- x[, grepl("(transmit.*)|(reflect.*)|(abs.*)|(intens.*)", + ignore.case = T, names(x)), with = F] + } else { + warning("Ambiguous column names: taking 'spectra' data from all but the", + " 'wavenumber' column; use 'colnames' to supply user-defined", + " columns", call. = F) + spectra <- x[, -wn, with = F] + } + } else { + spectra <- x[, colnames$spectra, with = F] + } + + do.call("as_OpenSpecy", + list(x = wavenumber, spectra = spectra, ...)) +} + +#' @rdname as_OpenSpecy +#' +#' @export +as_OpenSpecy.default <- function(x, spectra, + metadata = list( + file_name = NULL, + user_name = NULL, + contact_info = NULL, + organization = NULL, + citation = NULL, + spectrum_type = NULL, + spectrum_identity = NULL, + material_form = NULL, + material_phase = NULL, + material_producer = NULL, + material_purity = NULL, + material_quality = NULL, + material_color = NULL, + material_other = NULL, + cas_number = NULL, + instrument_used = NULL, + instrument_accessories = NULL, + instrument_mode = NULL, + intensity_units = NULL, + spectral_resolution = NULL, + laser_light_used = NULL, + number_of_accumulations = NULL, + total_acquisition_time_s = NULL, + data_processing_procedure = NULL, + level_of_confidence_in_identification = NULL, + other_info = NULL, + license = "CC BY-NC"), + coords = "gen_grid", + session_id = FALSE, + ...) { + if (!is.numeric(x) || !is.vector(x)) + stop("'x' must be numeric vector", call. = F) + if (!inherits(spectra, c("data.frame", "matrix"))) + stop("'spectra' must inherit from data.frame or matrix", call. = F) + if (!sapply(spectra, is.numeric)[1L] && !sapply(spectra, is.complex)[1L] && + !sapply(spectra, is.logical)[1L]) + stop("at least the first column of 'spectra' must be numeric or logical", + call. = F) + if(length(unique(names(spectra))) != ncol(spectra)) + stop("column names in 'spectra' must be unique", call. = F) + if (length(x) != nrow(spectra)) + stop("'x' and 'spectra' must be of equal length", call. = F) + + obj <- structure(list(), class = c("OpenSpecy", "list")) + + obj$wavenumber <- x[order(x)] + + obj$spectra <- as.data.table(spectra)[order(x)] + + if (inherits(coords, "character")) { + obj$metadata <- do.call(coords, list(ncol(obj$spectra))) + } else if (inherits(coords, c("data.frame", "list")) && + all(is.element(c("x", "y"), names(coords)))) { + obj$metadata <- as.data.table(coords) + } else if(is.null(coords)){ + obj$metadata <- data.table() + } + else { + stop("inconsistent input for 'coords'", call. = F) + } + if (!is.null(metadata)) { + if (inherits(metadata, c("data.frame", "list"))) { + obj$metadata <- cbind(obj$metadata, as.data.table(metadata)) + if(session_id) + obj$metadata$session_id <- paste(digest(Sys.info()), + digest(sessionInfo()), + sep = "/") + if(!c("file_id") %in% names(obj$metadata)) + obj$metadata$file_id = digest(obj[c("wavenumber", "spectra")]) + if(!c("col_id") %in% names(obj$metadata)) + obj$metadata$col_id = names(obj$spectra) + } else { + stop("inconsistent input for 'metadata'", call. = F) + } + } + + return(obj) +} + +#' @rdname as_OpenSpecy +#' +#' @export +is_OpenSpecy <- function(x) { + inherits(x, "OpenSpecy") +} + +#' @rdname as_OpenSpecy +#' +#' @importFrom data.table is.data.table +#' @export +check_OpenSpecy <- function(x) { + if(!(is_OpenSpecy(x))) + stop("object 'x' is not of class 'OpenSpecy'", call. = F) + if(!identical(names(x), c("wavenumber", "spectra", "metadata"))) + stop("names of the object components are incorrect", call. = F) + + if(!(cw <- is.vector(x$wavenumber))) + warning("Wavenumber is not a vector", call. = F) + if(!(cs <- is.data.table(x$spectra))) + message("Spectra are not of class 'data.table'") + if(!(cm <- is.data.table(x$metadata))) + message("Metadata are not a 'data.table'") + if(!(cr <- ncol(x$spectra) == nrow(x$metadata))) + warning("Number of columns in spectra is not equal to number of rows ", + "in metadata", call. = F) + if(!(cl <- length(x$wavenumber) == nrow(x$spectra))) + warning("Length of wavenumber is not equal to number of rows in spectra", + call. = F) + if(!(cu <- length(unique(names(x$spectra))) == ncol(x$spectra))) + warning("Column names in spectra are not unique", call. = F) + if(!(cv <- length(unique(names(x$metadata))) == ncol(x$metadata))) + message("Column names in metadata are not unique") + if(!(co <- identical(order(x$wavenumber), 1:length(x$wavenumber)) | + identical(order(x$wavenumber), length(x$wavenumber):1))) + message("This is technically an 'OpenSpecy' object but wavenumbers ", + "should be a continuous sequence for all OpenSpecy functions to ", + "run smoothly") + + chk <- all(cw, cs, cm, cr, cl, cu, cv, co) + + return(chk) +} + +#' @rdname as_OpenSpecy +#' +#' @export +OpenSpecy <- function(x, ...) { + if (is_OpenSpecy(x)) { + return(x) + } else { + do.call("as_OpenSpecy", list(x, ...)) + } +} + +#' @rdname as_OpenSpecy +#' +#' @export +gen_grid <- function(n) { + base <- sqrt(n) + + expand.grid(x = 1:ceiling(base), y = 1:ceiling(base))[1:n,] |> + as.data.table() +} diff --git a/R/conform_spec.R b/R/conform_spec.R new file mode 100644 index 00000000..04762c48 --- /dev/null +++ b/R/conform_spec.R @@ -0,0 +1,86 @@ +#' @rdname conform_spec +#' @title Conform spectra to a standard wavenumber series +#' +#' @description +#' Spectra can be conformed to a standard suite of wavenumbers to be compared +#' with a reference library or to be merged to other spectra. +#' +#' @param x a list object of class \code{OpenSpecy}. +#' @param range a vector of new wavenumber values, can be just supplied as a +#' min and max value. +#' @param res spectral resolution adjusted to or \code{NULL} if the raw range +#' should be used. +#' @param type the type of wavenumber adjustment to make. \code{"interp"} +#' results in linear interpolation while \code{"roll"} conducts a nearest +#' rolling join of the wavenumbers. +#' @param \ldots further arguments passed to \code{\link[stats]{approx}()} +#' +#' @return +#' \code{adj_intens()} returns a data frame containing two columns +#' named \code{"wavenumber"} and \code{"intensity"} +#' +#' @examples +#' data("raman_hdpe") +#' conform_spec(raman_hdpe, c(1000, 2000)) +#' +#' @author +#' Win Cowger, Zacharias Steinmetz +#' +#' @seealso +#' \code{\link{restrict_range}()} and \code{\link{flatten_range}()} for +#' adjusting wavenumber ranges; +#' \code{\link{subtr_baseline}()} for spectral background correction +#' +#' @importFrom data.table .SD +#' @export +conform_spec <- function(x, ...) { + UseMethod("conform_spec") +} + +#' @rdname conform_spec +#' +#' @export +conform_spec.default <- function(x, ...) { + stop("object 'x' needs to be of class 'OpenSpecy'", call. = F) +} + +#' @rdname conform_spec +#' +#' @export +conform_spec.OpenSpecy <- function(x, range = NULL, res = 5, type = "interp", + ...) { + if(!any(type %in% c("interp", "roll"))) + stop("type must be either interp or roll") + + if(is.null(range)) range <- x$wavenumber + + if(!is.null(res)) { + range <- c(max(min(range), min(x$wavenumber)), + min(max(range), max(x$wavenumber))) + + wn <- conform_res(range, res = res) + } else { + wn <- range + } + + if(type == "interp") + spec <- x$spectra[, lapply(.SD, .conform_intens, x = x$wavenumber, + xout = wn, ...)] + + if(type == "roll") { + join <- data.table("wavenumber" = wn) + # Rolling join option + spec <- x$spectra[,"wavenumber" := x$wavenumber] + spec <- spec[join, roll = "nearest", on = "wavenumber"] + spec <- spec[,-"wavenumber"] + } + + x$wavenumber <- wn + x$spectra <- spec + + return(x) +} + +.conform_intens <- function(...) { + approx(...)$y +} diff --git a/R/data_norm.R b/R/data_norm.R index 29ef6dbd..a6ea0cf1 100644 --- a/R/data_norm.R +++ b/R/data_norm.R @@ -1,33 +1,45 @@ #' @rdname data_norm -#' -#' @title Normalization of spectral data +#' @title Normalization and conversion of spectral data #' #' @description -#' \code{adj_neg()} converts numeric values \code{x} < 1 into values -#' >= 1, keeping absolute differences between values by shifting intensity -#' values with the value of the smallest number. -#' \code{make_rel()} converts values \code{x} into relative values between +#' \code{adj_res()} and \code{conform_res()} are helper functions to align +#' wavenumbers in terms of their spectral resolution. +#' \code{adj_neg()} converts numeric intensities \code{y} < 1 into values >= 1, +#' keeping absolute differences between intensity values by shifting each value +#' by the minimum intensity. +#' \code{make_rel()} converts intensities \code{y} into relative values between #' 0 and 1 using the standard normalization equation. #' If \code{na.rm} is \code{TRUE}, missing values are removed before the #' computation proceeds. #' #' @details -#' \code{adj_neg()} is used in Open Specy to avoid errors that could -#' arise from log transforming spectra when using -#' \code{\link{adj_intens}()} and other functions. -#' \code{make_rel()} is used in Open Specy to retain the relative -#' height proportions between spectra while avoiding the large numbers that can -#' result from some spectral instruments. +#' \code{adj_res()} and \code{conform_res()} are used in Open Specy to +#' facilitate comparisons of spectra with different resolutions. +#' \code{adj_neg()} is used to avoid errors that could arise from log +#' transforming spectra when using \code{\link{adj_intens}()} and other +#' functions. +#' \code{make_rel()} is used to retain the relative height proportions between +#' spectra while avoiding the large numbers that can result from some spectral +#' instruments. #' #' @param x a numeric vector or an \R object which is coercible to one by -#' \code{as.vector(x, "numeric")}; \code{x} should be \code{intensity} data. +#' \code{as.vector(x, "numeric")}; \code{x} should contain the spectral +#' wavenumbers. +#' @param y a numeric vector containing the spectral intensities. +#' @param res spectral resolution supplied to \code{fun}. +#' @param fun the function to be applied to each element of \code{x}; defaults +#' to \code{\link[base]{round}()} to round to a specific resolution \code{res}. #' @param na.rm logical. Should missing values be removed? #' #' @return +#' \code{adj_res()} and \code{conform_res()} return a numeric vector with +#' resolution-conformed wavenumbers. #' \code{adj_neg()} and \code{make_rel()} return numeric vectors -#' with the normalized data. +#' with the normalized intensity data. #' #' @examples +#' adj_res(seq(500, 4000, 4), 5) +#' conform_res(seq(500, 4000, 4)) #' adj_neg(c(-1000, -1, 0, 1, 10)) #' make_rel(c(-1000, -1, 0, 1, 10)) #' @@ -35,22 +47,72 @@ #' Win Cowger, Zacharias Steinmetz #' #' @seealso -#' \code{\link[base]{min}()} for the calculation of minima; -#' \code{\link{adj_intens}()} for log transformation functions +#' \code{\link[base]{min}()} and \code{\link[base]{round}()}; +#' \code{\link{adj_intens}()} for log transformation functions; +#' \code{\link{conform_spec}()} for conforming wavenumbers of an +#' \code{OpenSpecy} object to be matched with a reference library +#' +#' @export +adj_res <- function(x, res = 1, fun = round) { + fun(x / res) * res +} + +#' @rdname data_norm #' -#' @importFrom dplyr %>% #' @export -adj_neg <- function(x, na.rm = FALSE) { - if (min(x, na.rm = na.rm) < 1) { - x + min(x, na.rm = na.rm) %>% abs() + 1 +conform_res <- function(x, res = 5) { + seq(adj_res(min(x), res, ceiling), adj_res(max(x), res, floor), by = res) +} + +#' @rdname data_norm +#' +#' @export +adj_neg <- function(y, na.rm = FALSE) { + if (min(y, na.rm = na.rm) < 1) { + y + min(y, na.rm = na.rm) |> abs() + 1 } else { - x + y } } #' @rdname data_norm #' #' @export -make_rel <- function(x, na.rm = FALSE) { - (x - min(x, na.rm = na.rm)) / (max(x, na.rm = na.rm) - min(x, na.rm = na.rm)) +make_rel <- function(y, na.rm = FALSE) { + r <- range(y, na.rm = na.rm) + + (y - r[1]) / (r[2] - r[1]) +} + +#' @rdname data_norm +#' @importFrom data.table fifelse +#' +#' @export +mean_replace <- function(y, na.rm = TRUE) { + m <- mean(y, na.rm = na.rm) + + fifelse(is.na(y), m, y) +} + +#' @rdname data_norm +#' +#' @export +is_empty_vector <- function(x) { + # Check if the vector is NULL or has zero length + if (is.null(x) || length(x) == 0) { + return(TRUE) + } + + # Check if all values are NA or NaN (for numeric vectors) + if (is.numeric(x)) { + return(all(is.na(x) | is.nan(x))) + } + + # Check if all values are NA or empty strings (for character vectors) + if (is.character(x)) { + return(all(is.na(x) | x == "")) + } + + # Check if all values are NA (for other types of vectors) + return(all(is.na(x))) } diff --git a/R/def_features.R b/R/def_features.R new file mode 100644 index 00000000..a587f5e3 --- /dev/null +++ b/R/def_features.R @@ -0,0 +1,189 @@ +#' @rdname def_features +#' @title Define features +#' +#' @description +#' Functions for analyzing features, like particles, fragments, or fibers, in +#' spectral map oriented \code{OpenSpecy} object. +#' +#' @details +#' `def_features()` accepts an \code{OpenSpecy} object and a logical or +#' character vector describing which pixels correspond to particles. +#' `collapse_spec()` takes an \code{OpenSpecy} object with particle-specific +#' metadata (from `def_features()`) and collapses the spectra to median +#' intensities for each unique particle. +#' It also updates the metadata with centroid coordinates, while preserving the +#' feature information on area and Feret max. +#' +#' @return +#' An \code{OpenSpecy} object appended with metadata about the features or +#' collapsed for the features. +#' +#' @examples +#' # Logical input +#' map <- read_extdata("CA_tiny_map.zip") |> read_any() +#' map$metadata$features <- map$metadata$x == 0 +#' identified_map <- def_features(map, map$metadata$features) +#' test_collapsed <- collapse_spec(identified_map) +#' +#' # Character input +#' map <- read_extdata("CA_tiny_map.zip") |> read_any() +#' map$metadata$features <- ifelse(map$metadata$x == 1, "particle", +#' "not_particle") +#' identified_map <- def_features(map, map$metadata$features) +#' test_collapsed <- collapse_spec(identified_map) +#' +#' @param x an \code{OpenSpecy} object +#' @param features a logical vector or character vector describing which of the +#' spectra are of features (\code{TRUE}) and which are not (\code{FALSE}). +#' If a character vector is provided, it should represent the different feature +#' types present in the spectra. +#' @param \ldots additional arguments passed to subfunctions. +#' +#' @author +#' Win Cowger, Zacharias Steinmetz +#' +#' @importFrom data.table data.table as.data.table setDT rbindlist transpose .SD := +#' @export +collapse_spec <- function(x, ...) { + UseMethod("collapse_spec") +} + +#' @rdname def_features +#' +#' @export +collapse_spec.default <- function(x, ...) { + stop("object 'x' needs to be of class 'OpenSpecy'") +} + +#' @rdname def_features +#' +#' @export +collapse_spec.OpenSpecy <- function(x, ...) { + # Calculate the median spectra for each unique feature_id + ts <- transpose(x$spectra) + ts$id <- x$metadata$feature_id + x$spectra <- ts[, lapply(.SD, median, na.rm = T), by = "id"] |> + transpose(make.names = "id") + + x$metadata <- x$metadata |> + unique(by = c("feature_id", "area", "feret_max", "centroid_y", + "centroid_x")) + + return(x) +} + +#' @rdname def_features +#' +#' @export +def_features <- function(x, ...) { + UseMethod("def_features") +} + +#' @rdname def_features +#' +#' @export +def_features.default <- function(x, ...) { + stop("object 'x' needs to be of class 'OpenSpecy'") +} + +#' @rdname def_features +#' +#' @importFrom imager label as.cimg +#' @importFrom data.table as.data.table setDT rbindlist data.table +#' @export +def_features.OpenSpecy <- function(x, features, ...) { + if(is.logical(features)) { + if(all(features) | all(!features)) + stop("features cannot be all TRUE or FALSE because that would indicate ", + "that there are no distinct features", call. = F) + + features_df <- .def_features(x, features) + } else if(is.character(features)) { + if(length(unique(features)) == 1) + stop("features cannot all have a single name because that would ", + "indicate that there are no distinct features", call. = F) + + features_df <- rbindlist(lapply(unique(features), + function(y) .def_features(x, features == y)) + ) + } else { + stop("features needs to be a character or logical vector", call. = F) + } + + obj <- x + x <- y <- feature_id <- NULL # workaround for data.table non-standard + # evaluation + md <- features_df[setDT(obj$metadata), on = c("x", "y")] + md[, feature_id := ifelse(is.na(feature_id), "-88", feature_id)] + md[, "centroid_x" := mean(x), by = "feature_id"] + md[, "centroid_y" := mean(y), by = "feature_id"] + + obj$metadata <- md + + return(obj) +} + +#' @importFrom grDevices chull +#' @importFrom stats dist +.def_features <- function(x, binary, name = NULL) { + # Label connected components in the binary image + binary_matrix <- matrix(binary, ncol = max(x$metadata$y) + 1, byrow = T) + labeled_image <- imager::label(imager::as.cimg(binary_matrix), + high_connectivity = T) + + # Create a dataframe with feature IDs for each true pixel + feature_points_dt <- data.table(x = x$metadata$x, + y = x$metadata$y, + feature_id = ifelse(binary_matrix, + labeled_image, -88) |> + t() |> as.vector() |> as.character()) + + # Apply the logic to clean components + cleaned_components <- ifelse(binary_matrix, labeled_image, -88) + + # Calculate the convex hull for each feature + convex_hulls <- lapply( + split( + as.data.frame(which(cleaned_components >= 0, arr.ind = TRUE)), + cleaned_components[cleaned_components >= 0] + ), + function(coords) {coords[unique(chull(coords[,2], coords[,1])),] + }) + + # Calculate area, Feret max, and feature IDs for each feature + features_dt <- rbindlist(lapply(seq_along(convex_hulls), function(i) { + hull <- convex_hulls[[i]] + id <- names(convex_hulls)[i] + + # Calculate Feret dimensions + dist_matrix <- as.matrix(dist(hull)) + feret_max <- max(dist_matrix) + 1 + + perimeter <- 0 + cols = 1:nrow(hull) + rows = c(2:nrow(hull), 1) + for (j in 1:length(cols)) { + # Fetch the distance from the distance matrix + perimeter <- perimeter + dist_matrix[rows[j], cols[j]] + } + + # Area + area <- sum(cleaned_components == as.integer(id)) + + feret_min = area/feret_max #Can probably calculate this better. + + data.table(feature_id = id, + area = area, + perimeter = perimeter, + feret_min = feret_min, + feret_max = feret_max + ) + }), fill = T) + + # Join with the coordinates from the binary image + if (!is.null(name)) { + features_dt$feature_id <- paste0(name, "_", features_dt$feature_id) + } + + feature_points_dt[features_dt, on = "feature_id"] +} diff --git a/R/gen_OpenSpecy.R b/R/gen_OpenSpecy.R new file mode 100644 index 00000000..25b9f267 --- /dev/null +++ b/R/gen_OpenSpecy.R @@ -0,0 +1,106 @@ +#' @title Generic Open Specy Methods +#' @rdname gen_OpenSpecy +#' +#' @description +#' Methods to visualize \code{OpenSpecy} objects. +#' +#' @details +#' \code{head()} shows the first few lines of an \code{OpenSpecy} object. +#' \code{print()} prints the contents of an \code{OpenSpecy} object. +#' \code{summary()} produces a result summary of an \code{OpenSpecy} object. +#' \code{plot()} produces a \code{\link[graphics]{matplot}()} of an OpenSpecy +#' object; \code{lines()} adds new spectra to it. +#' +#' @param x an \code{OpenSpecy} object. +#' @param object an \code{OpenSpecy} object. +#' @param \ldots further arguments passed to the respective default method. +#' +#' @return +#' \code{head()}, \code{print()}, and \code{summary()} return a textual +#' representation of an \code{OpenSpecy} object. +#' \code{plot()} and \code{lines()} return a plot. +#' +#' @examples +#' data("raman_hdpe") +#' +#' # Printing the OpenSpecy object +#' print(raman_hdpe) +#' +#' # Displaying the first few lines of the OpenSpecy object +#' head(raman_hdpe) +#' +#' # Plotting the spectra +#' plot(raman_hdpe) +#' +#' @author +#' Zacharias Steinmetz, Win Cowger +#' +#' @seealso +#' \code{\link[utils]{head}()}, \code{\link[base]{print}()}, +#' \code{\link[base]{summary}()}, \code{\link[graphics]{matplot}()}, and +#' \code{\link[graphics]{matlines}()} +#' +#' @importFrom utils head +#' @importFrom graphics matplot matlines +#' @export +head.OpenSpecy <- function(x, ...) { + cbind(wavenumber = x$wavenumber, x$spectra) |> head(...) +} + +#' @rdname gen_OpenSpecy +#' +#' @method print OpenSpecy +#' @export +print.OpenSpecy <- function(x, ...) { + cbind(wavenumber = x$wavenumber, x$spectra) |> print(...) + cat("\n$metadata\n") + print(x$metadata) +} + +#' @rdname gen_OpenSpecy +#' +#' @method plot OpenSpecy +#' @export +plot.OpenSpecy <- function(x, ...) { + matplot(x$wavenumber, x$spectra, type = "l", + xlab = "wavenumber", + ylab = "absorbance intensity", + xlim = rev(range(x$wavenumber)), + ...) +} + +#' @rdname gen_OpenSpecy +#' +#' @method lines OpenSpecy +#' @export +lines.OpenSpecy <- function(x, ...) { + matlines(x$wavenumber, x$spectra, type = "l", + ...) +} + +#' @rdname gen_OpenSpecy +#' +#' @method summary OpenSpecy +#' @export +summary.OpenSpecy <- function(object, ...) { + cat("$wavenumber\n") + wl <- length(object$wavenumber) + wr <- range(object$wavenumber) + res <- spec_res(object) + array(c(wl, wr, res), c(1,4), list("", c("Length", "Min.", "Max.", + "Res."))) |> + print() + + cat("\n$spectra\n") + sl <- length(object$spectra) + sr <- range(object$spectra) + array(c(sl, sr), c(1,3), list("", c("Number", "Min. Intensity", + "Max. Intensity"))) |> + print() + + cat("\n$metadata\n") + xr <- range(object$metadata$x) + yr <- range(object$metadata$y) + t(array(c(xr, yr), c(2,2), list(c("Min.", "Max."), c("x", "y")))) |> print() + names(object$metadata) |> print() +} diff --git a/R/human_ts.R b/R/human_ts.R index c133b3c0..6792a07a 100644 --- a/R/human_ts.R +++ b/R/human_ts.R @@ -16,13 +16,12 @@ #' human_ts() #' #' @author -#' Win Cowger +#' Win Cowger, Zacharias Steinmetz #' #' @seealso #' \code{\link[base]{format.Date}} for date conversion functions #' -#' @importFrom dplyr %>% #' @export human_ts <- function() { - Sys.time() %>% format("%Y%m%d-%H%M%OS") + Sys.time() |> format("%Y%m%d-%H%M%OS") } diff --git a/R/interactive_plots.R b/R/interactive_plots.R new file mode 100644 index 00000000..5f437bf6 --- /dev/null +++ b/R/interactive_plots.R @@ -0,0 +1,239 @@ +#' @rdname interactive_plots +#' @title Interactive plots for OpenSpecy objects +#' +#' @description +#' These functions generate heatmaps, spectral plots, and interactive plots for +#' OpenSpecy data. +#' +#' @param x an \code{OpenSpecy} object containing metadata and spectral data for +#' the first group. +#' @param x2 an optional second \code{OpenSpecy} object containing metadata and +#' spectral data for the second group. +#' @param z optional numeric vector specifying the intensity values for the +#' heatmap. If not provided, the function will use the intensity values from the +#' \code{OpenSpecy} object. +#' @param sn optional numeric value specifying the signal-to-noise ratio +#' threshold. If provided along with \code{min_sn}, regions with SNR below the +#' threshold will be excluded from the heatmap. +#' @param cor optional numeric value specifying the correlation threshold. If +#' provided along with \code{min_cor}, regions with correlation below the +#' threshold will be excluded from the heatmap. +#' @param min_sn optional numeric value specifying the minimum signal-to-noise +#' ratio for inclusion in the heatmap. Regions with SNR below this threshold +#' will be excluded. +#' @param min_cor optional numeric value specifying the minimum correlation for +#' inclusion in the heatmap. Regions with correlation below this threshold +#' will be excluded. +#' @param select optional index of the selected spectrum to highlight on the +#' heatmap. +#' @param line list; \code{line} parameter for \code{x}; passed to +#' \code{\link[plotly]{add_trace}()}. +#' @param line2 list; \code{line} parameter for \code{x2}; passed to +#' @param font list; passed to \code{\link[plotly]{layout}()}. +#' @param plot_bgcolor color value; passed to \code{\link[plotly]{layout}()}. +#' @param paper_bgcolor color value; passed to \code{\link[plotly]{layout}()}. +#' @param colorscale colorscale passed to \code{\link[plotly]{add_trace}()}. +#' @param \ldots further arguments passed to \code{\link[plotly]{plot_ly}()}. +#' +#' @return +#' A plotly heatmap object displaying the OpenSpecy data. A subplot +#' containing the heatmap and spectra plot. A plotly object displaying the +#' spectra from the \code{OpenSpecy} object(s). +#' +#' @examples +#' data("raman_hdpe") +#' tiny_map <- read_extdata("CA_tiny_map.zip") |> read_zip() +#' plotly_spec(raman_hdpe) +#' +#' correlation <- cor_spec( +#' conform_spec(raman_hdpe, range = raman_hdpe$wavenumber, res = 5), +#' conform_spec(tiny_map, tiny_map$wavenumbers, res = 5) +#' ) +#' heatmap_spec(tiny_map, z = tiny_map$metadata$y) +#' +#' sample_spec(tiny_map, size = 12) |> +#' interactive_plot(select = 2, x2 = raman_hdpe) +#' +#' @author +#' Win Cowger, Zacharias Steinmetz +#' +#' @importFrom plotly plot_ly add_trace add_markers subplot layout +#' @importFrom data.table melt +#' +#' @export +plotly_spec <- function(x, ...) { + UseMethod("plotly_spec") +} + +#' @rdname interactive_plots +#' +#' @export +plotly_spec.default <- function(x, ...) { + stop("object 'x' needs to be of class 'OpenSpecy'") +} + +#' @rdname interactive_plots +#' +#' @export +plotly_spec.OpenSpecy <- function(x, x2 = NULL, + line = list(color = 'rgb(255, 255, 255)'), + line2 = list(dash = "dot", + color = "rgb(255,0,0)"), + font = list(color = '#FFFFFF'), + plot_bgcolor = 'rgb(17, 0, 73)', + paper_bgcolor = 'rgb(0, 0, 0)', + ...) { + dt <- cbind(wavenumber = x$wavenumber, x$spectra) |> + melt(id.vars = "wavenumber", variable.name = "id", value.name = "intensity") + + p <- plot_ly(dt, type = "scatter", mode = "lines", ...) |> + add_trace(x = ~wavenumber, y = ~make_rel(intensity, na.rm = T), + color = ~id, line = line, + name = "Your Spectra", showlegend = F) |> + layout(xaxis = list(title = "wavenumber [cm-1]", + autorange = "reversed"), + yaxis = list(title = "intensity [-]"), + plot_bgcolor = plot_bgcolor, + paper_bgcolor = paper_bgcolor, + legend = list(orientation = 'h', y = 1.1), font = font) + + # Add dummy trace for Your Spectra + p <- p |> + add_trace(x = NULL, y = NULL, + line = line, name = "Your Spectra", showlegend = T) + + if (!is.null(x2)) { + dt2 <- cbind(wavenumber = x2$wavenumber, x2$spectra) |> + melt(id.vars = "wavenumber", variable.name = "id", value.name = "intensity") + + p <- p |> + add_trace(data = dt2, x = ~wavenumber, y = ~make_rel(intensity, na.rm = T), + color = ~id, type = "scatter", mode = "lines", + name = "Library Spectra", + line = line2, showlegend = F) + + # Add dummy trace for Library Spectra + p <- p |> + add_trace(x = NULL, y = NULL, + line = line2, + name = "Library Spectra", showlegend = T) + } + + return(p) +} + +#' @rdname interactive_plots +#' +#' @export +heatmap_spec <- function(x, ...) { + UseMethod("heatmap_spec") +} + +#' @rdname interactive_plots +#' +#' @export +heatmap_spec.default <- function(x, ...) { + stop("object 'x' needs to be of class 'OpenSpecy'") +} + +#' @rdname interactive_plots +#' +#' @export +heatmap_spec.OpenSpecy <- function(x, + z = NULL, sn = NULL, cor = NULL, + min_sn = NULL, min_cor = NULL, select = NULL, + font = list(color = '#FFFFFF'), + plot_bgcolor = 'rgba(17, 0, 73, 0)', + paper_bgcolor = 'rgb(0, 0, 0)', + colorscale = 'Viridis', + ...) { + if(!is.null(z)) + plot_z <- z # default + else if(!is.null(cor)) + plot_z <- cor + else if(!is.null(sn)) + plot_z <- sn + else stop("z, cor, or sn need to be specified to plot the z axis", call. = F) + + if (!is.null(sn) && !is.null(min_sn)) + plot_z <- ifelse(sn > min_sn, plot_z, NA) + + if (!is.null(cor) && !is.null(min_cor)) + plot_z <- ifelse(cor > min_cor, plot_z, NA) + + p <- plot_ly(...) |> + add_trace(x = x$metadata$x, y = x$metadata$y, z = if(!is.numeric(plot_z)){as.numeric(as.factor(plot_z))} else{plot_z}, + colorscale = colorscale, type = "heatmap", hoverinfo = 'text', + showscale = F, + text = ~paste("row: ", 1:nrow(x$metadata), + "
x: ", x$metadata$x,", y: ", x$metadata$y, + ", z: ", plot_z, + if(!is.null(sn)) paste("
snr: ", signif(sn, 2)) else "", + if(!is.null(cor)) paste("
cor: ", signif(cor, 2)) else "" + )) |> + layout( + xaxis = list(title = 'x', zeroline = F, showgrid = F), + yaxis = list(title = 'y', scaleanchor = "x", scaleratio = 1, + zeroline = F, showgrid = F), + plot_bgcolor = plot_bgcolor, paper_bgcolor = paper_bgcolor, + showlegend = FALSE, font = font) + + if(!is.null(select)){ + p <- p |> add_markers(x = x$metadata$x[select], y = x$metadata$y[select], + name = "Selected Spectrum") + } + + return(p) +} + +#' @rdname interactive_plots +#' +#' @export +interactive_plot <- function(x, ...) { + UseMethod("interactive_plot") +} + +#' @rdname interactive_plots +#' +#' @export +interactive_plot.default <- function(x, ...) { + stop("object 'x' needs to be of class 'OpenSpecy'") +} + +#' @rdname interactive_plots +#' +#' @export +interactive_plot.OpenSpecy <- function(x, x2 = NULL, select = NULL, + line = list(color = 'rgb(255, 255, 255)'), + line2 = list(dash = "dot", + color = "rgb(255,0,0)"), + font = list(color = '#FFFFFF'), + plot_bgcolor = 'rgba(17, 0, 73, 0)', + paper_bgcolor = 'rgb(0, 0, 0)', + colorscale = 'Viridis', + ...) { + # Generate the heatmap + heat_map <- heatmap_spec(x, z = x$metadata$y, select = select, + font = font, plot_bgcolor = plot_bgcolor, + paper_bgcolor = paper_bgcolor, + colorscale = colorscale) + + x3 <- filter_spec(x, logic = select) + + # Generate the spectral plot + spectra_plot <- plotly_spec(x3, x2 = x2, + line = line, line2 = line2, font = font, + plot_bgcolor = plot_bgcolor, + paper_bgcolor = paper_bgcolor) + + + # Add margin to heatmap for separation + heat_map <- heat_map |> layout(autosize = TRUE, margin = list(b = 100)) + + # Combine both plots using subplot + plot_grid <- subplot(heat_map, spectra_plot, nrows = 2, heights = c(0.6, 0.4), + margin = 0.1) + + # Show the interactive plot + return(plot_grid) +} diff --git a/R/io_spec.R b/R/io_spec.R new file mode 100644 index 00000000..b25bb5ac --- /dev/null +++ b/R/io_spec.R @@ -0,0 +1,149 @@ +#' @rdname io_spec +#' @title Read and write spectral data +#' +#' @description +#' Functions for reading and writing spectral data to and from OpenSpecy format. +#' \code{OpenSpecy} objects are lists with components `wavenumber`, `spectra`, +#' and `metadata`. Currently supported formats are .y(a)ml, .json, or .rds. +#' +#' @param x an object of class \code{\link{OpenSpecy}}. +#' @param file file path to be read from or written to. +#' @param share defaults to \code{NULL}; needed to share spectra with the +#' Open Specy community; see \code{\link{share_spec}()} for details. +#' @param method optional; function to be used as a custom reader or writer. +#' Defaults to the appropriate function based on the file extension. +#' @param digits number of significant digits to use when formatting numeric +#' values; defaults to \code{\link[base]{getOption}("digits")}. +#' @param \ldots further arguments passed to the submethods. +#' +#' @details +#' Due to floating point number errors there may be some differences in the +#' precision of the numbers returned if using multiple devices for .json and +#' .yaml files but the numbers should be nearly identical. +#' \code{\link[base]{readRDS}()} should return the exact same object every time. +#' +#' @return +#' \code{read_spec()} reads data formatted as an \code{OpenSpecy} object and +#' returns a list object of class \code{\link{OpenSpecy}} containing spectral +#' data. +#' \code{write_spec()} writes a file for an object of class +#' \code{\link{OpenSpecy}} containing spectral data. +#' \code{as_hyperspec()} converts an \code{OpenSpecy} object to a +#' \code{\link[hyperSpec]{hyperSpec-class}} object. +#' +#' @examples +#' read_extdata("raman_hdpe.yml") |> read_spec() +#' read_extdata("raman_hdpe.json") |> read_spec() +#' read_extdata("raman_hdpe.rds") |> read_spec() +#' +#' \dontrun{ +#' data(raman_hdpe) +#' write_spec(raman_hdpe, "raman_hdpe.yml") +#' write_spec(raman_hdpe, "raman_hdpe.json") +#' write_spec(raman_hdpe, "raman_hdpe.rds") +#' +#' # Convert an OpenSpecy object to a hyperSpec object +#' hyper <- as_hyperSpec(raman_hdpe) +#' } +#' +#' @author +#' Zacharias Steinmetz, Win Cowger +#' +#' @seealso +#' \code{\link{OpenSpecy}()}; +#' \code{\link{read_text}()}, \code{\link{read_asp}()}, \code{\link{read_spa}()}, +#' \code{\link{read_spc}()}, and \code{\link{read_jdx}()} for text files, .asp, +#' .spa, .spa, .spc, and .jdx formats, respectively; +#' \code{\link{read_zip}()} and \code{\link{read_any}()} for wrapper functions; +#' \code{\link[base]{saveRDS}()}; \code{\link[base]{readRDS}()}; +#' \code{\link[yaml]{write_yaml}()}; \code{\link[yaml]{read_yaml}()}; +#' \code{\link[jsonlite]{write_json}()}; \code{\link[jsonlite]{read_json}()}; +#' +#' @importFrom yaml write_yaml read_yaml +#' @importFrom jsonlite write_json read_json +#' @importFrom data.table as.data.table +#' +#' @export +write_spec <- function(x, ...) { + UseMethod("write_spec") +} + +#' @rdname io_spec +#' +#' @export +write_spec.default <- function(x, ...) { + stop("object 'x' needs to be of class 'OpenSpecy'", call. = F) +} + +#' @rdname io_spec +#' +#' @export +write_spec.OpenSpecy <- function(x, file, method = NULL, + digits = getOption("digits"), + ...) { + if (is.null(method)) { + if (grepl("(\\.yaml$)|(\\.yml$)", file, ignore.case = T)) { + write_yaml(x, file = file, precision = digits, ...) + } else if (grepl("\\.json$", file, ignore.case = T)) { + write_json(x, path = file, dataframe = "columns", digits = digits, ...) + } else if (grepl("\\.rds$", file, ignore.case = T)) { + saveRDS(x, file = file, ...) + } else { + stop("unknown file type: specify a method to write custom formats or ", + "provide one of the supported .yml, .json, or .rds formats as ", + "file extension", call. = F) + } + } else { + do.call(method, list(x, file, ...)) + } +} + +#' @rdname io_spec +#' +#' @export +read_spec <- function(file, share = NULL, method = NULL, ...) { + if (is.null(method)) { + if (grepl("(\\.yaml$)|(\\.yml$)", file, ignore.case = T)) { + yml <- read_yaml(file = file, ...) + + os <- as_OpenSpecy(yml$wavenumber, + spectra = as.data.table(yml$spectra), + metadata = data.table(as.data.table(yml$metadata), + file_name = basename(file)), + coords = NULL) + } else if (grepl("\\.json$", file, ignore.case = T)) { + jsn <- read_json(file, simplifyVector = T, ...) + + os <- as_OpenSpecy(jsn$wavenumber, + spectra = as.data.table(jsn$spectra), + metadata = data.table(as.data.table(jsn$metadata), + file_name = basename(file)), + coords = NULL) + } else if (grepl("\\.rds$", file, ignore.case = T)) { + os <- readRDS(file, ...) + os$metadata$file_name <- basename(file) + } else { + stop("unknown file type: specify a method to read custom formats or ", + "provide files of one of the supported file types .yml, .json, .rds", + call. = F) + } + } else { + io <- do.call(method, list(file, ...)) + + os <- OpenSpecy(io, coords = NULL) + os$metadata$file_name <- basename(file) + } + + if (!is.null(share)) share_spec(os, file = file, share = share) + + return(os) +} + +#' @rdname io_spec +#' @importFrom methods new +#' +#' @export +as_hyperSpec <- function(x) { + new("hyperSpec", spc = as.matrix(transpose(x$spectra)), + wavelength = x$wavenumber) +} diff --git a/R/manage_lib.R b/R/manage_lib.R index aea62e61..0199f92e 100644 --- a/R/manage_lib.R +++ b/R/manage_lib.R @@ -1,5 +1,4 @@ #' @rdname manage_lib -#' #' @title Manage spectral libraries #' #' @description @@ -16,12 +15,13 @@ #' (\doi{10.17605/OSF.IO/X7DPZ}). #' \code{load_lib()} will load the library into the global environment for use #' with the Open Specy functions. +#' \code{rm_lib()} removes the libraries from your computer. #' -#' @param which a character string specifying which library to use, -#' \code{"raman"} or \code{"ftir"}. -#' @param types library types to check/retrieve; defaults to -#' \code{c("metadata", "library", "peaks")}. -#' @param node the OSF node to be retrieved; should be \code{"x7dpz"}. +#' @param type library type to check/retrieve; defaults to +#' \code{c("derivative", "nobaseline", "raw", "mediod", "model")} which reads +#' everything. +#' @param node the OSF node to be retrieved; should be \code{"x7dpz"} unless you +#' maintain your own OSF node with spectral libraries. #' @param path where to save or look for local library files; defaults to #' \code{"system"} pointing to #' \code{system.file("extdata", package = "OpenSpecy")}. @@ -41,22 +41,19 @@ #' #' @return #' \code{check_lib()} and \code{get_lib()} return messages only; -#' \code{load_lib()} returns a list object containing the respective spectral -#' reference library. +#' \code{load_lib()} returns an \code{OpenSpecy} object containing the +#' respective spectral reference library. #' #' @examples #' \dontrun{ -#' check_lib(which = c("ftir", "raman")) -#' get_lib(which = c("ftir", "raman")) +#' check_lib("derivative") +#' get_lib("derivative") #' -#' spec_lib <- load_lib(which = c("ftir", "raman")) +#' spec_lib <- load_lib("derivative") #' } #' #' @author -#' Zacharias Steinmetz -#' -#' @seealso -#' \code{\link{match_spec}()} +#' Zacharias Steinmetz, Win Cowger #' #' @references #' Cowger W, Gray A, Christiansen SH, Christiansen SH, Christiansen SH, @@ -67,19 +64,16 @@ #' #' Cowger, W (2021). “Library data.” \emph{OSF}. \doi{10.17605/OSF.IO/X7DPZ}. #' -#' @importFrom dplyr %>% -#' #' @export -check_lib <- function(which = c("ftir", "raman"), - types = c("metadata", "library", "peaks"), - path = "system", - condition = "warning") { +check_lib <- function(type = c("derivative", "nobaseline", "raw", "mediod", + "model"), + path = "system", condition = "warning") { lp <- ifelse(path == "system", system.file("extdata", package = "OpenSpecy"), path) - sapply(which, .chkf, types = types, path = lp, condition = condition) + .chkf(type, path = lp, condition = condition) invisible() } @@ -90,24 +84,22 @@ check_lib <- function(which = c("ftir", "raman"), #' @importFrom osfr osf_retrieve_node osf_ls_files osf_download #' #' @export -get_lib <- function(which = c("ftir", "raman"), - types = c("metadata", "library", "peaks"), - path = "system", - node = "x7dpz", conflicts = "overwrite", ...) { +get_lib <- function(type = c("derivative", "nobaseline", "raw", "mediod", + "model"), + path = "system", node = "x7dpz", conflicts = "overwrite", + ...) { lp <- ifelse(path == "system", system.file("extdata", package = "OpenSpecy"), path) - osf <- osf_retrieve_node(node) %>% + osf <- osf_retrieve_node(node) |> osf_ls_files(pattern = ".rds", n_max = Inf) message("Fetching Open Specy reference libraries from OSF ...") - for (w in which) { - osf %>% subset(grepl( - paste0("^", w, "_(", paste(types, collapse = "|"), ").rds"), - osf$name)) %>% + osf |> subset(grepl( + paste0("(", paste(type, collapse = "|"), ").rds"), + osf$name)) |> osf_download(path = lp, conflicts = conflicts, progress = TRUE, ...) - } message("Use 'load_lib()' to load the library") } @@ -115,51 +107,54 @@ get_lib <- function(which = c("ftir", "raman"), #' @rdname manage_lib #' #' @export -load_lib <- function(which = c("ftir", "raman"), - types = c("metadata", "library", "peaks"), - path = "system") { +load_lib <- function(type, path = "system") { lp <- ifelse(path == "system", system.file("extdata", package = "OpenSpecy"), path) - chk <- lapply(which, .chkf, types = types, path = lp, condition = "stop") + chk <- .chkf(type, path = lp, condition = "stop") - res <- lapply(chk, function(x) { - fls <- file.path(lp, paste0(x[[2L]], "_", names(x[[1L]]), ".rds")) + fp <- file.path(lp, paste0(type, ".rds")) + rds <- readRDS(fp) - rrds <- lapply(fls, readRDS) - names(rrds) <- names(x[[1L]]) + return(rds) +} - rrds - }) +#' @rdname manage_lib +#' +#' @export +rm_lib <- function(type = c("derivative", "nobaseline", "raw", "mediod", + "model"), + path = "system") { + lp <- ifelse(path == "system", + system.file("extdata", package = "OpenSpecy"), + path) + + fp <- file.path(lp, paste0(type, ".rds")) + file.remove(fp) - names(res) <- which - return(res) + invisible() } # Auxiliary function for library checks -.chkf <- function(which, types = c("metadata", "library", "peaks"), - path = "system", - condition = "warning") { - fn <- paste0(which, "_", types, ".rds") +.chkf <- function(type, path = "system", condition = "warning") { + fn <- paste0(type, ".rds") - lp <- ifelse(path == "system", - system.file("extdata", package = "OpenSpecy"), + lp <- ifelse(path == "system", system.file("extdata", package = "OpenSpecy"), path) - chk <- file.path(lp, fn) %>% file.exists() - names(chk) <- types - - out <- switch (which, - "ftir" = "FTIR", - "raman" = "Raman" - ) - - if (!all(chk)) do.call(condition, list(out, " library missing or incomplete; ", - "use 'get_lib()' to download a current version", - call. = ifelse(condition %in% - c("message", - "packageStartupMessage"), - "", FALSE))) - list(chk, which, out) + chk <- file.path(lp, fn) |> file.exists() + + names(chk) <- type + + out <- paste(type[!chk], collapse = ", ") + + if (!all(chk)) + do.call(condition, list("Library missing or incomplete: ", out, "; ", + "use 'get_lib()' to download a current version", + call. = ifelse(condition %in% + c("message", + "packageStartupMessage"), + "", FALSE))) + chk } diff --git a/R/manage_spec.R b/R/manage_spec.R new file mode 100644 index 00000000..b5b645e1 --- /dev/null +++ b/R/manage_spec.R @@ -0,0 +1,128 @@ +#' @rdname manage_spec +#' @title Manage spectral objects +#' +#' @description +#' \code{c_spec()} concatenates \code{OpenSpecy} objects. +#' \code{sample_spec()} samples spectra from an \code{OpenSpecy} object. +#' +#' @param x a list of \code{OpenSpecy} objects. +#' @param range a numeric providing your own wavenumber ranges or character +#' argument called \code{"common"} to let \code{c_spec()} find the common +#' wavenumber range of the supplied spectra. \code{NULL} will interpret the +#' spectra having all the same wavenumber range. +#' @param res defaults to \code{NULL}, the resolution you want the output +#' wavenumbers to be. +#' @param \ldots further arguments passed to submethods. +#' +#' @return +#' \code{c_spec()} and \code{sample_spec()} return \code{OpenSpecy} objects. +#' +#' @examples +#' # Concatenating spectra +#' spectra <- lapply(c(read_extdata("raman_hdpe.csv"), +#' read_extdata("ftir_ldpe_soil.asp")), read_any) +#' common <- c_spec(spectra, range = "common", res = 5) +#' range <- c_spec(spectra, range = c(1000, 2000), res = 5) +#' +#' # Sampling spectra +#' tiny_map <- read_any(read_extdata("CA_tiny_map.zip")) +#' sampled <- sample_spec(tiny_map, size = 3) +#' +#' @author +#' Zacharias Steinmetz, Win Cowger +#' +#' @seealso +#' \code{\link[OpenSpecy]{conform_spec}()} for conforming wavenumbers +#' +#' @importFrom data.table data.table as.data.table fread rbindlist +#' +#' @export +c_spec <- function(x, ...) { + UseMethod("c_spec") +} + +#' @rdname manage_spec +#' +#' @export +c_spec.default <- function(x, ...) { + stop("object 'x' needs to be a list of 'OpenSpecy' objects") +} + +#' @rdname manage_spec +#' +#' @export +c_spec.OpenSpecy <- function(x, ...) { + warning("object 'x' needs to be a list of 'OpenSpecy' objects; ", + "nothing to concatenate, returning 'x'") + + return(x) +} + +#' @rdname manage_spec +#' +#' @export +c_spec.list <- function(x, range = NULL, res = 5, ...) { + if(!all(vapply(x, function(y) {inherits(y, "OpenSpecy")}, FUN.VALUE = T))) + stop("object 'x' needs to be a list of 'OpenSpecy' objects", call. = F) + + if(!is.null(range)) { + if(is.numeric(range)) { + wn <- range + } + else if(!is.null(range) && range == "common") { + pmin <- vapply(x, function(y) min(y$wavenumber), FUN.VALUE = numeric(1)) + pmax <- vapply(x, function(y) max(y$wavenumber), FUN.VALUE = numeric(1)) + + if(any(max(pmin) > pmax) | any(min(pmax) < pmin)) + stop("data points need to overlap in their ranges", call. = F) + + wn <- c(max(pmin), min(pmax)) + } + + x <- lapply(x, conform_spec, range = wn, res = res) + } + + unlisted <- unlist(x, recursive = F) + list <- tapply(unlisted, names(unlisted), unname) + + if(length(unique(vapply(list$wavenumber, length, FUN.VALUE = numeric(1)))) > 1 + & is.null(range)) { + stop("wavenumbers need to be identical between spectra; specify how; use ", + "'range' to specify how wavenumbers should be merged", call. = F) + } + + as_OpenSpecy(x = list$wavenumber[[1]], + spectra = as.data.table(list$spectra), + metadata = rbindlist(list$metadata, fill = T)[,-c("x","y")] + ) +} + + +#' @rdname manage_spec +#' +#' @export +sample_spec <- function(x, ...) { + UseMethod("sample_spec") +} + +#' @rdname manage_spec +#' +#' @export +sample_spec.default <- function(x, ...) { + stop("object 'x' needs to be of class 'OpenSpecy'") +} + +#' @rdname manage_spec +#' +#' @export +sample_spec.OpenSpecy <- function(x, ...) { + # replace = false is mandatory currently because we don't have a way to + # rename and recoordinate duplicates. + cols <- sample(1:ncol(x$spectra), ...) + + as_OpenSpecy( + x = x$wavenumber, + spectra = x$spectra[, cols, with = F], + metadata = x$metadata[cols, ] + ) +} diff --git a/R/match_spec.R b/R/match_spec.R index c035acb2..86eefcd0 100644 --- a/R/match_spec.R +++ b/R/match_spec.R @@ -1,62 +1,71 @@ -#' @title Match spectra with reference library +#' @rdname match_spec +#' @title Identify and filter spectra #' #' @description -#' \code{match_spec()} will compare a spectrum to a spectral library formatted -#' with the Open Specy standard and report the best match using the Pearson -#' correlation coefficient. -#' \code{find_spec()} makes it easy to retrieve single spectra and metadata -#' from the Open Specy reference library. -#' -#' @details -#' This routine will match the spectrum you want to identify to the wavenumbers -#' present in the spectral library. Once the spectra are aligned, it computes -#' the Pearson correlation coefficient between the spectrum you want to -#' identify and all spectra in the library (see \code{\link[stats]{cor}}). -#' The function returns a table with the Pearson correlation coefficient values -#' and all metadata for the top spectral matches. -#' If using the Open Specy library, all intensity values are in absorbance, so -#' your spectra should also be in absorbance units. If you need to convert your -#' spectrum, use \code{\link{adj_intens}()}. -#' -#' @param x a numeric vector containing the spectral wavenumbers; alternatively -#' a data frame containing spectral data as \code{"wavenumber"} and -#' \code{"intensity"} can be supplied. -#' @param y a numeric vector containing the spectral intensities. -#' @param formula an object of class '\code{\link[stats]{formula}}' of the form -#' \code{intensity ~ wavenumber}. -#' @param data a data frame containing the variables in \code{formula}. -#' @param library reference library you want to compare against. -#' @param which a character string specifying which library to match, -#' \code{"raman"} or \code{"ftir"}. -#' @param type a character string specifying whether the \code{"full"} spectrum -#' should be matched or spectrum \code{"peaks"} only. \code{"metadata"} is -#' needed to browser spectra with \code{find_spec()}. -#' @param range this should be all possible wavenumber values from your spectral -#' library. -#' @param top_n number of top matches that you want to be returned. -#' @param subset logical expression indicating elements or rows to search for; -#' see \code{\link[base]{subset}()} for details. -#' @param cols columns to retrieve from the Open Specy reference library; -#' columns containing no or missing values are automatically removed. -#' @param \ldots further arguments passed to the submethods. +#' \code{match_spec()} joins two \code{OpenSpecy} objects and their metadata +#' based on similarity. +#' \code{cor_spec()} correlates two \code{OpenSpecy} objects, typically one with +#' knowns and one with unknowns. +#' \code{ident_spec()} retrieves the top match values from a correlation matrix +#' and formats them with metadata. +#' \code{get_metadata()} retrieves metadata from OpenSpecy objects. +#' \code{max_cor_named()} formats the top correlation values from a correlation +#' matrix as a named vector. +#' \code{filter_spec()} filters an Open Specy object. #' -#' @examples -#' \dontrun{ -#' data("raman_hdpe") +#' @param x an \code{OpenSpecy} object, typically with unknowns. +#' @param library an \code{OpenSpecy} or \code{glmnet} object representing the +#' reference library of spectra or model to use in identification. +#' @param na.rm logical; indicating whether missing values should be removed +#' when calculating correlations. Default is \code{TRUE}. +#' @param top_n integer; specifying the number of top matches to return. +#' If \code{NULL} (default), all matches will be returned. +#' @param cor_matrix a correlation matrix for object and library, +#' can be returned by \code{cor_spec()} +#' @param add_library_metadata name of a column in the library metadata to be +#' joined; \code{NULL} if you don't want to join. +#' @param add_object_metadata name of a column in the object metadata to be +#' joined; \code{NULL} if you don't want to join. +#' @param rm_empty logical; whether to remove empty columns in the metadata. +#' @param logic a logical or numeric vector describing which spectra to keep. +#' @param fill an \code{OpenSpecy} object with a single spectrum to be used to +#' fill missing values for alignment with the AI classification. +#' @param \ldots additional arguments passed \code{\link[stats]{cor}()}. #' -#' get_lib("raman") -#' spec_lib <- load_lib("raman") +#' @return +#' \code{match_spec()} and \code{ident_spec()} will return +#' a \code{\link[data.table]{data.table-class}()} containing correlations +#' between spectra and the library. +#' The table has three columns: \code{object_id}, \code{library_id}, and +#' \code{match_val}. +#' Each row represents a unique pairwise correlation between a spectrum in the +#' object and a spectrum in the library. +#' If \code{top_n} is specified, only the top \code{top_n} matches for each +#' object spectrum will be returned. +#' If \code{add_library_metadata} is \code{is.character}, the library metadata +#' will be added to the output. +#' If \code{add_object_metadata} is \code{is.character}, the object metadata +#' will be added to the output. +#' \code{filter_spec()} returns an \code{OpenSpecy} object. +#' \code{cor_spec()} returns a correlation matrix. +#' \code{get_metadata()} returns a \code{\link[data.table]{data.table-class}()} +#' with the metadata for columns which have information. #' -#' match_spec(raman_proc, library = spec_lib, which = "raman") +#' @examples +#' data("test_lib") +#' unknown <- read_extdata("ftir_ldpe_soil.asp") |> +#' read_any() |> +#' conform_spec(range = test_lib$wavenumber, +#' res = spec_res(test_lib)) |> +#' process_spec() +#' matches <- cor_spec(unknown, test_lib) #' -#' find_spec(sample_name == 5381, library = spec_lib, which = "raman") -#' } +#' test_lib_extract <- filter_spec(test_lib, +#' logic = grepl("polycarbonate", test_lib$metadata$polymer_class, +#' ignore.case = TRUE) +#' ) #' -#' @return -#' \code{match_spec()} returns a data frame with the \code{top_n} material -#' matches, their Pearson's r value, and the organization they were provided by. -#' \code{find_spec()} returns a data frame with the spectral raw data or -#' metadata of a specific reference spectrum. +#' matches2 <- cor_spec(unknown, library = test_lib_extract) #' #' @author #' Win Cowger, Zacharias Steinmetz @@ -67,11 +76,51 @@ #' \code{\link{load_lib}()} loads the Open Specy reference library into an \R #' object of choice #' -#' @importFrom rlang .data -#' @importFrom stats approx cor -#' @importFrom dplyr %>% inner_join mutate group_by ungroup summarize select arrange desc top_n +#' @importFrom stats cor predict +#' @importFrom glmnet predict.glmnet +#' @importFrom data.table data.table setorder fifelse .SD as.data.table rbindlist +#' @export +cor_spec <- function(x, ...) { + UseMethod("cor_spec") +} + +#' @rdname match_spec +#' +#' @export +cor_spec.default <- function(x, ...) { + stop("object 'x' needs to be of class 'OpenSpecy'") +} + +#' @rdname match_spec #' #' @export +cor_spec.OpenSpecy <- function(x, library, na.rm = T, ...) { + if(sum(x$wavenumber %in% library$wavenumber) < 3) + stop("there are less than 3 matching wavenumbers in the objects you are ", + "trying to correlate; this won't work for correlation analysis. ", + "Consider first conforming the spectra to the same wavenumbers.", + call. = F) + + if(!all(x$wavenumber %in% library$wavenumber)) + warning(paste0("some wavenumbers in 'x' are not in the library and the ", + "function is not using these in the identification routine: ", + paste(x$wavenumber[!x$wavenumber %in% library$wavenumber], + collapse = " ")), + call. = F) + + lib <- library$spectra[library$wavenumber %in% x$wavenumber, ] + lib <- lib[, lapply(.SD, make_rel, na.rm = na.rm)] + lib <- lib[, lapply(.SD, mean_replace)] + + spec <- x$spectra[x$wavenumber %in% library$wavenumber,] + spec <- spec[,lapply(.SD, make_rel, na.rm = na.rm)] + spec <- spec[,lapply(.SD, mean_replace)] + + cor(lib, spec, ...) +} + +#' @rdname match_spec +#' @export match_spec <- function(x, ...) { UseMethod("match_spec") } @@ -79,94 +128,194 @@ match_spec <- function(x, ...) { #' @rdname match_spec #' #' @export -match_spec.formula <- function(formula, data = NULL, ...) { - if (missing(formula) || (length(formula) != 3L) || - (length(attr(terms(formula[-2L]), "term.labels")) != 1L)) - stop("'formula' missing or incorrect") +match_spec.default <- function(x, ...) { + stop("object 'x' needs to be of class 'OpenSpecy'") +} - mf <- model.frame(formula, data) - lst <- as.list(mf) - names(lst) <- c("y", "x") +#' @rdname match_spec +#' +#' @export +match_spec.OpenSpecy <- function(x, library, na.rm = T, top_n = NULL, + add_library_metadata = NULL, + add_object_metadata = NULL, fill = NULL, ...) { + if(is_OpenSpecy(library)) { + cor_spec(x, library = library) |> + ident_spec(x, library = library, top_n = top_n, + add_library_metadata = add_library_metadata, + add_object_metadata = add_object_metadata) + } else { + ai_classify(x, library, fill) + } +} - do.call("match_spec", c(lst, list(...))) +#' @rdname match_spec +#' +#' @export +ident_spec <- function(cor_matrix, x, library, top_n = NULL, + add_library_metadata = NULL, + add_object_metadata = NULL, ...){ + if(is.numeric(top_n) && top_n > ncol(library$spectra)){ + top_n = NULL + message("'top_n' was larger than the number of spectra in the library; ", + "returning all matches") + } + + out <- data.table(object_id = colnames(x$spectra), + library_id = rep(colnames(library$spectra), + each = ncol(x$spectra)), + match_val = c(cor_matrix)) + + if (is.character(add_library_metadata)) + out <- merge(out, library$metadata, + by.x = "library_id", by.y = add_library_metadata, all.x = T) + if (is.character(add_object_metadata)) + out <- merge(out, x$metadata, + by.x = "object_id", by.y = add_object_metadata, all.x = T) + if (is.numeric(top_n)) { + setorder(out, -"match_val") + out <- out[, head(.SD, top_n), by = "object_id"] + } + + return(out) } #' @rdname match_spec #' #' @export -match_spec.data.frame <- function(x, ...) { - if (!all(c("wavenumber", "intensity") %in% names(x))) - stop("'data' must contain 2 columns named 'wavenumber' and 'intensity'") +get_metadata <- function(x, ...) { + UseMethod("get_metadata") +} - do.call("match_spec", list(x$wavenumber, x$intensity, ...)) +#' @rdname match_spec +#' +#' @export +get_metadata.default <- function(x, ...) { + stop("object 'x' needs to be of class 'OpenSpecy'") } #' @rdname match_spec #' #' @export -match_spec.default <- function(x, y, library, which = NULL, type = "full", - range = seq(0, 6000, 0.1), top_n = 100, ...) { - if(type == "full") type <- "library" +get_metadata.OpenSpecy <- function(x, logic, rm_empty = TRUE, ...) { + if(is.character(logic)) + logic <- which(names(x$spectra) %in% logic) - lib <- library[[which]][[type]] - meta <- library[[which]][["metadata"]] + res <- x$metadata[logic, ] - wls <- range[range >= min(x) & range <= max(x)] - aprx <- approx(x, y, xout = wls, rule = 2, method = "linear", ties = mean) %>% - data.frame() - names(aprx) <- c("wavenumber", "baseline_remove") + if(rm_empty) + res <- res[, !sapply(res, is_empty_vector), with = F] - m <- lib %>% - inner_join(aprx, by = "wavenumber") %>% - group_by(.data$group, .data$sample_name) %>% - mutate(baseline_remove = .data$baseline_remove - - min(.data$baseline_remove)) %>% - ungroup() %>% - mutate(baseline_remove = make_rel(.data$baseline_remove)) %>% - group_by(.data$sample_name) %>% - summarize(rsq = cor(.data$intensity, .data$baseline_remove)) %>% - top_n(top_n, .data$rsq) %>% - inner_join(select(meta, -.data$rsq), by = "sample_name") %>% - select(.data$sample_name, .data$spectrum_identity, .data$rsq, - .data$organization) %>% - arrange(desc(.data$rsq)) %>% - mutate(rsq = round(.data$rsq, 2)) + return(res) +} - message("Visit openspecy.org to share your spectrum with the Open Specy ", - "community and improve the spectral library.") - return(m) +#' @rdname match_spec +#' +#' @export +max_cor_named <- function(cor_matrix, na.rm = T) { + # Find the indices of maximum correlations + max_cor_indices <- apply(cor_matrix, 2, function(x) which.max(x)) + + # Use indices to get max correlation values + max_cor_values <- vapply(1:length(max_cor_indices), function(idx) { + cor_matrix[max_cor_indices[idx],idx]}, FUN.VALUE = numeric(1)) + + # Use indices to get the corresponding names + names(max_cor_values) <- rownames(cor_matrix)[max_cor_indices] + + return(max_cor_values) +} + +#' @rdname match_spec +#' +#' @export +filter_spec <- function(x, ...) { + UseMethod("filter_spec") +} + +#' @rdname match_spec +#' +#' @export +filter_spec.default <- function(x, ...) { + stop("object 'x' needs to be of class 'OpenSpecy'") +} + +#' @rdname match_spec +#' +#' @export +filter_spec.OpenSpecy <- function(x, logic, ...) { + if(is.character(logic)){ + logic = which(names(x$spectra) %in% logic) + } + x$spectra <- x$spectra[, logic, with = F] + x$metadata <- x$metadata[logic,] + + return(x) +} + +#' @rdname match_spec +#' +#' @export +ai_classify <- function(x, ...) { + UseMethod("ai_classify") } #' @rdname match_spec #' #' @export -find_spec <- function(subset, library, which = NULL, type = "metadata", - cols = c("spectrum_identity", "organization", - "contact_info", "spectrum_type", - "instrument_used", "instrument_accessories", - "instrument_mode", "laser_light_used", - "total_acquisition_time_s", - "number_of_accumulations", - "level_of_confidence_in_identification", - "cas_number", "material_producer", - "material_purity", "material_form", - "material_quality", "spectral_resolution", - "data_processing_procedure", - "other_information", "sample_name", - "wavenumber", "intensity", "group"), - ...) { +ai_classify.default <- function(x, ...) { + stop("object 'x' needs to be of class 'OpenSpecy'") +} + +#' @rdname match_spec +#' +#' @export +ai_classify.OpenSpecy <- function(x, library, fill = NULL, ...) { + if(!is.null(fill)) { + filled <- .fill_spec(x, fill) + } else { + filled <- x + } + filled$spectra$wavenumber <- filled$wavenumber + proc <- transpose(filled$spectra, make.names = "wavenumber") |> + as.matrix() + + pred <- predict(library$model, + newx = proc, + min(library$model$lambda), + type = "response") |> + as.data.table() + names(pred)[1:3] <- c("x", "y", "z") + pred$x <- as.integer(pred$x) + pred$y <- as.integer(pred$y) + pred <- merge(pred, data.table(x = 1:dim(proc)[1]), all.y = T) + + value <- NULL # workaround for data.table non-standard evaluation + filt <- pred[, .SD[value == max(value, na.rm = T) | is.na(value)], by = "x"] + + res <- merge(filt, library$dimension_conversion, all.x = T, + by.x = "y", by.y = "factor_num") + setorder(res, "x") + + return(res) +} + +.fill_spec <- function(x, fill) { + blank_dt <- x$spectra[1,] + + blank_dt[1,] <- NA - if(type == "full") type <- "library" + test <- rbindlist(lapply(1:length(fill$wavenumber), function(x) { + blank_dt + } + ))[, lapply(.SD, function(x) { + unlist(fill$spectra) + })] - df <- data.frame(library[[which]][[type]]) - e <- substitute(subset) + test[match(x$wavenumber, fill$wavenumber),] <- x$spectra - if (!is.call(e)) stop("subset needs to be a logical expression") + x$spectra <- test - r <- eval(e, df, parent.frame()) - c <- cols[cols %in% names(df)] - out <- df[r, c] + x$wavenumber <- fill$wavenumber - out[,colSums(is.na(out)) < nrow(out) & - colSums(out == "") < nrow(out)] + x } diff --git a/R/process_spec.R b/R/process_spec.R new file mode 100644 index 00000000..2d31a6ff --- /dev/null +++ b/R/process_spec.R @@ -0,0 +1,131 @@ +#' @rdname process_spec +#' @title Preprocess Spectra +#' +#' @description +#' \code{process_spec()} is a monolithic wrapper function for all spectral +#' preprocessing steps. +#' +#' @param x an \code{OpenSpecy} object. +#' @param active logical; indicating whether to perform preprocessing. +#' If \code{TRUE}, the preprocessing steps will be applied. +#' If \code{FALSE}, the original data will be returned. +#' @param adj_intens logical; describing whether to adjust the intensity units. +#' @param adj_intens_args named list of arguments passed to +#' \code{\link{smooth_intens}()}. +#' @param conform_spec logical; whether to conform the spectra to a new +#' wavenumber range and resolution. +#' @param conform_spec_args named list of arguments passed to +#' \code{\link{conform_spec}()}. +#' @param restrict_range logical; indicating whether to restrict the wavenumber +#' range of the spectra. +#' @param restrict_range_args named list of arguments passed to +#' \code{\link{restrict_range}()}. +#' @param flatten_range logical; indicating whether to flatten the range around +#' the carbon dioxide region. +#' @param flatten_range_args named list of arguments passed to +#' \code{\link{flatten_range}()}. +#' @param smooth_intens logical; indicating whether to apply a smoothing filter +#' to the spectra. +#' @param smooth_intens_args named list of arguments passed to +#' \code{\link{smooth_intens}()}. +#' @param subtr_baseline logical; indicating whether to subtract the baseline +#' from the spectra. +#' @param subtr_baseline_args named list of arguments passed to +#' \code{\link{subtr_baseline}()}. +#' @param make_rel logical; if \code{TRUE} spectra are automatically normalized +#' with \code{\link{make_rel}()}. +#' @param \ldots further arguments passed to subfunctions. +#' +#' @return +#' \code{process_spec()} returns an \code{OpenSpecy} object with preprocessed +#' spectra based on the specified parameters. +#' +#' @examples +#' data("raman_hdpe") +#' plot(raman_hdpe) +#' +#' # Process spectra with range restriction and baseline subtraction +#' process_spec(raman_hdpe, +#' restrict_range = TRUE, +#' restrict_range_args = list(min = 500, max = 3000), +#' subtr_baseline = TRUE, +#' subtr_baseline_args = list(type = "polynomial", +#' polynomial = 8)) |> +#' lines(col = "darkred") +#' +#' # Process spectra with smoothing and derivative +#' process_spec(raman_hdpe, +#' smooth_intens = TRUE, +#' smooth_intens_args = list( +#' polynomial = 3, +#' window = 11, +#' derivative = 1 +#' ) +#' ) |> +#' lines(col = "darkgreen") +#' +#' @export +process_spec <- function(x, ...) { + UseMethod("process_spec") +} + +#' @rdname process_spec +#' +#' @export +process_spec.default <- function(x, ...) { + stop("object 'x' needs to be of class 'OpenSpecy'") +} + +#' @rdname process_spec +#' +#' @export +process_spec.OpenSpecy <- function(x, active = TRUE, + adj_intens = FALSE, + adj_intens_args = list( + type = "none" + ), + conform_spec = TRUE, + conform_spec_args = list( + range = NULL, res = 5, type = "interp" + ), + restrict_range = FALSE, + restrict_range_args = list( + min = 0, max = 6000 + ), + flatten_range = FALSE, + flatten_range_args = list( + min = 2200, max = 2420 + ), + subtr_baseline = FALSE, + subtr_baseline_args = list( + type = "polynomial", degree = 8, + raw = FALSE, baseline = NULL), + smooth_intens = TRUE, + smooth_intens_args = list( + polynomial = 3, window = 11, + derivative = 1, abs = TRUE), + make_rel = TRUE, + ...) { + if(active) { + if(adj_intens) + x <- do.call("adj_intens", c(list(x, make_rel = F), adj_intens_args)) + if(conform_spec) + x <- do.call("conform_spec", c(list(x), conform_spec_args)) + if(restrict_range) + x <- do.call("restrict_range", c(list(x, make_rel = F), + restrict_range_args)) + if(subtr_baseline) + x <- do.call("subtr_baseline", c(list(x, make_rel = F), + subtr_baseline_args)) + if(flatten_range) + x <- do.call("flatten_range", c(list(x, make_rel = F), + flatten_range_args)) + if(smooth_intens) + x <- do.call("smooth_intens", c(list(x, make_rel = F), + smooth_intens_args)) + if(make_rel) + x$spectra <- x$spectra[, lapply(.SD, make_rel)] + } + + return(x) +} diff --git a/R/raman_hdpe.R b/R/raman_hdpe.R index 60843897..d379a34a 100644 --- a/R/raman_hdpe.R +++ b/R/raman_hdpe.R @@ -1,23 +1,27 @@ #' @title Sample Raman spectrum #' #' @description -#' Raman spectrum of high-density polyethylene (HDPE). +#' Raman spectrum of high-density polyethylene (HDPE) provided by +#' Horiba Scientific. #' #' @format -#' A data table containing 964 rows and 2 columns: +#' An threepart list of class \code{\link{OpenSpecy}} containing: #' \tabular{ll}{ -#' \code{wavenumber}: \tab spectral wavenumber [1/cm] \cr -#' \code{intensity}: \tab absorbance values [-] \cr +#' \code{wavenumber}: \tab spectral wavenumbers \[1/cm\] (vector of 964 rows) \cr +#' \code{spectra}: \tab absorbance values [-] +#' (a \code{\link[data.table]{data.table}} with 964 rows and 1 column) \cr +#' \code{metadata}: \tab spectral metadata \cr #' } #' #' @examples -#' data("raman_hdpe") +#' data(raman_hdpe) +#' print(raman_hdpe) #' #' @author -#' Win Cowger +#' Zacharias Steinmetz, Win Cowger #' #' @references -#' Cowger W, Gray A, Christiansen SH, Christiansen SH, Christiansen SH, +#' Cowger W, Steinmetz Z, Gray A, Christiansen SH, Christiansen SH, Christiansen SH, #' De Frond H, Deshpande AD, Hemabessiere L, Lee E, Mill L, et al. (2020). #' “Critical Review of Processing and Classification Techniques for Images and #' Spectra in Microplastic Research.” \emph{Applied Spectroscopy}, diff --git a/R/read_envi.R b/R/read_envi.R new file mode 100644 index 00000000..f93bd553 --- /dev/null +++ b/R/read_envi.R @@ -0,0 +1,150 @@ +#' @title Read ENVI data +#' +#' @description +#' This function allows ENVI data import. +#' +#' @param file name of the binary file. +#' @param header name of the ASCII header file. If `NULL`, the name of the +#' header file is guessed by looking for a second file with the same basename as +#' `file` but with .hdr extension. +#' @param share defaults to \code{NULL}; needed to share spectra with the +#' Open Specy community; see \code{\link{share_spec}()} for details. +#' @param metadata a named list of the metadata; see +#' \code{\link{as_OpenSpecy}()} for details. +#' @param \ldots further arguments passed to the submethods. +#' +#' @details +#' ENVI data usually consists of two files, an ASCII header and a binary data +#' file. The header contains all information necessary for correctly reading +#' the binary file via \code{\link[caTools]{read.ENVI}()}. +#' +#' @return +#' An `OpenSpecy` object. +#' +#' @author Zacharias Steinmetz, Claudia Beleites +#' +#' @seealso +#' \code{\link{read_spec}()} for reading .y(a)ml, .json, or .rds (OpenSpecy) +#' files; +#' \code{\link{read_text}()}, \code{\link{read_asp}()}, \code{\link{read_spa}()}, +#' \code{\link{read_spc}()}, and \code{\link{read_jdx}()} for text files, .asp, +#' .spa, .spa, .spc, and .jdx formats, respectively; +#' \code{\link{read_opus}()} for reading .0 (OPUS) files; +#' \code{\link{read_zip}()} and \code{\link{read_any}()} for wrapper functions; +#' \code{\link[caTools]{read.ENVI}()} +#' +#' @importFrom utils modifyList +#' @importFrom data.table as.data.table dcast +#' @importFrom caTools read.ENVI +#' @export +read_envi <- function(file, header = NULL, share = NULL, + metadata = list( + file_name = basename(file), + user_name = NULL, + contact_info = NULL, + organization = NULL, + citation = NULL, + spectrum_type = NULL, + spectrum_identity = NULL, + material_form = NULL, + material_phase = NULL, + material_producer = NULL, + material_purity = NULL, + material_quality = NULL, + material_color = NULL, + material_other = NULL, + cas_number = NULL, + instrument_used = NULL, + instrument_accessories = NULL, + instrument_mode = NULL, + spectral_resolution = NULL, + laser_light_used = NULL, + number_of_accumulations = NULL, + total_acquisition_time_s = NULL, + data_processing_procedure = NULL, + level_of_confidence_in_identification = NULL, + other_info = NULL, + license = "CC BY-NC"), + ...) { + if(is.null(header)) + header <- sub(pattern = "(.*)\\..*$", replacement = "\\1", file) |> + paste0(".hdr") + + hdr <- .read_envi_header(header) + arr <- read.ENVI(file, header) + dt <- as.data.table(arr) + md <- hdr[names(hdr) != "wavelength"] + names(dt) <- c("x", "y", "z", "value") + dt[, 1:2] <- dt[, 1:2] -1 + + os <- as_OpenSpecy(x = hdr$wavelength, + spectra = dcast(dt, z ~ x + y)[, -1], + metadata = c(metadata, md), + coords = dt[, 1:2] |> unique(), + session_id = T) + + if (!is.null(share)) share_spec(os, file = file, share = share) + + return(os) +} + +.read_envi_header <- function(headerfile, ...) { + tr <- file.path(headerfile) |> file(...) + hdr <- tr |> readLines() + close(tr) + + if(!grepl("ENVI", hdr[1])) { + stop("envi header not found", call. = F) + } else { + hdr <- hdr[-1] + } + + hdr <- gsub("\\{([^}]*)\\}", "\\1", hdr) + + l <- grep("\\{", hdr) + r <- grep("\\}", hdr) + + if (length(l) != length(r) || any(r <= l)) + stop("header data does not match", call. = F) + + hdr[l] <- sub("\\{", "", hdr[l]) + hdr[r] <- sub("\\}", "", hdr[r]) + + for (i in rev(seq_along(l))) { + hdr <- c( + hdr[seq_len(l[i] - 1)], + paste(hdr[l[i]:r[i]], collapse = " "), + hdr[-seq_len(r[i])] + ) + } + + hdr <- sapply(hdr, .split_line, "=", USE.NAMES = FALSE) + names(hdr) <- tolower(names(hdr)) + + tmp <- names(hdr) %in% c("samples", "lines", "bands", "data type", + "header offset") + hdr[tmp] <- lapply(hdr[tmp], as.numeric) + + hdr$wavelength <- strsplit(hdr$wavelength, "[,;[:blank:]]+") |> unlist() |> + as.numeric() + + return(hdr) +} + +.split_line <- function(x, sep, trim.blank = TRUE) { + tmp <- regexpr(sep, x) + + key <- substr(x, 1, tmp - 1) + val <- substr(x, tmp + 1, nchar(x)) + + if (trim.blank) { + blank.pattern <- "^[[:blank:]]*([^[:blank:]]+.*[^[:blank:]]+)[[:blank:]]*$" + key <- sub(blank.pattern, "\\1", key) + val <- sub(blank.pattern, "\\1", val) + } + + val <- as.list(val) + names(val) <- key + + return(val) +} diff --git a/R/read_ext.R b/R/read_ext.R new file mode 100644 index 00000000..b4e41353 --- /dev/null +++ b/R/read_ext.R @@ -0,0 +1,338 @@ +#' @rdname read_ext +#' +#' @title Read spectral data +#' +#' @description +#' Functions for reading spectral data from external file types. +#' Currently supported reading formats are .csv and other text files, .asp, +#' .spa, .spc, and .jdx. +#' Additionally, .0 (OPUS) and .dat (ENVI) files are supported via +#' \code{\link{read_opus}()} and \code{\link{read_envi}()}, respectively. +#' \code{\link{read_zip}()} takes any of the files listed above. +#' Note that proprietary file formats like .0, .asp, and .spa are poorly +#' supported but will likely still work in most cases. +#' +#' @param file file to be read from or written to. +#' @param colnames character vector of \code{length = 2} indicating the column +#' names for the wavenumber and intensity; if \code{NULL} columns are guessed. +#' @param method submethod to be used for reading text files; defaults to +#' \code{\link[data.table]{fread}()} but \code{\link[utils]{read.csv}()} works +#' as well. +#' @param share defaults to \code{NULL}; needed to share spectra with the +#' Open Specy community; see \code{\link{share_spec}()} for details. +#' @param metadata a named list of the metadata; see +#' \code{\link{as_OpenSpecy}()} for details. +#' @param \ldots further arguments passed to the submethods. +#' +#' @details +#' \code{read_spc()} and \code{read_jdx()} are wrappers around the +#' functions provided by the \link[hyperSpec:hyperSpec-package]{hyperSpec}. +#' Other functions have been adapted various online sources. +#' Metadata is harvested if possible. +#' There are many unique iterations of spectral file formats so there may be +#' bugs in the file conversion. Please contact us if you identify any. +#' +#' @return +#' All \code{read_*()} functions return data frames containing two columns +#' named \code{"wavenumber"} and \code{"intensity"}. +#' +#' @examples +#' read_extdata("raman_hdpe.csv") |> read_text() +#' read_extdata("raman_atacamit.spc") |> read_spc() +#' read_extdata("ftir_ldpe_soil.asp") |> read_asp() +#' read_extdata("testdata_zipped.zip") |> read_zip() +#' +#' @author +#' Zacharias Steinmetz, Win Cowger +#' +#' @seealso +#' \code{\link{read_spec}()} for reading .y(a)ml, .json, or .rds (OpenSpecy) +#' files; +#' \code{\link{read_opus}()} for reading .0 (OPUS) files; +#' \code{\link{read_envi}()} for reading .dat (ENVI) files; +#' \code{\link{read_zip}()} and \code{\link{read_any}()} for wrapper functions; +#' \code{\link[hyperSpec]{read.jdx}()}; \code{\link[hyperSpec]{read.spc}()} +#' +#' @importFrom data.table data.table as.data.table fread +#' @export +read_text <- function(file, colnames = NULL, method = "fread", + share = NULL, + metadata = list( + file_name = basename(file), + user_name = NULL, + contact_info = NULL, + organization = NULL, + citation = NULL, + spectrum_type = NULL, + spectrum_identity = NULL, + material_form = NULL, + material_phase = NULL, + material_producer = NULL, + material_purity = NULL, + material_quality = NULL, + material_color = NULL, + material_other = NULL, + cas_number = NULL, + instrument_used = NULL, + instrument_accessories = NULL, + instrument_mode = NULL, + spectral_resolution = NULL, + laser_light_used = NULL, + number_of_accumulations = NULL, + total_acquisition_time_s = NULL, + data_processing_procedure = NULL, + level_of_confidence_in_identification = NULL, + other_info = NULL, + license = "CC BY-NC"), + ...) { + dt <- do.call(method, list(file, ...)) |> as.data.table() + + if (all(grepl("^X[0-9]*", names(dt)))) stop("missing header: ", + "use 'header = FALSE' or an ", + "alternative read method", + call. = F) + + os <- as_OpenSpecy(dt, colnames = colnames, metadata = metadata, + session_id = T) + + if (!is.null(share)) share_spec(os, file = file, share = share) + + return(os) +} + +#' @rdname read_ext +#' +#' @export +read_asp <- function(file, share = NULL, + metadata = list( + file_name = basename(file), + user_name = NULL, + contact_info = NULL, + organization = NULL, + citation = NULL, + spectrum_type = NULL, + spectrum_identity = NULL, + material_form = NULL, + material_phase = NULL, + material_producer = NULL, + material_purity = NULL, + material_quality = NULL, + material_color = NULL, + material_other = NULL, + cas_number = NULL, + instrument_used = NULL, + instrument_accessories = NULL, + instrument_mode = NULL, + spectral_resolution = NULL, + laser_light_used = NULL, + number_of_accumulations = NULL, + total_acquisition_time_s = NULL, + data_processing_procedure = NULL, + level_of_confidence_in_identification = NULL, + other_info = NULL, + license = "CC BY-NC"), + ...) { + if (!grepl("\\.asp$", ignore.case = T, file)) + stop("file type should be 'asp'", call. = F) + + tr <- file.path(file) |> file(...) + lns <- tr |> readLines() |> as.numeric() + close(tr) + + y <- lns[-c(1:6)] + x <- seq(lns[2], lns[3], length.out = lns[1]) + + os <- as_OpenSpecy(x, data.table(intensity = y), metadata = metadata, + session_id = T) + + if (!is.null(share)) share_spec(os, file = file, share = share) + + return(os) +} + +#' @rdname read_ext +#' +#' @importFrom utils read.table +#' @export +read_spa <- function(file, share = NULL, + metadata = list( + file_name = basename(file), + user_name = NULL, + contact_info = NULL, + organization = NULL, + citation = NULL, + spectrum_type = NULL, + spectrum_identity = NULL, + material_form = NULL, + material_phase = NULL, + material_producer = NULL, + material_purity = NULL, + material_quality = NULL, + material_color = NULL, + material_other = NULL, + cas_number = NULL, + instrument_used = NULL, + instrument_accessories = NULL, + instrument_mode = NULL, + spectral_resolution = NULL, + laser_light_used = NULL, + number_of_accumulations = NULL, + total_acquisition_time_s = NULL, + data_processing_procedure = NULL, + level_of_confidence_in_identification = NULL, + other_info = NULL, + license = "CC BY-NC"), + ...) { + if (!grepl("\\.spa$", ignore.case = T, file)) + stop("file type should be 'spa'", call. = F) + + trb <- file.path(file) |> file(open = "rb", ...) + + seek(trb, 576, origin = "start") + spr <- readBin(trb, "numeric", n = 2, size = 4) + + if (!all(spr >= 0 & spr <= 15000 & spr[1] > spr[2])) + stop("unknown spectral range", call. = F) + + # Read the start offset + seek(trb, 386, origin = "start") + startOffset <- readBin(trb, "int", n = 1, size = 2) + # Read the length + seek(trb, 390, origin = "start") + readLength <- readBin(trb, "int", n = 1, size = 2) + + # seek to the start + seek(trb, startOffset, origin = "start") + + # we'll read four byte chunks + floatCount <- readLength / 4 + + # read all our floats + floatData <- c(readBin(trb, "double", floatCount, size = 4)) + + close(trb) + + x <- seq(spr[1], spr[2], length = length(floatData)) + y <- floatData + + os <- as_OpenSpecy(x, data.table(intensity = y), metadata = metadata, + session_id = T) + + if (!is.null(share)) share_spec(os, file = file, share = share) + + return(os) +} + + +#' @rdname read_ext +#' +#' @importFrom hyperSpec read.spc +#' @export +read_spc <- function(file, share = NULL, + metadata = list( + file_name = basename(file), + user_name = NULL, + contact_info = NULL, + organization = NULL, + citation = NULL, + spectrum_type = NULL, + spectrum_identity = NULL, + material_form = NULL, + material_phase = NULL, + material_producer = NULL, + material_purity = NULL, + material_quality = NULL, + material_color = NULL, + material_other = NULL, + cas_number = NULL, + instrument_used = NULL, + instrument_accessories = NULL, + instrument_mode = NULL, + spectral_resolution = NULL, + laser_light_used = NULL, + number_of_accumulations = NULL, + total_acquisition_time_s = NULL, + data_processing_procedure = NULL, + level_of_confidence_in_identification = NULL, + other_info = NULL, + license = "CC BY-NC"), + ...) { + spc <- read.spc(file) + + x <- spc@wavelength + y <- as.numeric(unname(spc@data$spc[1,])) + + os <- as_OpenSpecy(x, data.table(intensity = y), metadata = metadata, + session_id = T) + + if (!is.null(share)) share_spec(os, file = file, share = share) + + return(os) +} + +#' @rdname read_ext +#' +#' @importFrom hyperSpec read.jdx +#' @export +read_jdx <- function(file, share = NULL, + metadata = list( + file_name = basename(file), + user_name = NULL, + contact_info = NULL, + organization = NULL, + citation = NULL, + spectrum_type = NULL, + spectrum_identity = NULL, + material_form = NULL, + material_phase = NULL, + material_producer = NULL, + material_purity = NULL, + material_quality = NULL, + material_color = NULL, + material_other = NULL, + cas_number = NULL, + instrument_used = NULL, + instrument_accessories = NULL, + instrument_mode = NULL, + spectral_resolution = NULL, + laser_light_used = NULL, + number_of_accumulations = NULL, + total_acquisition_time_s = NULL, + data_processing_procedure = NULL, + level_of_confidence_in_identification = NULL, + other_info = NULL, + license = "CC BY-NC"), + ...) { + jdx <- read.jdx(file, ...) + + x <- jdx@wavelength + y <- as.numeric(unname(jdx@data$spc[1,])) + + lns <- readLines(file) + test <- lns[grepl("##", lns)|grepl("[:alpha:]", lns)] + vals <- ifelse(grepl("##",test), gsub("##.{1,}=", "", test), + gsub(".{1,}:", "", test)) + names <- ifelse(grepl("##",test), gsub("##", "", gsub("=.{1,}", "", test)), + gsub(":.{1,}", "", test)) + df_metadata <- as.data.table(t(vals)) + colnames(df_metadata) <- names + + os <- as_OpenSpecy(x, data.table(intensity = y), metadata = df_metadata, + session_id = T) + + if (!is.null(share)) share_spec(os, file = file, share = share) + + return(os) +} + +#' @rdname read_ext +#' +#' @export +read_extdata <- function(file = NULL) { + if (is.null(file)) { + dir(system.file("extdata", package = "OpenSpecy")) + } + else { + system.file("extdata", file, package = "OpenSpecy", mustWork = TRUE) + } +} diff --git a/R/read_multi.R b/R/read_multi.R new file mode 100644 index 00000000..912f4184 --- /dev/null +++ b/R/read_multi.R @@ -0,0 +1,80 @@ +#' @rdname read_multi +#' @title Read spectral data from multiple files +#' +#' @description +#' Wrapper functions for reading files in batch. +#' +#' @details +#' \code{read_any()} provides a single function to quickly read in any of the +#' supported formats, it assumes that the file extension will tell it how to +#' process the spectra. +#' \code{read_zip()} provides functionality for reading in spectral map files +#' with ENVI file format or as individual files in a zip folder. If individual +#' files, spectra are concatenated. +#' +#' @param file file to be read from or written to. +#' @param \ldots further arguments passed to the submethods. +#' +#' @return +#' All \code{read_*()} functions return \code{OpenSpecy} objects +#' +#' @examples +#' read_extdata("raman_hdpe.csv") |> read_any() +#' read_extdata("ftir_ldpe_soil.asp") |> read_any() +#' read_extdata("ftir_ps.0") |> read_any() +#' read_extdata("testdata_zipped.zip") |> read_zip() +#' read_extdata("CA_tiny_map.zip") |> read_zip() +#' +#' @author +#' Zacharias Steinmetz, Win Cowger +#' +#' @seealso +#' \code{\link{read_spec}()} +#' +#' @export +read_any <- function(file, ...) { + if (grepl("(\\.csv$)|(\\.txt$)", ignore.case = T, file)) { + os <- read_text(file = file, ...) + } else if (grepl("\\.[0-999]$", ignore.case = T, file)) { + os <- read_opus(file = file, ...) + } else if (grepl("(\\.asp$)|(\\.spa$)|(\\.spc$)|(\\.jdx$)", + ignore.case = T, file)) { + ex <- strsplit(basename(file), split="\\.")[[1]][-1] + os <- do.call(paste0("read_", tolower(ex)), list(file = file, ...)) + } else if (grepl("(\\.zip$)", ignore.case = T, file)) { + os <- read_zip(file = file, ...) + } else { + os <- read_spec(file = file, ...) + } + + return(os) +} + +#' @rdname read_multi +#' +#' @importFrom utils unzip +#' @importFrom data.table transpose +#' @export +read_zip <- function(file, ...) { + flst <- unzip(zipfile = file, list = T) + + tmp <- file.path(tempdir(), "OpenSpecy-unzip") + dir.create(tmp, showWarnings = F) + + unzip(file, exdir = tmp) + + if(nrow(flst) == 2 & any(grepl("\\.dat$", ignore.case = T, flst$Name)) & + any(grepl("\\.hdr$", ignore.case = T, flst$Name))) { + dat <- flst$Name[grepl("\\.dat$", ignore.case = T, flst$Name)] + hdr <- flst$Name[grepl("\\.hdr$", ignore.case = T, flst$Name)] + + os <- read_envi(file.path(tmp, dat), file.path(tmp, hdr), ...) + } else { + lst <- lapply(file.path(tmp, flst$Name), read_any, ...) + + os <- c_spec(lst) + } + + unlink(tmp, recursive = T) + return(os) +} diff --git a/R/read_opus.R b/R/read_opus.R new file mode 100644 index 00000000..d8a4bcf2 --- /dev/null +++ b/R/read_opus.R @@ -0,0 +1,189 @@ +#' @title Read spectral data from Bruker OPUS binary files +#' +#' @description +#' Read file(s) acquired with a Bruker Vertex FTIR Instrument. This function +#' is basically a fork of \code{opus_read()} from +#' \url{https://github.com/pierreroudier/opusreader}. +#' +#' @param file character vector with path to file(s). +#' @param share defaults to \code{NULL}; needed to share spectra with the +#' Open Specy community; see \code{\link{share_spec}()} for details. +#' @param metadata a named list of the metadata; see +#' \code{\link{as_OpenSpecy}()} for details. +#' @param type character vector of spectra types to extract from OPUS binary +#' file. Default is `"spec"`, which will extract the final spectra, e.g. +#' expressed in absorbance (named `AB` in Bruker OPUS programs). Possible +#' additional values for the character vector supplied to `type` are +#' `"spec_no_atm_comp"` (spectrum of the sample without compensation for +#' atmospheric gases, water vapor and/or carbon dioxide), +#' `"sc_sample"` (single channel spectrum of the sample measurement), `"sc_ref"` +#' (single channel spectrum of the reference measurement), +#' `"ig_sample"` (interferogram of the sample measurement) and `"ig_ref"` +#' (interferogram of the reference measurement). +#' @param digits Integer that specifies the number of decimal places used to +#' round the wavenumbers (values of x-variables). +#' @param atm_comp_minus4offset Logical whether spectra after atmospheric +#' compensation are read with an offset of -4 bytes from Bruker OPUS files; +#' default is `FALSE`. +#' +#' @details +#' The type of spectra returned by the function when using +#' `type = "spec"` depends on the setting of the Bruker instrument: typically, +#' it can be either absorbance or reflectance. +#' +#' The type of spectra to extract from the file can also use Bruker's OPUS +#' software naming conventions, as follows: +#' +#' - `ScSm` corresponds to `sc_sample` +#' - `ScRf` corresponds to `sc_ref` +#' - `IgSm` corresponds to `ig_sample` +#' - `IgRf` corresponds to `ig_ref` +#' +#' @return +#' An \code{OpenSpecy} object. +#' +#' @examples +#' read_extdata("ftir_ps.0") |> read_opus() +#' +#' @author Philipp Baumann, Zacharias Steinmetz, Win Cowger +#' +#' @seealso +#' \code{\link{read_spec}()} for reading .y(a)ml, .json, or .rds (OpenSpecy) +#' files; +#' \code{\link{read_text}()}, \code{\link{read_asp}()}, \code{\link{read_spa}()}, +#' \code{\link{read_spc}()}, and \code{\link{read_jdx}()} for text files, .asp, +#' .spa, .spa, .spc, and .jdx formats, respectively; +#' \code{\link{read_text}()} for reading .dat (ENVI) files; +#' \code{\link{read_zip}()} and \code{\link{read_any}()} for wrapper functions; +#' \code{\link{read_opus_raw}()}; +#' +#' @importFrom stats approx +#' @importFrom data.table as.data.table data.table +#' @export +read_opus <- function(file, share = NULL, + metadata = list( + file_name = basename(file), + user_name = NULL, + contact_info = NULL, + organization = NULL, + citation = NULL, + spectrum_type = NULL, + spectrum_identity = NULL, + material_form = NULL, + material_phase = NULL, + material_producer = NULL, + material_purity = NULL, + material_quality = NULL, + material_color = NULL, + material_other = NULL, + cas_number = NULL, + instrument_used = NULL, + instrument_accessories = NULL, + instrument_mode = NULL, + spectral_resolution = NULL, + laser_light_used = NULL, + number_of_accumulations = NULL, + total_acquisition_time_s = NULL, + data_processing_procedure = NULL, + level_of_confidence_in_identification = NULL, + other_info = NULL, + license = "CC BY-NC"), + type = "spec", digits = 1L, + atm_comp_minus4offset = FALSE) { + if (!all(grepl("\\.[0-999]$", ignore.case = T, file))) + stop("file type should be '0'", call. = F) + + res <- lapply( + file, + function(fn) { + + if (!file.exists(fn)) stop(paste0("file '", fn, "' does not exist"), + call. = F) + + # Get raw vector + rw <- readBin(fn, "raw", n = file.size(fn)) + out <- read_opus_raw(rw, type = type, + atm_comp_minus4offset = atm_comp_minus4offset) + + return(out) + }) + + # If there was only one file to read, we un-nest the list one level + if (length(file) == 1) { + res <- res[[1]] + res$spec <- as.data.table(c(res$spec)) + } else { + if (length(type) > 1) { + stop("simple output is currently only implemented for one value of the ", + "`type` option.\nA workaround this limitation is to use the ", + "`lapply` function, e.g.:\n\nlapply(c('spec', 'sc_ref'), ", + "function(x) read_opus(file, type = x))", call. = F) + } + + # Fetch wavenumbers + wns <- lapply(res, function(x) round(x$wavenumbers, digits = digits)) + + # Arbitrarily take the first rounded WN as the reference one + wn_ref <- wns[[1]] + + # Check the wavenumbers have all the same length + if (length(unique(sapply(wns, length))) > 1) { + warning("Spectra don't all have the same number of wavenumbers; ", + "interpolation will be used to combine them in a matrix", + call. = F) + } + + specs <- lapply( + res, + function(x) { + + id <- switch(type, + spec = "spec", + spec_no_atm_comp = "spec_no_atm_comp", + sc_sample = "sc_sample", + sc_ref = "sc_ref", + ig_sample = "ig_sample", + ig_ref = "ig_ref" + ) + + # Grab correct wavenumbers for interpolation + wn <- switch(type, + spec = x$wavenumbers, + spec_no_atm_comp = x$wavenumbers, + sc_sample = x$wavenumbers_sc_sample, + sc_ref = x$wavenumbers_sc_ref, + ig_sample = x$wavenumbers_sc_sample, + ig_ref = x$wavenumbers_sc_ref + ) + + # Linear interpolation to get spectra at rounded wavenumber + s <- approx( + x = wn, + y = x[[id]], + xout = wn_ref, + method = "linear" + )$y + names(s) <- as.character(wn_ref) + + return(s) + }) + + md <- do.call(rbind, lapply(res, function(x) x$metadata)) + + res <- list( + wavenumbers = wns[[1]], + spec = as.data.table(specs), + metadata = as.data.table(md) + ) + + } + + os <- as_OpenSpecy(x = res$wavenumbers, + spectra = res$spec, + metadata = cbind(as.data.table(metadata), res$metadata), + session_id = T) + + if (!is.null(share)) share_spec(os, file = file, share = share) + + return(os) +} diff --git a/R/read_opus_raw.R b/R/read_opus_raw.R new file mode 100644 index 00000000..53df1e7e --- /dev/null +++ b/R/read_opus_raw.R @@ -0,0 +1,1027 @@ +#' @title Read a Bruker OPUS spectrum binary raw string +#' +#' @description +#' Read single binary acquired with an Bruker Vertex FTIR Instrument +#' +#' @param rw a raw vector +#' @param type character vector of spectra types to extract from OPUS binary +#' file. Default is `"spec"`, which will extract the final spectra, e.g. +#' expressed in absorbance (named `AB` in Bruker OPUS programs). Possible +#' additional values for the character vector supplied to `type` are +#' `"spec_no_atm_comp"` (spectrum of the sample without compensation for +#' atmospheric gases, water vapor and/or carbon dioxide), +#' `"sc_sample"` (single channel spectrum of the sample measurement), +#' `"sc_ref"` (single channel spectrum of the reference measurement), +#' `"ig_sample"` (interferogram of the sample measurement) and `"ig_ref"` +#' (interferogram of the reference measurement). +#' @param atm_comp_minus4offset logical; whether spectra after atmospheric +#' compensation are read with an offset of -4 bytes from Bruker OPUS +#' files. Default is `FALSE`. +#' +#' @details +#' The type of spectra returned by the function when using +#' `type = "spec"` depends on the setting of the Bruker instrument: typically, +#' it can be either absorbance or reflectance. +#' +#' The type of spectra to extract from the file can also use Bruker's OPUS +#' software naming conventions, as follows: +#' +#' - `ScSm` corresponds to `sc_sample` +#' - `ScRf` corresponds to `sc_ref` +#' - `IgSm` corresponds to `ig_sample` +#' - `IgRf` corresponds to `ig_ref` +#' +#' @return +#' A list of 10 elements: +#' +#' \describe{ +#' \item{`metadata`}{a `data.frame` containing metadata from the OPUS file.} +#' \item{`spec`}{if `"spec"` was requested in the `type` option, a matrix of +#' the spectrum of the sample (otherwise set to `NULL`).} +#' \item{`spec_no_atm_comp`}{if `"spec_no_atm_comp"` was requested in the +#' `type` option, a matrix of the spectrum of the sample without atmospheric +#' compensation (otherwise set to `NULL`).} +#' \item{`sc_sample`}{if `"sc_sample"` was requested in the `type` option, a +#' matrix of the single channel spectrum of the sample (otherwise set to +#' `NULL`).} +#' \item{`sc_ref`}{if `"sc_ref"` was requested in the `type` option, a matrix +#' of the single channel spectrum of the reference (otherwise set to `NULL`).} +#' \item{`ig_sample`}{if `"ig_sample"` was requested in the `type` option, a +#' matrix of the interferogram of the sample (otherwise set to `NULL`).} +#' \item{`ig_ref`}{if `"ig_ref"` was requested in the `type` option, a matrix +#' of the interferogram of the reference (otherwise set to `NULL`).} +#' \item{`wavenumbers`}{if `"spec"` or `"spec_no_atm_comp"` was requested in +#' the `type` option, a numeric vector of the wavenumbers of the spectrum of +#' the sample (otherwise set to `NULL`).} +#' \item{`wavenumbers_sc_sample`}{if `"sc_sample"` was requested in the `type` +#' option, a numeric vector of the wavenumbers of the single channel spectrum +#' of the sample (otherwise set to `NULL`).} +#' \item{`wavenumbers_sc_ref`}{if `"sc_ref"` was requested in the `type` +#' option, a numeric vector of the wavenumbers of the single channel spectrum +#' of the reference (otherwise set to `NULL`).} +#' } +#' +#' @author Philipp Baumann and Pierre Roudier +#' +#' @seealso +#' \code{\link{read_opus}()} +#' +#' @importFrom stats setNames +#' @export +read_opus_raw <- function(rw, type = "spec", atm_comp_minus4offset = FALSE) { + # Silently support and convert the default Bruker values + type <- switch (type, + "spc" = "spec", # for backwards compatibility + "spc_nocomp" = "spec_no_atm_comp", # for backwards compatibility + "ScSm" = "sc_sample", + "ScRf" = "sc_ref", + "IgSm" = "ig_sample", + "IgRf" = "ig_ref", + type + ) + + # Sanity check on `type` + if (!all(type %in% c("spec", "spec_no_atm_comp", "sc_sample", "sc_ref", + "ig_sample", "ig_ref"))) { + stop("Invalid value for the `type` option.", call. = F) + } + + # Avoid `R CMD check` NOTE: no visible binding for global variable ... + x <- y <- i <- npt <- NULL + + # do not stop if one OPUS has erroneous parsing for any reason + try({ + + # Read byte positions for selected 3 letter strings that flag important + # spectral information ------------------------------------------------------- + + # Get positions of "END" strings + end <- grepRaw("END", rw, all = TRUE) + 11 + # Get all positions of "NPT" (number of points) string + npt_all <- grepRaw("NPT", rw, all = TRUE) + 3 + # Get frequency of first (FXV) and last point (LXV) positions + fxv_all <- grepRaw("FXV", rw, all = TRUE) + 7 + lxv_all <- grepRaw("LXV", rw, all = TRUE) + 7 + + # For some files, the number of positions where "FXV" and "LXV" occur + # are not equal, e.g. for the file in + # data/soilspec_esal_bin/BF_mo_01_soil_cal.0 ; As a consequence, the + # fist and last point numbers (e.g. wavenumber or points for interferograms) + # are not correctly read. This results in an error when trying to calculate + # the wavenumbers; The below code is a quick and dirty fix to remove + # FXV values that don't have LXV values and vice versa + # (difference between "LXV" and "FXV" for a spectral data block + # should be 16) -------------------------------------------------------------- + if (length(fxv_all) > length(lxv_all)) { + + # We detect the `fxv_all` values to remove by finding those that do NOT have + # a 16 bit difference with the `lxv_all` values + idx_extra <- which(!(fxv_all + 16) %in% lxv_all) + fxv_all <- fxv_all[-idx_extra] + } + + if (length(lxv_all) > length(fxv_all)) { + + # We detect the `lxv_all` values to remove by finding those that do NOT have + # a 16 bit difference with the `fxv_all` values + idx_extra <- which(!(lxv_all - 16) %in% fxv_all) + lxv_all <- lxv_all[-idx_extra] + } + + # Reduce size of npt_all ----------------------------------------------------- + # Some files have an extra "NPT" string without FXV, LXV, and spectral block + if (length(npt_all) != length(fxv_all)) { + + diff_npt_fxv <- lapply(npt_all, function(x) fxv_all - x) + + min_bigger0_smallerequal40 <- lapply( + diff_npt_fxv, + function(x) { + which_min_bigger0 <- x == min(x[x > 0]) + which_smallerequal40 <- x <= 40 + which_min_bigger0 & which_smallerequal40 + } + ) + + which_npt_valid <- vapply( + min_bigger0_smallerequal40, + FUN = function(x) any(x == TRUE), + FUN.VALUE = logical(1) + ) + + npt_all <- npt_all[which_npt_valid] + } + + # ---------------------------------------------------------------------------- + + ## Read basic spectral information =========================================== + con <- rawConnection(rw) + + # Read all number of points (NPT) at once + NPT <- vapply( + npt_all, + FUN = function(npt) { + seek(con, npt, origin = "start", rw = "read") + readBin(con, what = "integer", n = 12, size = 4)[2] + }, + FUN.VALUE = numeric(1) + ) + + # Specific error for file: <"data/soilspec_eth_bin/CI_tb_05_soil_cal.2"> + # "Invalid number of bytes" when trying to read spectra + # -> Reason: NPT at position 1 is 995236000 !!! + # Omit this entry in NPT and corresponding byte position in npt_all + # Quick fix ------------------------------------------------------------------ + npt_all <- npt_all[NPT < 40000] + NPT <- NPT[NPT < 40000] + + # ---------------------------------------------------------------------------- + + # Figure out how many spectral blocks exist and select final spectra + # positions; end_spc is vector of offsets where spectra start + if (length(end) == 1) { + end_spc <- end + } else { + end_spc <- end[diff(end) > 4 * min(NPT)] + } + + ## Find final spectra information block positions + ## that belong to spectra data =============================================== + + # Save positions that contain possible spectra data block + # standard parameters + spc_param_list <- list( + 'npt' = npt_all, + 'fxv' = fxv_all, + 'lxv' = lxv_all + ) + + ## Return list of final parameters corresponding to data blocks that contain + ## spectra, elements are npt (number of points), + ## fxv (frequency of first point) and lxv (frequency of last point); + ## returned values represent byte positions in the file where spectra + ## parameters are stored. ---------------------------------------------------- + return_spc_param <- function(end_spc, spc_param_list) { + + # Difference between any NPT position vector elements end_spc element + # (end_spc[i] is a scalar, constant value at iteration i) + diff_l <- lapply(end_spc, function(x) npt_all - x) + # Test of any vector in list contains -164 (returns list of vectors + # TRUE or FALSE) + isminus164 <- lapply(diff_l, function(x) x == -164) + + # Find minimum positive difference within each list + if (length(diff_l) == 1) {sel_min <- list(TRUE)} else { + sel_min <- lapply(diff_l, + function(x) { + if (any(x > 0)) { + x == min(x[x > 0]) + } else {x == -164} + }) + } + + # Set FALSE repeated vector in sel_min element where TRUE positions are + # duplicated + which_elem_dupl <- which(duplicated(vapply(sel_min, FUN = which, + FUN.VALUE = numeric(1)))) + + if (length(which_elem_dupl) > 1) { + sel_min[which_elem_dupl] <- NULL + # Reduce end_spc with duplicated elements + end_spc <- end_spc[- which_elem_dupl] + } + + # Select minimum difference NPT position for each END position + npt_min <- Map( + function(x, y) x[y], + rep(list(npt_all), length(end_spc)), + sel_min + ) + + npt_min <- Filter(length, npt_min) + + # Select spectra parameters that immediately follow END positions before + # corresponding spectra + param_min <- setNames( + lapply( + seq_along(spc_param_list), + function(i) { + Map(function(x, y) x[y], + rep(list(spc_param_list[[i]]), length(end_spc)), + sel_min) + } + ), + names(spc_param_list) + ) + + # Test if any difference in list is -164 + if (any(unlist(isminus164) == TRUE)) { + # Find all list element that contain TRUE in logical vector + minus164 <- lapply(isminus164, function(x) Find(isTRUE, x)) + # Return element position of last TRUE in list + where <- function(f, x) { + vapply(x, f, logical(1)) + } + last_minus164 <- Position(isTRUE, where(isTRUE, minus164), + right = TRUE) + # Replace positions in parameter list are at positions of last + # -164 difference between end_spc element and NPT position + param_min <- setNames( + lapply( + seq_along(spc_param_list), + function(i) { + param_min[[i]][[last_minus164]] <- + spc_param_list[[i]][isminus164[[last_minus164]]] + param_min[[i]] + } + ), + names(spc_param_list) + ) + } + # Return list of final parameters corresponding to data blocks that + # contain spectra + param_spc <- lapply(param_min, unlist) + param_spc$end_spc <- end_spc + param_spc + } + + # Save spectra parameter list + param_spc <- return_spc_param(end_spc, spc_param_list) + + # Create individual vectors containing spectra parameters + npt_spc <- param_spc[["npt"]] + fxv_spc <- param_spc[["fxv"]] + lxv_spc <- param_spc[["lxv"]] + end_spc <- param_spc[["end_spc"]] + + # Read number of points corresponding to spectra in file --------------------- + NPT_spc <- vapply( + seq_along(npt_spc), + FUN = function(i) { + seek(con, npt_spc[i], origin = "start", rw = "read") + readBin(con, what = "integer", n = 12, size = 4)[2] + }, + FUN.VALUE = numeric(1) + ) + + # Delete NPT with negative signs + NPT_spc <- NPT_spc[NPT_spc > 0] + + ## Read all spectra ========================================================== + + spc <- Map( + function(end, NPT) { + seek(con, end - 4, origin = "start", rw = "read") + readBin(con, what = "numeric", n = NPT, size = 4, endian = "little") + }, + end_spc, + NPT_spc + ) + + # Read FXV and LXV and calculate wavenumbers -------------------------------- + + FXV_spc <- vapply( + fxv_spc, + FUN = function(fxv_spc) { + seek(con, fxv_spc, origin = "start", rw = "read") + readBin(con, what = "numeric", n = 16, size = 8)[1] + }, + FUN.VALUE = numeric(1) + ) + + LXV_spc <- vapply( + lxv_spc, + FUN = function(lxv_spc) { + seek(con, lxv_spc, origin = "start", rw = "read") + readBin(con, what = "numeric", n = 16, size = 8)[1] + }, + FUN.VALUE = numeric(1) + ) + + # Calculate wavenumbers + wavenumbers <- lapply( + seq_along(FXV_spc), + function(i) { + rev(seq(LXV_spc[i], FXV_spc[i], (FXV_spc[i] - LXV_spc[i]) / + (NPT_spc[i] - 1))) + } + ) + + ## Assigning list of initially read spectra depending on block type ========== + + # Assign an index name to the spectra and parameters for reading + names(end_spc) <- paste0("idx", 1:length(end_spc)) + names(spc) <- paste0("idx", 1:length(spc)) + names(NPT_spc) <- paste0("idx", 1:length(NPT_spc)) + names(FXV_spc) <- paste0("idx", 1:length(FXV_spc)) + names(wavenumbers) <- paste0("idx", 1:length(wavenumbers)) + + # Check if elements in FXV_spc (frequency of first point) are equal to 0; + # these are interferogram spectra + which_Ig <- FXV_spc[which(FXV_spc == 0)] + Ig_assigned <- if (length(which_Ig) == 0) { + NULL + } else if (length(which_Ig) == 1) { + list( + spc_idx = names(which_Ig), + spc_code = "ig_sample" + ) + } else if (length(which_Ig) == 3) { + list( + spc_idx = names(which_Ig)[c(1, 3)], + spc_code = c("ig_sample", "ig_ref") + ) + } else { + list( + spc_idx = names(which_Ig), + spc_code = c("ig_sample", "ig_ref") + ) + } + + na_assigned <- list( + spc_idx = NULL, + spc_code = NULL + ) + if (length(which_Ig) == 3) { + # Assign duplicated interferogram spectrum to 'not available' assigned + na_assigned <- list( + spc_idx = names(which_Ig)[2], + spc_code = NA + ) + } + + # Remove NA assigned spectra in spc list ------------------------------------- + if (!is.null(na_assigned$spc_idx)) { + spc[na_assigned$spc_idx] <- NULL + # Remove wavenumbers with NA assigned spectra in spc list + wavenumbers[na_assigned$spc_idx] <- NULL + } + + # Assign single channel spectra if present in file --------------------------- + + # Return idx (index names) of all remaining spectra that are not + # interferograms + notIg <- names(spc)[ + !names(spc) %in% c(Ig_assigned$spc_idx, na_assigned$spc_idx) + ] + + # Check if the MIR range was measured + wavenumbers_mir <- lapply( + names(wavenumbers[notIg]), + function(i) { + spc[[i]][wavenumbers[notIg][[i]] < 2392 & + wavenumbers[notIg][[i]] > 2358] + }) + + is_mir <- any( + vapply( + wavenumbers_mir, + FUN = function(x) {length(x) != 0}, + FUN.VALUE = logical(1) + ) + ) + + if (isTRUE(is_mir)) { + # Calculate peak ratio for absorbance at around 2392 cm^(-1) + # and 2358 cm^(-1) + peak_ratio <- lapply( + lapply(names(wavenumbers[notIg]), + function(i) { + spc[[i]][wavenumbers[notIg][[i]] < 2392 & + wavenumbers[notIg][[i]] > 2358] + }), + function(j) j[[1]] / j[[length(j)]] + ) + names(peak_ratio) <- names(spc[notIg]) + # Single channel (Sc) assignment list + which_Sc <- names(which(peak_ratio > 2)) + } else { + peak_ratio <- lapply( + lapply( + names(wavenumbers[notIg]), + function(i) { + spc[[i]][wavenumbers[notIg][[i]] < 5340 & + wavenumbers[notIg][[i]] > 5318] + } + ), + function(j) { + j[[1]] / j[[length(j)]] + } + ) + names(peak_ratio) <- names(spc[notIg]) + # Single channel (Sc) assignment list + which_Sc <- names(which(peak_ratio < 0.9)) + } + + # browser() + + # Check for single channel, exclude spectral blocks already assigned to + # interferograms + Sc_assigned <- if (length(which_Sc) == 0) { + NULL + } else if (length(which_Sc) == 1) { + list( + spc_idx = which_Sc, + spc_code = "sc_sample" + ) + } else { + list( + spc_idx = which_Sc, + spc_code = c("sc_sample", "sc_ref") + ) + } + + # Assign corrected and uncorrected (if present) ------------------------------ + # AB spectra list + which_AB <- names(spc)[ + !names(spc) %in% c( + Ig_assigned[["spc_idx"]], + na_assigned[["spc_idx"]], + Sc_assigned[["spc_idx"]] + ) + ] + + AB_assigned <- if (length(which_AB) == 1) { + list( + spc_idx = which_AB, + spc_code = "spec" + ) + } else { + list( + spc_idx = which_AB, + spc_code = c("spec_no_atm_comp", "spec") + ) + } + + # Read result spectrum with new offset (no `-4`) when atmospheric + # compensation was done by the OPUS software; replace the spectrum position + # with index name idx that corresponds to final spectrum after atmospheric + # compensation; OPUS files from particular spectrometers/OPUS software + # versions do still need the same offset end_spc[[spc_idx]] - 4 as the other + # spectra types; new argument atm_comp_minus4offset (default FALSE) is a + # quick fix to read files with different offsets after atmospheric + # compensation --------------------------------------------------------------- + if (length(which_AB) == 2 && !atm_comp_minus4offset) { + seek(con, end_spc[which_AB[length(which_AB)]], origin = "start", + rw = "read") + spc[[which_AB[length(which_AB)]]] <- readBin( + con, + what = "numeric", + # n = NPT_spc[which_AB[length(which_AB)]] * 4, + n = NPT_spc[which_AB[length(which_AB)]], + size = 4, + endian = "little" + ) + + } + + # Assign spectra type for final spectra in element names of spc list --------- + # Combine spectral assignments lists + list_assigned <- list( + 'Ig' = Ig_assigned, + 'Sc' = Sc_assigned, + 'AB' = AB_assigned + ) + + # Transpose spectra assignment list, first remove NULL elements in list + # https://stackoverflow.com/questions/54970592/how-to-transpose-a-list-of-vectors + .base_transpose <- function(l) do.call(Map, c(f = list, l)) + # to avoid bringing purrr::transpose + + list_assigned_t <- .base_transpose( + Filter(Negate(function(x) is.null(unlist(x))), list_assigned) + ) + + # Save spectra index (spc_idx) and spectra code (spc_code) + # in character vector + spc_idx <- unlist(list_assigned_t[["spc_idx"]]) + spc_code <- unlist(list_assigned_t[["spc_code"]]) + + # Order spc_idx from 1 to n spectra (n = length of end_spc) + order_spc <- as.numeric( + sub(".*idx", "", unlist(list_assigned_t[["spc_idx"]])) + ) + + spc_type <- spc_code[order(order_spc)] + + # Set spectrum type as element names of spectra list (spc) + names(spc) <- spc_type + + # Set spectrum type in wavenumbers list + names(wavenumbers) <- spc_type + + # Read with new offset when first value of + # ScSm single channel sample spectrumspectrum is 0 and replace previous --- + if (any(names(spc) %in% "sc_sample" & spc[["sc_sample"]][1] == 0)) { + seek( + con, + end_spc[Sc_assigned$spc_idx[Sc_assigned$spc_code == "sc_sample"]], + origin = "start", + rw = "read" + ) + spc[["sc_sample"]] <- readBin( + con, + what = "numeric", + # n = NPT_spc[Sc_assigned$spc_idx[Sc_assigned$spc_code == "ScSm"]] * 4, + n = NPT_spc[Sc_assigned$spc_idx[Sc_assigned$spc_code == "sc_sample"]], + size = 4, + endian = "little" + ) + } + + ## Get additional parameters from OPUS binary file =========================== + + # Instrument parameters ------------------------------------------------------ + + ins <- grepRaw("INS", rw, all = TRUE) # Instrument type + seek(con, ins[length(ins)] + 7, origin = "start", rw = "read") + INS <- readBin( + con, + what = "character", + n = 10, + size = 1, + endian = "little" + )[1] + + lwn <- grepRaw("LWN", rw, all = TRUE)[1] + 7 # Laser wavenumber + seek(con, lwn, origin = "start", rw = "read") + LWN <- readBin( + con, + what = "numeric", + n = 8, + size = 8 + )[1] + + tsc <- grepRaw("TSC", rw, all = TRUE) + 7 # Scanner temperature + TSC_all <- lapply(tsc, function(tsc) { + seek(con, tsc, origin = "start", rw = "read") + readBin( + con, + what = "numeric", + n = 16, + size = 8 + )[[1]] + }) + + # Read relative humidity of the interferometer during measurement + hum_rel <- grepRaw("HUM", rw, all = TRUE) + 7 + HUM_rel <- lapply( + hum_rel, + function(hum_rel) { + # can include sample and background humidity + seek(con, hum_rel, origin = "start", rw = "read") + readBin( + con, + what = "integer", + n = 16, + size = 8 + )[[1]] + }) + HUM_rel <- HUM_rel[[1]] + + # Read absolute humidity of the interferometer during measurement + hum_abs <- grepRaw("HUA", rw, all = TRUE) + 7 + HUM_abs <- lapply( + hum_abs, + function(hum_abs) { + # can include sample and background humidity + seek(con, hum_abs, origin = "start", rw = "read") + readBin( + con, + what = "numeric", + n = 16, + size = 8 + )[[1]] + }) + HUM_abs <- unlist(HUM_abs) + if (is.null(HUM_abs)) HUM_abs <- NA + + # Optics parameters ---------------------------------------------------------- + + src <- grepRaw("SRC", rw, all = TRUE) # Source: MIR or NIR + seek(con, src[length(src)] + 4, origin = "start", rw = "read") + SRC <- readBin( + con, + what = "character", + n = 3, + size = 1, + endian = "little" + )[[1]][1] + + # instrument range + instr_range <- paste(INS, SRC, sep = "-") + instr_range <- unlist(strsplit(instr_range, "'"))[1] + + bms <- grepRaw("BMS", rw, all = TRUE) # Beamsplitter + seek(con, bms[length(bms)] + 4, origin = "start", rw = "read") + BMS <- readBin( + con, + what = "character", + n = 3, + size = 1, + endian = "little" + )[[1]][1] + BMS <- unlist(strsplit(BMS, "'", useBytes = TRUE))[1] + + # Fourier transform parameters ----------------------------------------------- + zff <- grepRaw("ZFF", rw, all = TRUE)[1] + 5 # Zero filling factor (numeric) + seek(con, zff, origin = "start", rw = "read") + ZFF <- readBin( + con, + what = "integer", + n = 4, + size = 2, + endian = "little" + )[1] + + # (Additional) Standard parameters ------------------------------------------- + + csf_all <- grepRaw("CSF", rw, all = TRUE) + 7 # y-scaling factor + # Read only CSF byte positions that correspond to final spectra + CSF <- lapply( + csf_all[npt_all %in% npt_spc], + function(csf) { + seek(con, csf, origin = "start", rw = "read") + readBin( + con, + what = "numeric", + n = 8, + size = 8, + endian = "little" + )[1] + }) + + mxy_all <- grepRaw("MXY", rw, all = TRUE) + 7 # Y-maximum + MXY <- unlist( + lapply( + mxy_all[npt_all %in% npt_spc], + function(mxy) { + seek(con, mxy, origin = "start", rw = "read") + readBin( + con, + what = "numeric", + n = 8, + size = 8 + )[1] + } + ) + ) + + mny <- grepRaw("MNY", rw, all = TRUE) + 7 # Y-minimum + + dxu_all <- grepRaw("DXU", rw, all = TRUE) + 7 # X units + DXU <- lapply( + dxu_all, + function(dxu) { + seek(con, dxu, origin = "start", rw = "read") + readBin( + con, + what = "character", + n = 3, + size = 1, + endian = "little" + )[1] + } + ) + + # Y units -> PR: there is no DYU present in file -> PB: yes, but there is in + # others + dyu_all <- grepRaw("DYU", rw, all = TRUE) + 7 + dat <- grepRaw("DAT", rw, all = TRUE) + 7 # Date + + tim <- grepRaw("TIM", rw, all = TRUE) + 7 # Time + + time <- unlist( + lapply( + tim, + function(tim) { + seek(con, tim, origin = "start", rw = "read") + readBin( + con, + what = "character", + n = 22, + size = 1, + endian = "little" + )[1] + } + ) + ) + + # Time needs to have a valid encoding; replace entries that have invalid + # encoding with NA + time_invalid <- !vapply(time, validEnc, FUN.VALUE = logical(1)) + time[time_invalid] <- NA + + # Only select "DAT" string positions that are immediately before time + dat_sel <- vapply( + seq_along(tim), + FUN = function(i) { + diff_sel <- dat - tim[i] + res <- dat[which(diff_sel <= 32 & diff_sel >= -20)] + + # If no valid position can be extracted we flag value as NA + if (length(res) == 0) res <- NA + + res + }, + FUN.VALUE = numeric(1) + ) + + date <- lapply( + dat_sel, + function(dat){ + + # If not date string was extracted we just return NA + if (is.na(dat)) { + res <- NA + } else { + seek(con, dat, origin = "start", rw = "read") + res <- readBin( + con, + what = "character", + n = 10, + size = 1, + endian = "little" + )[1] + } + + res + + } + ) + + date_time <- unique(paste(date, time)) + + # Convert date_time from character to class POSIXct (calendar date and time) + date_time <- as.POSIXct(date_time, format = "%d/%m/%Y %H:%M:%S") + # , tz = "GMT+1") # tz is argument for time zone + + # Scale all spectra with y-scaling factor if any of spectra types present + # in file are not 1 ---------------------------------------------------------- + # Set names of CSF elements equal to spectra list element names + names(CSF) <- names(spc) + + if (any(unlist(CSF) != 1)) { + # Return all elements in CSF that have scaling value not equal to 1 + CSF_toscale <- Filter(function(x) x != 1, CSF) + # Apply scaling for spectra with CSF value not equal to 1; + # Map() returns list + spc_scaled <- Map(function(CSF, spc) CSF * spc, unlist(CSF_toscale), + spc[names(CSF_toscale)]) + + # Replace all spc list elements that have CSF not equal 1 with + # scaled values + spc <- replace(x = spc, list = names(CSF_toscale), values = spc_scaled) + } + + # Data acquisition parameters ------------------------------------------------ + + plf <- grepRaw("PLF", rw, all = TRUE) + 4 # Result spectrum + PLF_all <- lapply( + plf, + function(plf) { + seek(con, plf, origin = "start", rw = "read") + res <- readBin( + con, + what = "character", + n = 2, + size = 1, + endian = "little" + )[1] + unlist(strsplit(res, ",", useBytes = TRUE))[1] + } + ) + + # Select only result spectra abbreviations that have valid encoding and + # are more than 0 characters long; some OPUS files had invalid encoding + # for this entry (first element) + PLF_invalid <- !vapply(PLF_all, validEnc, FUN.VALUE = logical(1)) + PLF_all[PLF_invalid] <- NA + PLF <- unlist(PLF_all[lapply(PLF_all, nchar) > 0]) + PLF <- unique(unlist(strsplit(PLF, "'"))) + + res <- grepRaw("RES", rw, all = TRUE)[1] + 5 # Resolution (wavenumber) + seek(con, res, origin = "start", rw = "read") + RES <- readBin( + con, + what = "integer", + n = 4, + size = 2, + endian = "little" + )[1] + + ## Create sample metadata objects ============================================ + + snm <- grepRaw("SNM", rw, all = TRUE)[1] + 7 + seek(con, snm, origin = "start", rw = "read") + SNM <- readBin( + con, + what = "character", + n = 30, + size = 1, + endian = "little" + )[1] + + # Close connection + close(con) + + # == sample ID == + + sample_name <- unlist(strsplit(SNM, ";", useBytes = TRUE))[1] + sample_id <- sample_name + # PB: 2021-09-01: todo: add `rep_no` via `file_id` or `stream_id` + file_name_nopath <- NA # PB: 2021-08-19: to fix + + # Create unique_id using file_name and time + ymdhms_id <- max(date_time, na.rm = TRUE) + unique_id <- paste0(sample_id, "_", ymdhms_id) + + ## Convert all spectra in list spc into a matrix of 1 row ==================== + spc_m <- lapply(spc, function(x) matrix(x, ncol = length(x), byrow = FALSE)) + + # Add dimnames (wavenumbers for columns and unique_id for rows + spc_m <- setNames( + lapply( + seq_along(spc_m), + function(i) { + colnames(spc_m[[i]]) <- round(wavenumbers[[i]], 1) + rownames(spc_m[[i]]) <- unique_id + spc_m[[i]] + } + ), + names(spc_m) + ) + + # Save all relevant metadata + metadata <- data.frame( + unique_id = unique_id, + # Removing "file_id" which does not make sense on RAW streams + # file_id = file_name_nopath, # pb (20170514): changed `scan_id` to `file_id` + sample_id = sample_id, + # rep_no = as.numeric(rep_no), # pb 2021-09-01: find workaround to re-add + date_time_sm = max(date_time, na.rm = TRUE), + date_time_rf = min(date_time, na.rm = TRUE), + sample_name = SNM, + instr_name_range = instr_range, + resolution_wn = RES, + # Result spectrum; e.g. "AB" = Absorbance + # result_spc = ifelse(length(unique(PLF)) == 1, unique(PLF), unique(PLF)[2]), + # // pb: 2019-11-19: allow NULL value for PLF + result_spc = if (length(unique(PLF)) == 1) { + unique(PLF) + } else if (length(unique(PLF)) > 1) { + unique(PLF)[2] + } else { + NA + }, + beamspl = BMS, + laser_wn = LWN, + # `spc_in_file`: character vector of spectra found in OPUS file + spc_in_file = paste(unlist(list_assigned_t[["spc_code"]]), + collapse = ";", sep = ";"), + zero_filling = ZFF, # Zero filling factor for fourier transformation + # Temperature of scanner during sample measurement + temp_scanner_sm = TSC_all[[length(TSC_all)]], # select last element + # Temperature of scanner during reference measurement; + # if there is only one element in TSC_all, temperature during reference + # measurement is not saved + temp_scanner_rf = ifelse(length(TSC_all) == 1, NA, TSC_all[[1]]), + # Relative humidity + hum_rel_sm = HUM_rel[[length(HUM_rel)]], # sample measurement + hum_rel_rf = ifelse(length(HUM_rel) == 1, NA, HUM_rel[[1]]), # reference + # measurement + # Absolute humidity; sample measurement (sm); reference measurment (rf); + # note: for Vertex 70 instrument HUA is not present, in this case, + # HUM_abs is a list without elements + hum_abs_sm = ifelse(length(HUM_abs) != 0, HUM_abs[[length(HUM_abs)]], NA), + hum_abs_rf = ifelse( + length(HUM_abs) == 1 | length(HUM_abs) == 0, + NA, + HUM_abs[[1]] + ), # reference measurement + stringsAsFactors = FALSE + ) + + ## Allocate and return data from spectra in output list (out) ================ + + out <- list( + # Metadata + 'metadata' = metadata, + 'spec' = if("spec" %in% type) { + if ("spec" %in% names(spc_m)) { + spc_m[["spec"]] + } else { + warning("No spectra found", call. = F) + NULL + } + } else { + NULL + }, + 'spec_no_atm_comp' = if ("spec_no_atm_comp" %in% type) { + if ("spec_no_atm_comp" %in% names(spc_m)) { + spc_m[["spec_no_atm_comp"]] + } else { + warning("No 'spec_no_atm_comp' spectra found", call. = F) + NULL + } + } else { + NULL + }, + 'sc_sample' = if ("sc_sample" %in% type) { + if ("sc_sample" %in% names(spc_m)) { + spc_m[["sc_sample"]] + } else { + warning("No 'sc_sample' spectra found", call. = F) + NULL + } + } else { + NULL + }, + 'sc_ref' = if ("sc_ref" %in% type) { + if ("sc_ref" %in% names(spc_m)) { + spc_m[["sc_ref"]] + } else { + warning("No 'sc_ref' spectra found", call. = F) + NULL + } + } else { + NULL + }, + 'ig_sample' = if ("ig_sample" %in% type) { + if ("ig_sample" %in% names(spc_m)) { + spc_m[["ig_sample"]] + } else { + warning("No 'ig_sample' spectra found", call. = F) + NULL + } + } else { + NULL + }, + 'ig_ref' = if ("ig_ref" %in% type) { + if("ig_ref" %in% names(spc_m)) { + spc_m[["ig_ref"]] + } else { + warning("No 'ig_ref' spectra found", call. = F) + NULL + } + } else { + NULL + }, + # Wavenumbers of final AB spectra + wavenumbers = wavenumbers[["spec"]], + wavenumbers_sc_sample = if ("sc_sample" %in% type) { + wavenumbers[["sc_sample"]] + } else { + NULL + }, + wavenumbers_sc_ref = if ("sc_ref" %in% type) { + wavenumbers[["sc_ref"]] + } else { + NULL + } + ) + + # Return spectra data and metadata contained as elements in list out + return(out) + }) # closes try() function +} diff --git a/R/read_spec.R b/R/read_spec.R deleted file mode 100644 index 0a254bb6..00000000 --- a/R/read_spec.R +++ /dev/null @@ -1,279 +0,0 @@ -#' @rdname read_spec -#' -#' @title Read spectral data -#' -#' @description -#' Functions for reading spectral data types including .asp, .jdx, -#' .spc, .spa, .0, and .csv. -#' -#' @details -#' \code{read_spc()} and \code{read_jdx()} are just a wrapper around the -#' functions provided by the \link[hyperSpec:hyperSpec-package]{hyperSpec} -#' package. -#' Other functions have been adapted various online sources. -#' All functions convert datasets to a 2 column table with one column labeled -#' "wavenumber" and the other "intensity". There are many unique iterations of -#' spectral file formats so there may be bugs in the file conversion. -#' Please contact us if you identify any. -#' -#' @param file file to be read from. -#' @param cols character vector of \code{length = 2} indicating the colum names -#' for the wavenumber and intensity; if \code{NULL} columns are guessed. -#' @param method submethod to be used for reading text files; defaults to -#' \link[utils]{read.csv} but \link[data.table]{fread} works as well. -#' @param share defaults to \code{NULL}; needed to share spectra with the -#' Open Specy community; see \code{\link{share_spec}()} for details. -#' @param id a unique user and/or session ID; defaults to -#' \code{paste(digest(Sys.info()), digest(sessionInfo()), sep = "/")}. -#' @param \ldots further arguments passed to the submethods. -#' -#' @return -#' All \code{read_*()} functions return data frames containing two columns -#' named \code{"wavenumber"} and \code{"intensity"}. -#' -#' @examples -#' read_text(read_extdata("raman_hdpe.csv")) -#' read_asp(read_extdata("ftir_ldpe_soil.asp")) -#' read_0(read_extdata("ftir_ps.0")) -#' -#' @author -#' Zacharias Steinmetz, Win Cowger -#' -#' @seealso -#' \code{\link[hyperSpec]{read.jdx}()}; \code{\link[hyperSpec]{read.spc}()}; -#' \code{\link[hexView]{readRaw}()}; \code{\link{share_spec}()} -#' -#' @importFrom dplyr %>% -#' @export -read_text <- function(file = ".", cols = NULL, method = "read.csv", - share = NULL, id = paste(digest(Sys.info()), - digest(sessionInfo()), - sep = "/"), - ...) { - df <- do.call(method, list(file, ...)) %>% - data.frame() - - if (all(grepl("^X[0-9]*", names(df)))) stop("missing header: ", - "use 'header = FALSE' or an ", - "alternative read method") - - # Try to guess column names - if (is.null(cols)) { - if (all(grepl("^V[0-9]*", names(df)))) { - cols <- 1:2 - warning("missing header: guessing 'wavenumber' and 'intensity' data ", - "from the first two columns; use 'cols' to supply user-defined ", - "columns") - } else { - cols <- c(names(df)[grep("(wav*)", ignore.case = T, names(df))][1L], - names(df)[grep("(transmit*)|(reflect*)|(abs*)|(intens*)", - ignore.case = T, names(df))][1L]) - } - } - if (any(is.na(cols))) stop("undefined columns selected; columns should be ", - "named 'wavenumber' and 'intensity'") - if (cols[1] == cols[2]) stop("inconsistent input format") - - df <- df[cols] - - # Check if columns are numeric - if (!all(sapply(df, is.numeric))) stop("input not numeric") - - names(df) <- c("wavenumber", "intensity") - - if (!is.null(share)) share_spec(df, file = file, share = share, id = id) - - return(df) -} - -#' @rdname read_spec -#' -#' @export -read_asp <- function(file = ".", share = NULL, id = paste(digest(Sys.info()), - digest(sessionInfo()), - sep = "/"), - ...) { - if (!grepl("\\.asp$", ignore.case = T, file)) - stop("file type should be 'asp'") - - tr <- file.path(file) %>% file(...) - lns <- tr %>% readLines() %>% as.numeric() - close(tr) - - y <- lns[-c(1:6)] - x <- seq(lns[2], lns[3], length.out = lns[1]) - - df <- data.frame(wavenumber = x, intensity = y) - - if (!is.null(share)) share_spec(df, file = file, share = share, id = id) - - return(df) -} - -#' @rdname read_spec -#' -#' @importFrom utils read.table -#' @export -read_spa <- function(file = ".", share = NULL, id = paste(digest(Sys.info()), - digest(sessionInfo()), - sep = "/"), - ...) { - if (!grepl("\\.spa$", ignore.case = T, file)) - stop("file type should be 'spa'") - - trb <- file.path(file) %>% file(open = "rb", ...) - - seek(trb, 576, origin = "start") - spr <- readBin(trb, "numeric", n = 2, size = 4) - - if (!all(spr >= 0 & spr <= 15000 & spr[1] > spr[2])) - stop("unknown spectral range") - - # Read the start offset - seek(trb, 386, origin = "start") - startOffset <- readBin(trb, "int", n = 1, size = 2) - # Read the length - seek(trb, 390, origin = "start") - readLength <- readBin(trb, "int", n = 1, size = 2) - - # seek to the start - seek(trb, startOffset, origin = "start") - - # we'll read four byte chunks - floatCount <- readLength / 4 - - # read all our floats - floatData <- c(readBin(trb, "double", floatCount, size = 4)) - - close(trb) - - df <- data.frame(wavenumber = seq(spr[1], spr[2], length = length(floatData)), - intensity = floatData) - - if (!is.null(share)) share_spec(df, file = file, share = share, id = id) - - return(df) -} - -#' @rdname read_spec -#' -#' @importFrom hyperSpec read.jdx -#' @export -read_jdx <- function(file = ".", share = NULL, id = paste(digest(Sys.info()), - digest(sessionInfo()), - sep = "/"), - ...) { - jdx <- read.jdx(file, ...) - - df <- data.frame(wavenumber = jdx@wavelength, - intensity = as.numeric(unname(jdx@data$spc[1,]))) - - if (!is.null(share)) share_spec(df, file = file, share = share, id = id) - - return(df) -} - -#' @rdname read_spec -#' -#' @importFrom hyperSpec read.spc -#' @export -read_spc <- function(file = ".", share = NULL, id = paste(digest(Sys.info()), - digest(sessionInfo()), - sep = "/"), - ...) { - spc <- read.spc(file) - - df <- data.frame(wavenumber = spc@wavelength, - intensity = as.numeric(unname(spc@data$spc[1,]))) - - if (!is.null(share)) share_spec(df, file = file, share = share, id = id) - - return(df) -} - -#' @rdname read_spec -#' -#' @importFrom hexView readRaw blockString -#' @export -read_0 <- function(file = ".", share = NULL, id = paste(digest(Sys.info()), - digest(sessionInfo()), - sep = "/"), - ...) { - if (!grepl("\\.[0-999]$", ignore.case = T, file)) - stop("file type should be '0'") - - pa <- readRaw(file, offset = 0, nbytes = file.info(file)$size, human = "char", - size = 1, endian = "little") - pr <- pa$fileRaw - - # Get positions where the following parameters are found in the file - codes <- c("ZFF", "RES", "SNM", "DAT", "LWN", "FXV", "LXV", "NPT", "MXY", - "MNY", "END", "TIM") - - z <- grepRaw(codes[1], pr, all = TRUE)[1] + 5 - re <- grepRaw(codes[2], pr, all = TRUE)[1] + 5 - snm <- grepRaw(codes[3], pr, all = TRUE)[1] + 7 - dat <- grepRaw(codes[4], pr, all = TRUE)[1] + 7 - lwn <- grepRaw(codes[5], pr, all = TRUE)[1] + 7 - fx <- grepRaw(codes[6], pr, all = TRUE)[3] + 7 - lx <- grepRaw(codes[7], pr, all = TRUE)[3] + 7 - npt0 <- grepRaw(codes[8], pr, all = TRUE)[2] + 3 - npt1 <- grepRaw(codes[8], pr, all = TRUE)[3] + 7 - mxy <- grepRaw(codes[9], pr, all = TRUE)[1] + 7 - mny <- grepRaw(codes[10], pr, all = TRUE)[3] + 7 - end <- grepRaw(codes[11], pr, all = TRUE) + 11 - tim <- grepRaw(codes[12], pr, all = TRUE) + 11 - - ## Calculate end and start of each block - offs <- sapply(5:10, function(x){end[x]}) - byts <- diff(offs) - ZFF <- readRaw(file, offset = z, nbytes = 4, human = "int", size = 2)[[5]][1] - RES <- readRaw(file, offset = re, nbytes = 4, human = "int", size = 2)[[5]][1] - snm.lab.material <- blockString(readRaw(file, offset = snm, nbytes = 22, - human = "char", size = 1, - endian = "little")) - - ## Get number of data points for each spectra data block - NPT0 <- readRaw(file, offset = npt0, nbytes = 12, human = "int", size = 4)[[5]][2] - NPT1 <- readRaw(file, offset = npt1, nbytes = 4, human = "int", size = 4)[[5]][1] - fxv <- readRaw(file, offset = fx, nbytes = 16, human = "real", size = 8)[[5]][1] ## fxv = frequency of first point - lxv <- readRaw(file, offset = lx, nbytes = 16, human = "real", size = 8)[[5]][1] ## lxv = frequency of last point - x <- rev(seq(lxv, fxv, (fxv - lxv) / (NPT1 - 1))) - - ## Read all through all the data blocks inside the OPUS file: - nbytes1 <- NPT0 * 4 ## initial parameters - smxa <- c() - smna <- c() - nbytes.f <- NPT1 * 4 - if(offs[1] < 2000) { - offs.f <- offs[3] - } - - if(offs[1] > 20000) { - offs.f <- offs[2] - } - - # Selected spectra block - opus.p <- readRaw(file, width = NULL, offset = offs.f - 4, - nbytes = nbytes.f, human = "real", size = 4, - endian = "little") - y <- opus.p[[5]] - - df <- data.frame(wavenumber = x, intensity = y) - - if (!is.null(share)) share_spec(df, file = file, share = share, id = id) - - return(df) -} - -#' @rdname read_spec -#' -#' @export -read_extdata <- function(file = NULL) { - if (is.null(file)) { - dir(system.file("extdata", package = "OpenSpecy")) - } - else { - system.file("extdata", file, package = "OpenSpecy", mustWork = TRUE) - } -} diff --git a/R/run_app.R b/R/run_app.R index 312d2b19..56874c8e 100644 --- a/R/run_app.R +++ b/R/run_app.R @@ -7,17 +7,17 @@ #' After running this function the Open Specy GUI should open in a separate #' window or in your computer browser. #' -#' @param app_dir the app to run; defaults to \code{"system"} pointing to -#' \code{system.file("shiny", package = "OpenSpecy")}. -#' @param path where to look for the local library files; defaults to -#' \code{"system"} pointing to -#' \code{system.file("extdata", package = "OpenSpecy")}. +#' @param path to store the downloaded app files; defaults to \code{"system"} +#' pointing to \code{system.file(package = "OpenSpecy")}. #' @param log logical; enables/disables logging to \code{\link[base]{tempdir}()} +#' @param ref git reference; could be a commit, tag, or branch name. Defaults to +#' "main". Only change this in case of errors. +#' @param test_mode logical; for internal testing only. #' @param \dots arguments passed to \code{\link[shiny]{runApp}()}. #' #' @return #' This function normally does not return any value, see -#' \code{\link[shiny]{runApp}()}. +#' \code{\link[shiny]{runGitHub}()}. #' #' @examples #' \dontrun{ @@ -28,34 +28,29 @@ #' Zacharias Steinmetz #' #' @seealso -#' \code{\link[shiny]{runApp}()} +#' \code{\link[shiny]{runGitHub}()} #' -#' @importFrom shiny runApp shinyOptions +#' @importFrom shiny runGitHub shinyOptions #' @importFrom utils installed.packages #' @export -run_app <- function(app_dir = "system", path = "system", log = TRUE, ...) { - if (is.null(app_dir) || app_dir == "") - stop("Could not find app directory. Try reinstalling OpenSpecy.", - call. = FALSE) +run_app <- function(path = "system", log = TRUE, ref = "main", + test_mode = FALSE, ...) { + pkg <- c("config", "qs", "shinyjs", "shinyWidgets", "bs4Dash", + "dplyr", "ggplot2", "DT", "curl", "aws.s3", "mongolite", "loggit") - pkg <- c("config", "shinyjs", "shinythemes", "shinyBS", "shinyWidgets", - "plotly", "data.table", "DT", "curl", "rdrop2", "mongolite", - "loggit") - mpkg <- pkg[!(pkg %in% installed.packages()[ , "Package"])] + miss <- pkg[!(pkg %in% installed.packages()[ , "Package"])] - if(length(mpkg)) stop("run_app() requires the following packages: ", - paste(paste0("'", mpkg, "'"), collapse = ", "), + if(length(miss)) stop("run_app() requires the following packages: ", + paste(paste0("'", miss, "'"), collapse = ", "), call. = F) - ad <- ifelse(app_dir == "system", - system.file("shiny", package = "OpenSpecy"), - app_dir) - wd <- ifelse(path == "system", - system.file("extdata", package = "OpenSpecy"), + dd <- ifelse(path == "system", + system.file(package = "OpenSpecy"), path) Sys.setenv(R_CONFIG_ACTIVE = "run_app") - shinyOptions(library_path = wd, log = log) - runApp(ad, ...) + shinyOptions(log = log) + if(!test_mode) + runGitHub("OpenSpecy-shiny", "wincowgerDEV", destdir = dd, ref = ref, ...) } diff --git a/R/share_spec.R b/R/share_spec.R index b0de01d8..82f7534c 100644 --- a/R/share_spec.R +++ b/R/share_spec.R @@ -4,79 +4,20 @@ #' This helper function shares spectral data and metadata with the Open Specy #' community. #' -#' \strong{Please note} that \code{share_spec()} only provides basic sharing +#' \strong{Please note:} that \code{share_spec()} only provides basic sharing #' functionality if used interactively. This means that files are only formatted -#' and saved for sharing but are not send automatically. This only works with +#' and saved for sharing but are not sent automatically. This only works with #' hosted instances of Open Specy. #' -#' @details -#' The \code{metadata} argument may contain a named vector with the following -#' details (\code{*} = mandatory): -#' -#' \tabular{ll}{ -#' \code{user_name*}: \tab User name, e.g. "Win Cowger"\cr -#' \code{contact_info}: \tab Contact information, e.g. "1-513-673-8956, -#' wincowger@@gmail.com"\cr -#' \code{organization}: \tab Affiliation, e.g. "University of California, -#' Riverside"\cr -#' \code{citation}: \tab Data citation, e.g. "Primpke, S., Wirth, M., Lorenz, -#' C., & Gerdts, G. (2018). Reference database design for the automated analysis -#' of microplastic samples based on Fourier transform infrared (FTIR) -#' spectroscopy. \emph{Analytical and Bioanalytical Chemistry}. -#' \doi{10.1007/s00216-018-1156-x}"\cr -#' \code{spectrum_type*}: \tab Raman or FTIR\cr -#' \code{spectrum_identity*}: \tab Material/polymer analyzed, e.g. -#' "Polystyrene"\cr -#' \code{material_form}: \tab Form of the material analyzed, e.g. textile fiber, -#' rubber band, sphere, granule \cr -#' \code{material_phase}: \tab Phase of the material analyzed (liquid, gas, -#' solid) \cr -#' \code{material_producer}: \tab Producer of the material analyzed, -#' e.g. Dow \cr -#' \code{material_purity}: \tab Purity of the material analyzed, e.g. 99.98\% -#' \cr -#' \code{material_quality}: \tab Quality of the material analyzed, e.g. -#' consumer product, manufacturer material, analytical standard, -#' environmental sample \cr -#' \code{material_color}: \tab Color of the material analyzed, -#' e.g. blue, #0000ff, (0, 0, 255) \cr -#' \code{material_other}: \tab Other material description, e.g. 5 µm diameter -#' fibers, 1 mm spherical particles \cr -#' \code{cas_number}: \tab CAS number, e.g. 9003-53-6 \cr -#' \code{instrument_used}: \tab Instrument used, e.g. Horiba LabRam \cr -#' \code{instrument_accessories}: \tab Instrument accessories, e.g. -#' Focal Plane Array, CCD\cr -#' \code{instrument_mode}: \tab Instrument modes/settings, e.g. -#' transmission, reflectance \cr -#' \code{spectral_resolution}: \tab Spectral resolution, e.g. 4/cm \cr -#' \code{laser_light_used}: \tab Wavelength of the laser/light used, e.g. -#' 785 nm \cr -#' \code{number_of_accumulations}: \tab Number of accumulations, e.g 5 \cr -#' \code{total_acquisition_time_s}: \tab Total acquisition time (s), e.g. 10 s -#' \cr -#' \code{data_processing_procedure}: \tab Data processing procedure, -#' e.g. spikefilter, baseline correction, none \cr -#' \code{level_of_confidence_in_identification}: \tab Level of confidence in -#' identification, e.g. 99\% \cr -#' \code{other_info}: \tab Other information \cr -#' \code{license}: \tab The license of the shared spectrum; defaults to -#' \code{"CC BY-NC"} (see -#' \url{https://creativecommons.org/licenses/by-nc/4.0/} for details). Any other -#' creative commons license is allowed, for example, CC0 or CC BY \cr -#' } -#' -#' @param data a data frame containing the spectral data; columns should be -#' named \code{"wavenumber"} and \code{"intensity"}. -#' @param metadata a named vector of the metadata to share; see details below. +#' @param x a list object of class \code{OpenSpecy}. #' @param file file to share (optional). #' @param share accepts any local directory to save the spectrum for later -#' sharing via e-mail to \email{wincowger@@gmail.com}; -#' \code{"system"} (default) uses the Open Specy package directory at -#' \code{system.file("extdata", package = "OpenSpecy")}; -#' if a correct API token exists, \code{"dropbox"} shares the spectrum with the -#' cloud. -#' @param id a unique user and/or session ID; defaults to -#' \code{paste(digest(Sys.info()), digest(sessionInfo()), sep = "/")}. +#' sharing via email to \email{wincowger@gmail.com}; \code{"system"} (default) +#' uses the Open Specy package directory at \code{system.file("extdata", +#' package = "OpenSpecy")}; if a correct API token exists, \code{"cloud"} +#' shares the spectrum with the cloud. +#' @param credentials a named list of credentials for cloud sharing; required if +#' \code{share = "cloud"}). #' @param \ldots further arguments passed to the submethods. #' #' @return @@ -86,11 +27,12 @@ #' \dontrun{ #' data("raman_hdpe") #' share_spec(raman_hdpe, -#' metadata = c(user_name = "Win Cowger", -#' spectrum_type = "FTIR", -#' spectrum_identity = "PE", -#' license = "CC BY-NC"), -#' share = tempdir()) +#' metadata = list( +#' user_name = "Win Cowger", +#' spectrum_type = "FTIR", +#' spectrum_identity = "PE", +#' license = "CC BY-NC" +#' )) #' } #' #' @author @@ -100,107 +42,85 @@ #' \code{\link{read_text}()}; #' \code{\link[digest]{digest}()}; \code{\link[utils]{sessionInfo}()} #' -#' @importFrom dplyr %>% #' @importFrom digest digest #' @importFrom utils write.csv sessionInfo #' #' @export -share_spec <- function(data, ...) { +share_spec <- function(x, ...) { UseMethod("share_spec") } #' @rdname share_spec #' #' @export -share_spec.default <- function(data, ...) { - stop("object needs to be of class 'data.frame'") +share_spec.default <- function(x, ...) { + stop("object 'x' needs to be of class 'OpenSpecy'", call. = F) } #' @rdname share_spec #' #' @export -share_spec.data.frame <- function(data, - metadata = c(user_name = "", - contact_info = "", - organization = "", - citation = "", - spectrum_type = "", - spectrum_identity = "", - material_form = "", - material_phase = "", - material_producer = "", - material_purity = "", - material_quality = "", - material_color = "", - material_other = "", - cas_number = "", - instrument_used = "", - instrument_accessories = "", - instrument_mode = "", - spectral_resolution = "", - laser_light_used = "", - number_of_accumulations = "", - total_acquisition_time_s = "", - data_processing_procedure = "", - level_of_confidence_in_identification = "", - other_info = "", - license = "CC BY-NC"), - file = NULL, - share = "system", - id = paste(digest(Sys.info()), - digest(sessionInfo()), sep = "/"), - ...) { - if (is.null(names(metadata))) stop("'metadata' needs to be a named vector") - if (any(is.na(metadata[c("user_name", "spectrum_type", "spectrum_identity")])) | - metadata["user_name"] == "" | metadata["spectrum_type"] == "" | - metadata["spectrum_identity"] == "") { - mex <- FALSE - warning("fields 'user_name', 'spectrum_type', and 'spectrum_identity' ", - "must not be empty if you like to share your metadata", call. = F) - } else { - mex <- TRUE - } +share_spec.OpenSpecy <- function(x, file = NULL, share = "system", + credentials = NULL, ...) { + md <- x$metadata + if(any(!c("user_name", "spectrum_type", "spectrum_identity") %in% + names(md)) | + is.null(md$user_name) | is.null(md$spectrum_type) | + is.null(md$spectrum_identity)) + warning("Fields 'user_name', 'spectrum_type', and 'spectrum_identity' ", + "should not be empty if you like to share your metadata", call. = F) - fid <- digest(data) - mdata <- data.frame(variable = names(metadata), input = metadata, - row.names = NULL) + if(is.null(md$session_id)) + stop("'session_id' must not be empty if you like to share your metadata;", + "run 'as_OpenSpecy(x, session_id = T)' to add one", call. = F) if (share == "system") { fp <- file.path(system.file("extdata", package = "OpenSpecy"), - "user_spectra", id) - } else if (share == "dropbox") { - - pkg <- "rdrop2" + "user_spectra", md$session_id) |> unique() + } else if (share == "cloud") { + pkg <- "aws.s3" mpkg <- pkg[!(pkg %in% installed.packages()[ , "Package"])] - if (length(mpkg)) stop("share = 'dropbox' requires package 'rdrop2'") + if(length(mpkg)) stop("share = 'cloud' requires package 'aws.s3'", + call. = F) + + if(is.null(credentials)) + stop("'credentials' required to share with the cloud", call. = F) + + if(!is.list(credentials) || !(all(c("s3_key", "s3_secret", "s3_region", + "s3_bucket") %in% names(credentials)))) + stop("'credentials' needs to be a named list containing the following ", + "items: 's3_key', 's3_secret', 's3_region', 's3_bucket'", call. = F) - fp <- file.path(tempdir(), id) + fp <- file.path(tempdir(), md$session_id) |> unique() } else { - fp <- file.path(share, id) + fp <- file.path(share, md$session_id) |> unique() } dir.create(fp, recursive = T, showWarnings = F) - fd <- file.path(fp, paste0(fid, ".csv")) - fm <- file.path(fp, paste0(fid, "_form", ".csv")) + fd <- file.path(fp, paste0(md$file_id, ".yml")) |> unique() - write.csv(data, fd, row.names = FALSE, quote = TRUE) + write_spec(x, fd) - if (mex) write.csv(mdata, fm, row.names = FALSE, quote = TRUE) if (!is.null(file)) { ex <- strsplit(basename(file), split="\\.")[[1]] - file.copy(file, file.path(fp, paste0(fid, ".", ex[-1]))) + file.copy(file, file.path(fp, paste0(md$file_id, ".", ex[-1]))) } - if (share == "dropbox") { - for (lf in list.files(fp, pattern = fid, full.names = T)) { - rdrop2::drop_upload(lf, path = paste0("data/users/", id), ...) + if (share == "cloud") { + for (lf in list.files(fp, pattern = md$file_id, full.names = T)) { + aws.s3::put_object( + file = lf, + bucket = credentials$s3_bucket, + key = credentials$s3_key, secret = credentials$s3_secret, + region = credentials$s3_region + ) } } - message("thank you for your willigness to share your data; ", + message("Thank you for your willigness to share your data; ", "your data has been saved to\n ", fp, "\n", - "if you run Open Specy locally, you may consider e-mailing your ", + "If you run Open Specy locally, you may consider e-mailing your ", "files to\n ", "Win Cowger ") } diff --git a/R/sig_noise.R b/R/sig_noise.R new file mode 100644 index 00000000..aae90245 --- /dev/null +++ b/R/sig_noise.R @@ -0,0 +1,77 @@ +#' @rdname sig_noise +#' @title Calculate signal and noise metrics for OpenSpecy objects +#' +#' @description +#' This function calculates common signal and noise metrics for \code{OpenSpecy} +#' objects. +#' +#' @param x an \code{OpenSpecy} object. +#' @param metric character; specifying the desired metric to calculate. +#' Options include \code{"sig"} (mean intensity), \code{"noise"} (standard +#' deviation of intensity), \code{"sig_times_noise"} (absolute value of +#' signal times noise), \code{"sig_over_noise"} (absolute value of signal / +#' noise), or \code{"tot_sig"} (total signal = signal * number of data +#' points). +#' @param na.rm logical; indicating whether missing values should be removed +#' when calculating signal and noise. Default is \code{TRUE}. +#' @param \ldots further arguments passed to subfunctions; currently not used. +#' +#' @return +#' A numeric vector containing the calculated metric for each spectrum in the +#' \code{OpenSpecy} object. +#' +#' @examples +#' data("raman_hdpe") +#' +#' sig_noise(raman_hdpe, metric = "sig") +#' sig_noise(raman_hdpe, metric = "noise") +#' sig_noise(raman_hdpe, metric = "sig_times_noise") +#' +#' @importFrom stats median +#' @importFrom data.table frollapply +#' +#' @export +sig_noise <- function(x, ...) { + UseMethod("sig_noise") +} + +#' @rdname sig_noise +#' +#' @export +sig_noise.default <- function(x, ...) { + stop("object 'x' needs to be of class 'OpenSpecy'", call. = F) +} + +#' @rdname sig_noise +#' +#' @export +sig_noise.OpenSpecy <- function(x, metric = "sig_over_noise", + na.rm = TRUE, ...) { + vapply(x$spectra, function(y) { + if(length(y[!is.na(y)]) < 20) { + warning("Need at least 20 intensity values to calculate the signal or ", + "noise values accurately; returning NA", call. = F) + return(NA) + } + + if(metric == "run_sig_over_noise") { + max <- frollapply(y[!is.na(y)], 20, max) + max[(length(max) - 19):length(max)] <- NA + signal <- max(max, na.rm = T)#/mean(x, na.rm = T) + noise <- median(max[max != 0], na.rm = T) + } + else { + signal = mean(y, na.rm = na.rm) + noise = sd(y, na.rm = na.rm) + } + + if(metric == "sig") return(signal) + if(metric == "noise") return(noise) + if(metric == "sig_times_noise") return(abs(signal * noise)) + + if(metric %in% c("sig_over_noise", "run_sig_over_noise")) + return(abs(signal/noise)) + if(metric == "tot_sig") return(sum(y)) + if(metric == "log_tot_sig") return(sum(exp(y))) + }, FUN.VALUE = numeric(1)) +} diff --git a/R/smooth_intens.R b/R/smooth_intens.R index f4db3b04..c52326c3 100644 --- a/R/smooth_intens.R +++ b/R/smooth_intens.R @@ -1,3 +1,4 @@ +#' @rdname smooth_intens #' @title Smooth spectral intensities #' #' @description @@ -11,22 +12,20 @@ #' A typical good smooth can be achieved with 11 data point window and a 3rd or #' 4th order polynomial. #' -#' @param x a numeric vector containing the spectral wavenumbers; alternatively -#' a data frame containing spectral data as \code{"wavenumber"} and -#' \code{"intensity"} can be supplied. -#' @param y a numeric vector containing the spectral intensities. -#' @param formula an object of class '\code{\link[stats]{formula}}' of the form -#' \code{intensity ~ wavenumber}. -#' @param data a data frame containing the variables in \code{formula}. -#' @param p polynomial order for the filter -#' @param n number of data points in the window, filter length (must be odd). +#' @param x an object of class \code{OpenSpecy}. +#' @param polynomial polynomial order for the filter +#' @param window number of data points in the window, filter length (must be +#' odd). +#' @param derivative the derivative order if you want to calculate the +#' derivative. Zero (default) is no derivative. +#' @param abs logical; whether you want to calculate the absolute value of the +#' resulting output. #' @param make_rel logical; if \code{TRUE} spectra are automatically normalized #' with \code{\link{make_rel}()}. #' @param \ldots further arguments passed to \code{\link[signal]{sgolay}()}. #' #' @return -#' \code{smooth_intens()} returns a data frame containing two columns named -#' \code{"wavenumber"} and \code{"intensity"}. +#' \code{smooth_intens()} returns an \code{OpenSpecy} object. #' #' @examples #' data("raman_hdpe") @@ -44,7 +43,7 @@ #' Simplified Least Squares Procedures.” \emph{Analytical Chemistry}, #' \strong{36}(8), 1627--1639. #' -#' @importFrom dplyr %>% +#' @importFrom data.table .SD #' @export smooth_intens <- function(x, ...) { UseMethod("smooth_intens") @@ -53,36 +52,28 @@ smooth_intens <- function(x, ...) { #' @rdname smooth_intens #' #' @export -smooth_intens.formula <- function(formula, data = NULL, ...) { - if (missing(formula) || (length(formula) != 3L) || - (length(attr(terms(formula[-2L]), "term.labels")) != 1L)) - stop("'formula' missing or incorrect") - - mf <- model.frame(formula, data) - lst <- as.list(mf) - names(lst) <- c("y", "x") - - do.call("smooth_intens", c(lst, list(...))) +smooth_intens.default <- function(x, ...) { + stop("object 'x' needs to be of class 'OpenSpecy'") } #' @rdname smooth_intens #' #' @export -smooth_intens.data.frame <- function(x, ...) { - if (!all(c("wavenumber", "intensity") %in% names(x))) - stop("'data' must contain 2 columns named 'wavenumber' and 'intensity'") +smooth_intens.OpenSpecy <- function(x, polynomial = 3, window = 11, + derivative = 0, abs = FALSE, + make_rel = TRUE, ...) { + filt <- x$spectra[, lapply(.SD, .sgfilt, p = polynomial, n = window, + m = derivative, abs = abs, ...)] + + if(make_rel) x$spectra <- filt[, lapply(.SD, make_rel)] else x$spectra <- filt - do.call("smooth_intens", list(x$wavenumber, x$intensity, ...)) + return(x) } -#' @rdname smooth_intens #' @importFrom signal filter sgolay -#' @export -smooth_intens.default <- function(x, y, p = 3, n = 11, make_rel = TRUE, - ...) { - yflt <- signal::filter(filt = sgolay(p = p, n = n, ...), x = y) - - if (make_rel) yout <- make_rel(yflt) else yout <- yflt +.sgfilt <- function(y, p, n, m, abs = F, ...) { + out <- signal::filter(filt = sgolay(p = p, n = n, m = m, ...), x = y) + if(abs) out <- abs(out) - data.frame(wavenumber = x, intensity = yout) + return(out) } diff --git a/R/spec_res.R b/R/spec_res.R index 74fe6642..44575609 100644 --- a/R/spec_res.R +++ b/R/spec_res.R @@ -1,3 +1,4 @@ +#' @rdname spec_res #' @title Spectral resolution #' #' @description @@ -9,22 +10,37 @@ #' frequency difference between two lines in a spectrum that can still be #' distinguished. #' -#' @param x a numeric vector or an \R object which is coercible to one by -#' \code{as.vector(x, "numeric")}; \code{x} should be \code{wavenumber} data. +#' @param x a numeric vector with \code{wavenumber} data or an \code{OpenSpecy} +#' object. +#' @param \ldots further arguments passed to subfunctions; currently not used. #' #' @return #' \code{spec_res()} returns a single numeric value. #' #' @examples #' data("raman_hdpe") -#' spec_res(raman_hdpe$wavenumber) +#' +#' spec_res(raman_hdpe) #' #' @author #' Win Cowger, Zacharias Steinmetz #' -#' @importFrom dplyr %>% #' @export -spec_res <- function(x) { - (max(x) - min(x)) / length(x) +spec_res <- function(x, ...) { + UseMethod("spec_res") +} + +#' @rdname spec_res +#' +#' @export +spec_res.default <- function(x, ...) { + freq <- diff(x) |> table() + as.numeric(names(freq)[which.max(freq)]) } +#' @rdname spec_res +#' +#' @export +spec_res.OpenSpecy <- function(x, ...) { + do.call("spec_res", list(x = x$wavenumber)) +} diff --git a/R/subtr_bg.R b/R/subtr_baseline.R similarity index 58% rename from R/subtr_bg.R rename to R/subtr_baseline.R index 8551ce9c..613a2bbe 100644 --- a/R/subtr_bg.R +++ b/R/subtr_baseline.R @@ -1,36 +1,36 @@ +#' @rdname subtr_baseline #' @title Automated background subtraction for spectral data #' #' @description #' This baseline correction routine iteratively finds the baseline of a spectrum -#' using a polynomial fitting. +#' using a polynomial fitting or accepts a manual baseline. #' #' @details #' This is a translation of Michael Stephen Chen's MATLAB code written for the #' \code{imodpolyfit} routine from Zhao et al. 2007. #' -#' @param x a numeric vector containing the spectral wavenumbers; alternatively -#' a data frame containing spectral data as \code{"wavenumber"} and -#' \code{"intensity"} can be supplied. -#' @param y a numeric vector containing the spectral intensities. -#' @param formula an object of class '\code{\link[stats]{formula}}' of the form -#' \code{intensity ~ wavenumber}. -#' @param data a data frame containing the variables in \code{formula}. +#' @param x a list object of class \code{OpenSpecy}. +#' @param type one of \code{"polynomial"} or \code{"manual"} depending on +#' whether you want spectra to be corrected with a manual baseline or with +#' polynomial baseline fitting. #' @param degree the degree of the polynomial. Must be less than the number of #' unique points when raw is \code{FALSE}. Typically a good fit can be #' found with a 8th order polynomial. #' @param raw if \code{TRUE}, use raw and not orthogonal polynomials. +#' @param baseline an \code{OpenSpecy} object containing the baseline data to be +#' subtracted. #' @param make_rel logical; if \code{TRUE} spectra are automatically normalized #' with \code{\link{make_rel}()}. #' @param \ldots further arguments passed to \code{\link[stats]{poly}()}. #' #' @return -#' \code{subtr_bg()} returns a data frame containing two columns named +#' \code{subtr_baseline()} returns a data frame containing two columns named #' \code{"wavenumber"} and \code{"intensity"}. #' #' @examples #' data("raman_hdpe") #' -#' subtr_bg(raman_hdpe) +#' subtr_baseline(raman_hdpe) #' #' @author #' Win Cowger, Zacharias Steinmetz @@ -49,44 +49,44 @@ #' \emph{Applied Spectroscopy}, \strong{61}(11), 1225–1232. #' \doi{10.1366/000370207782597003}. #' -#' @importFrom dplyr %>% #' @importFrom stats terms model.frame sd lm poly approx +#' @importFrom data.table .SD #' @export -subtr_bg <- function(x, ...) { - UseMethod("subtr_bg") +subtr_baseline <- function(x, ...) { + UseMethod("subtr_baseline") } -#' @rdname subtr_bg +#' @rdname subtr_baseline #' #' @export -subtr_bg.formula <- function(formula, data = NULL, ...) { - if (missing(formula) || (length(formula) != 3L) || - (length(attr(terms(formula[-2L]), "term.labels")) != 1L)) - stop("'formula' missing or incorrect") - - mf <- model.frame(formula, data) - lst <- as.list(mf) - names(lst) <- c("y", "x") - - do.call("subtr_bg", c(lst, list(...))) +subtr_baseline.default <- function(x, ...) { + stop("object 'x' needs to be of class 'OpenSpecy'", call. = F) } -#' @rdname subtr_bg +#' @rdname subtr_baseline #' #' @export -subtr_bg.data.frame <- function(x, ...) { - if (!all(c("wavenumber", "intensity") %in% names(x))) - stop("'data' must contain 2 columns named 'wavenumber' and 'intensity'") - - do.call("subtr_bg", list(x$wavenumber, x$intensity, ...)) +subtr_baseline.OpenSpecy <- function(x, type = "polynomial", + degree = 8, raw = FALSE, + baseline, make_rel = TRUE, ...) { + if(type == "polynomial") { + sbg <- x$spectra[, lapply(.SD, .subtr_bl_poly, x = x$wavenumber, + degree = degree, raw = raw, ...)] + } else if(type == "manual") { + if(!is_OpenSpecy(baseline)) + stop("'baseline' needs to be of class 'OpenSpecy'", call. = F) + sbg <- x$spectra[, lapply(.SD, .subtr_bl_manual, x = x$wavenumber, + bl_y = baseline$spectra[[1]], + bl_x = baseline$wavenumber)] + } else stop("'type' must be either 'polynomial' or 'manual'", call. = F) + + if (make_rel) x$spectra <- sbg[, lapply(.SD, make_rel)] else x$spectra <- sbg + + return(x) } -#' @rdname subtr_bg -#' -#' @export -subtr_bg.default <- function(x, y, degree = 8, raw = FALSE, - make_rel = TRUE, ...) { - xin <- x +.subtr_bl_poly <- function(y, x, degree, raw, ...) { + xout <- x yin <- y dev_prev <- 0 # standard deviation residuals for the last iteration of polyfit; # set initially to 0 @@ -132,18 +132,19 @@ subtr_bg.default <- function(x, y, degree = 8, raw = FALSE, # Approximate the intensity back to the original wavelengths, allows below # the peak to be interpolated if(criteria_met) { - ysbg <- approx(x, y, xout = xin, rule = 2, method = "linear", - ties = mean)[2] %>% - unlist() %>% + ysbg <- approx(x, y, xout = xout, rule = 2, method = "linear", + ties = mean)[2] |> + unlist() |> unname() - if (make_rel) yout <- make_rel(yin - ysbg) else - yout <- yin - ysbg - - return(data.frame(wavenumber = xin, intensity = yout)) + return(yin - ysbg) } # Update previous residual metric dev_prev <- dev_curr } } + +.subtr_bl_manual <- function(y, x, bl_y, bl_x, ...) { + y - approx(bl_x, bl_y, xout = x, rule = 2, method = "linear", ties = mean)$y +} diff --git a/R/test_lib.R b/R/test_lib.R index ecbae00c..79c1d2f9 100644 --- a/R/test_lib.R +++ b/R/test_lib.R @@ -1,22 +1,17 @@ #' @title Test reference library #' #' @description -#' Reference library of 34 Raman spectra used for internal testing. +#' Reference library with 29 FTIR and 28 Raman spectra used for examples and +#' internal testing. #' #' @format -#' A list named \code{"test"} with two elements: -#' -#' \tabular{ll}{ -#' \code{metadata}: \tab metadata of 34 Raman spectra \cr -#' \code{library}: \tab all reference spectra, \code{sample_name} serves as -#' identifier \cr -#' } +#' An \code{OpenSpecy} object; \code{sample_name} is the class of the spectra. #' #' @examples #' data("test_lib") #' #' @author -#' Jennifer Lynch +#' Win Cowger #' #' @docType data #' @keywords data diff --git a/README.md b/README.md index ae10cf15..aba59499 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,30 @@ -# Open Specy +# Open Specy 1.0 Analyze, Process, Identify, and Share Raman and (FT)IR Spectra [![CRAN version](https://www.r-pkg.org/badges/version/OpenSpecy)](https://CRAN.R-project.org/package=OpenSpecy) [![Project Status](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) -[![R-CMD-check](https://github.com/wincowgerDEV/OpenSpecy-package/workflows/R-CMD-check/badge.svg)](https://github.com/wincowgerDEV/OpenSpecy-package/actions) +[![R-CMD-check](https://github.com/wincowgerDEV/OpenSpecy-package/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/wincowgerDEV/OpenSpecy-package/actions/workflows/R-CMD-check.yaml) +[![Codecov test coverage](https://codecov.io/gh/wincowgerDEV/OpenSpecy-package/branch/main/graph/badge.svg)](https://app.codecov.io/gh/wincowgerDEV/OpenSpecy-package?branch=main) [![License: CC BY 4.0](https://img.shields.io/badge/license-CC%20BY%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by/4.0/) [![DOI](https://img.shields.io/badge/DOI-10.1021/acs.analchem.1c00123-blue.svg)](https://doi.org/10.1021/acs.analchem.1c00123) [![Website](https://img.shields.io/badge/web-openspecy.org-white)](https://wincowger.shinyapps.io/OpenSpecy/) -[![Twitter Follow](https://img.shields.io/twitter/follow/OpenSpecy)](https://twitter.com/OpenSpecy) -[![Gitter](https://badges.gitter.im/Open-Specy/community.svg)](https://gitter.im/Open-Specy/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Gitter](https://badges.gitter.im/Open-Specy/community.svg)](https://app.gitter.im/#/room/#Open-Specy_community:gitter.im) -Raman and (FT)IR spectral analysis tool for plastic particles and -other environmental samples (Cowger et al. 2021, doi: +Raman and (FT)IR spectral analysis tool for plastic particles and other +environmental samples (Cowger et al. 2021, doi: [10.1021/acs.analchem.1c00123](https://doi.org/10.1021/acs.analchem.1c00123)). -Supported features include reading spectral data files (.asp, .csv, .jdx, .spc, -.spa, .0), Savitzky-Golay smoothing of spectral intensities with -`smooth_intens()`, correcting background noise with `subtr_bg()` in accordance -with Zhao et al. (2007, doi: -[10.1366/000370207782597003](https://doi.org/10.1366/000370207782597003)), and -identifying spectra using an onboard reference library (Cowger et al. 2020, doi: [10.1177/0003702820929064](https://doi.org/10.1177/0003702820929064)). -Analyzed spectra can be shared with the Open Specy community. A Shiny app is -available via `run_app()` or online at -[https://openanalysis.org/openspecy/](https://openanalysis.org/openspecy/). +With `read_any()`, Open Specy provides a single function for reading individual, +batch, or map spectral data files like .asp, .csv, .jdx, .spc, .spa, .0, and +.zip. `process_spec()` simplifies preprocessing spectra, including smoothing, +baseline correction, range restriction and flattening, intensity conversions, +wavenumber alignment, and min-max normalization. +Spectra can be identified in batch using an onboard reference library +(Cowger et al. 2020, doi: [10.1177/0003702820929064](https://doi.org/10.1177/0003702820929064)) +using `match_spec()`. A Shiny app is available via `run_app()` +or online at [https://openanalysis.org/openspecy/](https://openanalysis.org/openspecy/). ## Installation @@ -46,7 +46,7 @@ into your R console (requires **devtools**): ```r if (!require(devtools)) install.packages("devtools") -devtools::install_github("wincowgerDEV/OpenSpecy") +devtools::install_github("wincowgerDEV/OpenSpecy-package") ``` ## Getting started @@ -55,45 +55,42 @@ library(OpenSpecy) run_app() ``` -See -[package vignette](https://htmlpreview.github.io/?https://github.com/wincowgerDEV/OpenSpecy-package/blob/main/vignettes/sop.html) -for a detailed standard operating procedure. +## See [package vignette](http://wincowger.com/OpenSpecy-package/articles/sop.html) for a detailed standard operating procedure. -## Workflow +## Simple workflow for single spectral identification ```r -library(dplyr) - # Fetch current spectral library from https://osf.io/x7dpz/ -get_lib() +get_lib("derivative") # Load library into global environment -spec_lib <- load_lib() +spec_lib <- load_lib("derivative") # Read sample spectrum -raman_hdpe <- read_text(read_extdata("raman_hdpe.csv")) +raman_hdpe <- read_extdata("raman_hdpe.csv") |> + read_any() + +# Look at the spectrum +plotly_spec(raman_hdpe) -# Share your spectrum with the Open Spey community -share_spec(raman_hdpe, - metadata = c(user_name = "Win Cowger", - contact_info = "wincowger@gmail.com", - spectrum_type = "Raman", - spectrum_identity = "HDPE") - ) +# Process the spectra and conform it to the library format +raman_proc <- raman_hdpe |> + process_spec(conform_spec_args = list(range = spec_lib$wavenumbers), + smooth_intens = T, make_rel = T) -# Adjust spectral intensity -raman_adj <- raman_hdpe %>% - adj_intens() +# Compare raw and processed spectra +plotly_spec(raman_hdpe, raman_proc) -# Smooth and background-correct spectrum -raman_proc <- raman_adj %>% - smooth_intens() %>% - subtr_bg() +top_matches <- match_spec(raman_proc, library = spec_lib, na.rm = T, top_n = 5, + add_library_metadata = "sample_name", + add_object_metadata = "col_id") -# Match spectrum with library and retrieve meta data -match_spec(raman_proc, library = spec_lib, which = "raman") +#Print the top 5 results with relevant metadata +top_matches[, c("object_id", "library_id", "match_val", "SpectrumType", + "SpectrumIdentity")] -find_spec(sample_name == 5381, library = spec_lib, which = "raman") +#Get all metadata for the matches +get_metadata(spec_lib, logic = top_matches$library_id) ``` ## Citations @@ -104,6 +101,6 @@ Needs an Open Source Community: Open Specy to the Rescue!” *Analytical Chemistry*, **93**(21), 7543–7548. doi: [10.1021/acs.analchem.1c00123](https://doi.org/10.1021/acs.analchem.1c00123). -Cowger W, Steinmetz Z (2021). “OpenSpecy: Analyze, Process, -Identify, and Share Raman and (FT)IR Spectra.” *R package*, **0.9.5**. +Cowger W, Steinmetz Z (2023). “OpenSpecy: Analyze, Process, +Identify, and Share Raman and (FT)IR Spectra.” *R package*, **1.0.0**. [https://github.com/wincowgerDEV/OpenSpecy-package](https://github.com/wincowgerDEV/OpenSpecy-package). diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..04c55859 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,14 @@ +comment: false + +coverage: + status: + project: + default: + target: auto + threshold: 1% + informational: true + patch: + default: + target: auto + threshold: 1% + informational: true diff --git a/cran-comments.md b/cran-comments.md index da0b5c40..72c6bdb7 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -1,6 +1,6 @@ ## Test environments -* manjaro linux 5.18.6-1 (local), R-4.2.1 +* manjaro linux 6.3.13-2 (local), R-4.3.1 * macOS latest (via GitHub Actions), R-release * ubuntu latest (via GitHub Actions), R-devel * ubuntu latest (via GitHub Actions), R-release @@ -10,12 +10,22 @@ ## R CMD check results -0 errors | 0 warnings | 1 note +0 errors | 0 warnings | 2 notes ## Comments -DOI links do not pass remote checks although they resolve fine using a web -browser + Possibly misspelled words in DESCRIPTION: + preprocessing (46:5) + wavenumber (47:62) -For further details, see NEWS file +Both words are spelled correctly. + + installed size is 6.5Mb + sub-directories of 1Mb or more: + doc 4.9Mb + +We tried to reduce the vignette size as much as possible but would like to keep +the current number of screenshots for better comprehensibility. + +For further details, see NEWS.md diff --git a/data/raman_hdpe.RData b/data/raman_hdpe.RData index aa19bb8e..75d53abd 100644 Binary files a/data/raman_hdpe.RData and b/data/raman_hdpe.RData differ diff --git a/data/test_lib.RData b/data/test_lib.RData index 566e9148..9cd423ec 100644 Binary files a/data/test_lib.RData and b/data/test_lib.RData differ diff --git a/inst/extdata/CA_tiny_map.zip b/inst/extdata/CA_tiny_map.zip new file mode 100644 index 00000000..508be0f5 Binary files /dev/null and b/inst/extdata/CA_tiny_map.zip differ diff --git a/inst/extdata/ftir_ps_interior.1 b/inst/extdata/ftir_ps_interior.1 deleted file mode 100644 index 8c43cb5a..00000000 Binary files a/inst/extdata/ftir_ps_interior.1 and /dev/null differ diff --git a/inst/extdata/raman_hdpe.csv b/inst/extdata/raman_hdpe.csv index 6ab5960c..0ed9137f 100644 --- a/inst/extdata/raman_hdpe.csv +++ b/inst/extdata/raman_hdpe.csv @@ -1,1096 +1,965 @@ -Wavelength,Absorbance -150.92,3264.21 -154.057,3347.43 -157.193,3358.72 -160.329,3346.65 -163.463,3418.66 -166.596,3411.73 -169.728,3331.23 -172.859,3408.39 -175.988,3375.29 -179.115,3427.94 -182.243,3365.69 -185.37,3404.79 -188.493,3388.36 -191.618,3431.65 -194.742,3401.5 -197.862,3447.33 -200.983,3430.75 -204.102,3374.73 -207.221,3411.18 -210.337,3464.12 -213.455,3463.18 -216.569,3449.68 -219.684,3404.23 -222.796,3474.66 -225.907,3476.48 -229.019,3498.27 -232.128,3477.13 -235.236,3467.18 -238.343,3452.98 -241.451,3370.01 -244.556,3399.67 -247.659,3431.37 -250.762,3342.03 -253.864,3352.13 -256.964,3371.29 -260.064,3397.34 -263.162,3354.89 -266.26,3316.97 -269.356,3360.31 -272.452,3457.76 -275.544,3379.16 -278.638,3413.9 -281.73,3419.73 -284.821,3452.37 -287.909,3420.14 -290.999,3382.45 -294.087,3470.43 -297.172,3473.75 -300.258,3471.46 -303.341,3393.9 -306.425,3478.48 -309.506,3455.51 -312.588,3416.15 -315.667,3493.94 -318.745,3419.68 -321.824,3489.42 -324.9,3547.65 -327.975,3556.18 -331.051,3560.12 -334.123,3620.58 -337.195,3591.92 -340.266,3667.08 -343.336,3651.43 -346.404,3703.35 -349.472,3766.32 -352.539,3771.1 -355.604,3821.1 -358.669,3862.19 -361.732,3842.41 -364.795,3783.53 -367.857,3838.93 -370.917,3743.65 -373.975,3787.94 -377.033,3716.15 -380.091,3656.18 -383.145,3740.77 -386.2,3717.66 -389.255,3713.7 -392.306,3734.33 -395.359,3785.11 -398.408,3799.8 -401.458,3728.63 -404.506,3726.81 -407.552,3780.11 -410.599,3765.81 -413.644,3814.53 -416.687,3757.41 -419.729,3848.85 -422.773,3773.93 -425.813,3787.15 -428.852,3770.89 -431.89,3785.42 -434.928,3814.7 -437.964,3778.64 -440.999,3795.95 -444.033,3777.13 -447.067,3730.75 -450.099,3752.22 -453.13,3706.41 -456.158,3740.69 -459.188,3703.46 -462.216,3751.39 -465.243,3765.53 -468.267,3778.31 -471.292,3728 -474.315,3704.36 -477.338,3883.48 -480.358,4154.71 -483.379,4201.9 -486.398,4150.49 -489.417,4157.42 -492.433,3752.87 -495.448,3644.24 -498.464,3665.89 -501.478,3748.05 -504.49,3673.19 -507.501,3653.01 -510.511,3703.71 -513.521,3708.62 -516.529,3662.01 -519.536,3621.28 -522.543,3700.87 -525.548,3675.56 -528.552,3675.23 -531.555,3671.57 -534.558,3694.77 -537.559,3687.15 -540.557,3686.72 -543.557,3726.62 -546.555,3680.37 -549.551,3652.22 -552.547,3648.42 -555.541,3703.67 -558.535,3682.93 -561.526,3658.7 -564.519,3676.42 -567.508,3683.79 -570.499,3639.15 -573.486,3695.45 -576.473,3677.06 -579.459,3729.83 -582.443,3687.3 -585.429,3701.42 -588.411,3743.52 -591.393,3694.01 -594.374,3739.13 -597.353,3717.36 -600.332,3707.84 -603.31,3725.23 -606.285,3714.25 -609.26,3749.64 -612.235,3743.62 -615.209,3748.32 -618.18,3747.03 -621.152,3732.1 -624.123,3779.62 -627.091,3665.55 -630.06,3728.71 -633.026,3730.89 -635.993,3710.17 -638.957,3756.22 -641.922,3731 -644.884,3777 -647.846,3730.36 -650.806,3754.2 -653.767,3716.96 -656.725,3818.72 -659.683,3729.31 -662.639,3736.25 -665.594,3719.59 -668.549,3774.61 -671.502,3799.41 -674.455,3754.32 -677.406,3777.95 -680.355,3819.05 -683.304,3774.15 -686.253,3810.9 -689.201,3774.13 -692.146,3768.63 -695.091,3840.29 -698.034,3740.78 -700.978,3792.04 -703.919,3834.22 -706.861,3815.08 -709.8,3855.1 -712.738,3827.77 -715.677,3810.33 -718.613,3917.19 -721.548,3894.65 -724.482,3892.88 -727.416,3885.84 -730.35,3879.56 -733.281,3969.3 -736.212,3930.29 -739.141,3891.74 -742.068,3930.67 -744.995,3877.25 -747.922,3905.31 -750.848,3868.73 -753.772,3922.41 -756.694,3876.08 -759.617,3888.85 -762.537,3957.1 -765.458,3940.52 -768.376,3956.42 -771.295,3860.81 -774.211,3897.8 -777.128,3880.74 -780.042,3933.17 -782.956,3937.51 -785.868,3904.75 -788.781,3881.88 -791.692,3940.04 -794.601,3972.38 -797.51,3970.29 -800.418,3950.78 -803.324,4018.78 -806.23,3974.24 -809.135,4078.56 -812.037,4018.32 -814.94,3984.75 -817.842,3984.04 -820.741,4015.98 -823.641,3978.77 -826.54,4082.99 -829.436,4034.74 -832.334,4065.47 -835.228,4060.62 -838.122,4039.33 -841.016,4072.56 -843.908,4089.46 -846.799,4076.23 -849.69,4110.62 -852.579,4111.96 -855.467,4117.38 -858.354,4122.42 -861.24,4168.75 -864.125,4148.81 -867.009,4109.69 -869.893,4177.9 -872.773,4168.61 -875.654,4170.62 -878.535,4197.37 -881.415,4244.5 -884.291,4158.38 -887.169,4176.83 -890.044,4129.8 -892.92,4175.8 -895.793,4156.5 -898.667,4198.97 -901.538,4133.88 -904.408,4096.42 -907.277,4149.62 -910.148,4180.57 -913.015,4086.53 -915.882,4152.44 -918.747,4161.15 -921.612,4092.85 -924.476,4134.67 -927.339,4078.66 -930.199,4124.66 -933.06,4065.75 -935.92,4135.17 -938.779,4152.46 -941.635,4018.72 -944.493,4082.39 -947.347,4022.02 -950.203,4027.42 -953.055,4066.72 -955.909,4064.64 -958.76,3997.29 -961.61,4045.38 -964.461,4083.1 -967.309,4055.64 -970.156,4058.14 -973.003,4073.97 -975.848,4075.94 -978.692,4041.83 -981.536,4058.39 -984.379,4100.25 -987.22,4128.6 -990.059,4124.57 -992.899,4100.75 -995.738,4094.7 -998.575,4167.49 -1001.41,4169.06 -1004.25,4083.14 -1007.08,4203.78 -1009.91,4195.68 -1012.75,4195.06 -1015.58,4181.69 -1018.41,4225.37 -1021.24,4178.74 -1024.07,4131.96 -1026.89,4226.56 -1029.72,4244.76 -1032.55,4280.16 -1035.37,4250.08 -1038.19,4270.58 -1041.02,4352.7 -1043.84,4390.87 -1046.66,4387.11 -1049.48,4521.85 -1052.3,4620.29 -1055.11,4988.05 -1057.93,5787.92 -1060.75,7498.33 -1063.56,8814.76 -1066.37,8896.85 -1069.19,8390.56 -1072,7090.36 -1074.81,5719.77 -1077.62,5141.34 -1080.42,5074.66 -1083.23,5041.24 -1086.04,5000.52 -1088.84,4969.94 -1091.65,4895.7 -1094.45,4785.52 -1097.25,4750.63 -1100.06,4734.25 -1102.86,4582.69 -1105.66,4641.5 -1108.45,4570.88 -1111.25,4613.36 -1114.05,4637.69 -1116.84,4680.85 -1119.64,4877.92 -1122.43,5228.33 -1125.22,6121.04 -1128.02,7916.85 -1130.81,8712.5 -1133.6,8653.49 -1136.39,7896.52 -1139.17,6547.75 -1141.96,5218.25 -1144.74,4812.58 -1147.53,4541.68 -1150.31,4483.79 -1153.1,4402 -1155.88,4414.83 -1158.66,4424.87 -1161.44,4489.06 -1164.22,4484.85 -1167,4717.06 -1169.77,4930.3 -1172.55,5017.39 -1175.32,4914.7 -1178.1,4860.99 -1180.87,4549 -1183.64,4375.14 -1186.41,4348.75 -1189.18,4316.6 -1191.95,4314.76 -1194.72,4344.14 -1197.49,4334.52 -1200.25,4343.21 -1203.02,4319.89 -1205.78,4312.41 -1208.54,4366.09 -1211.31,4284.87 -1214.07,4320.61 -1216.83,4297.22 -1219.59,4274.4 -1222.34,4345.17 -1225.1,4372.16 -1227.86,4379.47 -1230.61,4391.88 -1233.37,4329.46 -1236.12,4390.95 -1238.87,4390.23 -1241.62,4290.72 -1244.38,4352.33 -1247.13,4405.12 -1249.87,4407.56 -1252.62,4438.51 -1255.37,4358.12 -1258.11,4462.78 -1260.86,4571.03 -1263.6,4538.64 -1266.34,4633.41 -1269.09,4636.2 -1271.83,4697.79 -1274.57,4763.89 -1277.31,4722.72 -1280.04,4754.91 -1282.78,4813.67 -1285.51,5018.72 -1288.25,5271.36 -1290.98,6588.85 -1293.72,9705.09 -1296.45,11307 -1299.18,11323.1 -1301.91,10580.6 -1304.64,8384.68 -1307.37,6320.46 -1310.09,5720.17 -1312.82,5458.19 -1315.55,5224.83 -1318.27,5172.31 -1320.99,5010.53 -1323.72,4878.78 -1326.44,4744.36 -1329.16,4721.22 -1331.88,4686.74 -1334.6,4569.91 -1337.31,4549.3 -1340.03,4464.4 -1342.75,4452.15 -1345.46,4401.73 -1348.18,4424.27 -1350.89,4486.65 -1353.6,4489.75 -1356.31,4471.35 -1359.02,4444.46 -1361.73,4513.29 -1364.44,4595.51 -1367.14,4678.53 -1369.85,4816.26 -1372.55,4882.78 -1375.26,4881.58 -1377.96,4748.71 -1380.66,4614.2 -1383.37,4503.8 -1386.06,4466.42 -1388.77,4449 -1391.46,4393.46 -1394.16,4425.04 -1396.86,4405.78 -1399.55,4473.86 -1402.25,4440.79 -1404.94,4483.36 -1407.63,4546.19 -1410.33,4715.59 -1413.02,5361.53 -1415.71,6605.68 -1418.4,7210.9 -1421.08,7299.79 -1423.77,7163.15 -1426.46,6575.36 -1429.14,6118.29 -1431.83,6367.78 -1434.51,7020.6 -1437.19,8200.95 -1439.87,9476.78 -1442.55,9905.29 -1445.23,9898.64 -1447.91,9343.59 -1450.59,8510.2 -1453.27,7783.9 -1455.94,7602.21 -1458.61,7641.58 -1461.29,7819.6 -1463.96,7905.98 -1466.63,7750.59 -1469.3,7427.8 -1471.98,6887.81 -1474.64,6325.11 -1477.31,5889.39 -1479.98,5508.78 -1482.64,5277.77 -1485.31,4955.68 -1487.97,4872.01 -1490.64,4798.75 -1493.3,4765.08 -1495.96,4656.44 -1498.62,4667.88 -1501.28,4580.52 -1503.94,4548.29 -1506.6,4545.61 -1509.25,4603.98 -1511.91,4494.47 -1514.57,4494.3 -1517.22,4495.52 -1519.87,4469.64 -1522.52,4525.51 -1525.18,4559.84 -1527.83,4580.68 -1530.48,4500.05 -1533.13,4515.06 -1535.77,4485.72 -1538.42,4461.22 -1541.06,4519.49 -1543.71,4499.9 -1546.35,4451.56 -1548.99,4494.54 -1551.64,4505.74 -1554.28,4455.46 -1556.92,4544.85 -1559.56,4454.63 -1562.19,4502.87 -1564.83,4477.23 -1567.47,4504.51 -1570.1,4519.19 -1572.74,4490.47 -1575.37,4469.9 -1578,4458.71 -1580.64,4442.01 -1583.27,4475.3 -1585.9,4482.14 -1588.52,4403.55 -1591.15,4419.99 -1593.78,4458.23 -1596.4,4476.44 -1599.03,4420.86 -1601.66,4496.8 -1604.28,4437.07 -1606.9,4458.23 -1609.52,4491.46 -1612.14,4414.54 -1614.76,4504.4 -1617.38,4462.22 -1620,4455.89 -1622.61,4464.33 -1625.23,4486.88 -1627.85,4429.59 -1630.46,4392.87 -1633.07,4389.73 -1635.68,4467.6 -1638.29,4464.29 -1640.9,4471.13 -1643.52,4473.71 -1646.12,4462.99 -1648.73,4430.61 -1651.34,4465.97 -1653.94,4467.31 -1656.55,4430.66 -1659.15,4470.12 -1661.75,4490.6 -1664.36,4438.22 -1666.96,4456.58 -1669.56,4428.19 -1672.16,4484.04 -1674.76,4446.02 -1677.35,4409.03 -1679.95,4497.13 -1682.54,4442.74 -1685.14,4456.84 -1687.73,4450.33 -1690.33,4416.77 -1692.92,4449.58 -1695.51,4429.66 -1698.1,4425.71 -1700.69,4430.72 -1703.28,4432.35 -1705.86,4459.68 -1708.45,4451.1 -1711.04,4456 -1713.62,4453.37 -1716.2,4395.13 -1718.79,4469.17 -1721.37,4470.27 -1723.95,4414.41 -1726.53,4398.62 -1729.11,4532.49 -1731.69,4440.43 -1734.27,4463.29 -1736.84,4437.15 -1739.42,4461.12 -1741.99,4421.98 -1744.57,4414.69 -1747.14,4451.67 -1749.71,4424.24 -1752.28,4492.52 -1754.85,4456.95 -1757.42,4490.44 -1759.99,4485.8 -1762.56,4430.86 -1765.12,4515.53 -1767.69,4522.96 -1770.25,4559.77 -1772.82,4477 -1775.38,4565.34 -1777.94,4543.03 -1780.5,4519.18 -1783.06,4466.46 -1785.62,4428.16 -1788.18,4534.75 -1790.74,4470.05 -1793.29,4508.07 -1795.85,4472.48 -1798.4,4436.77 -1800.96,4447.32 -1803.51,4400.34 -1806.06,4496.12 -1808.61,4440.48 -1811.16,4438.83 -1813.71,4465.16 -1816.26,4414.74 -1818.81,4420.22 -1821.36,4442.6 -1823.9,4444.69 -1826.45,4417.08 -1828.99,4384.99 -1831.53,4458.12 -1834.08,4390.74 -1836.62,4426.78 -1839.16,4433.44 -1841.7,4412.45 -1844.23,4430.66 -1846.77,4416.92 -1849.31,4420.17 -1851.84,4392.87 -1854.38,4427.92 -1856.91,4423.56 -1859.45,4409.8 -1861.98,4454.08 -1864.51,4420.11 -1867.04,4362.08 -1869.57,4427 -1872.1,4389.34 -1874.63,4355.37 -1877.15,4450.22 -1879.68,4443.57 -1882.2,4300.96 -1884.73,4448.48 -1887.25,4393.92 -1889.77,4424.48 -1892.3,4399.11 -1894.82,4369.52 -1897.34,4406.73 -1899.86,4406.69 -1902.37,4373.26 -1904.89,4439.47 -1907.41,4408.88 -1909.92,4374.47 -1912.44,4362.29 -1914.95,4409.91 -1917.46,4428.04 -1919.98,4392 -1922.49,4412.34 -1925,4434.17 -1927.5,4345.13 -1930.01,4477.38 -1932.52,4414.24 -1935.03,4447.69 -1937.53,4392.21 -1940.04,4463.53 -1942.54,4422.74 -1945.04,4424.06 -1947.55,4422.37 -1950.05,4450.37 -1952.55,4449.07 -1955.05,4411.08 -1957.55,4463.09 -1960.04,4348.24 -1962.54,4394.06 -1965.04,4396.29 -1967.53,4378.03 -1970.02,4425.58 -1972.52,4369.75 -1975.01,4473.03 -1977.5,4449.46 -1979.99,4391.5 -1982.48,4427.61 -1984.97,4328.25 -1987.46,4360.16 -1989.95,4312.36 -1992.43,4382.81 -1994.92,4417.35 -1997.4,4349.33 -1999.89,4370.11 -2002.37,4368.04 -2004.85,4406.27 -2007.33,4380.97 -2009.81,4384.26 -2012.29,4388.14 -2014.77,4333.94 -2017.25,4401.78 -2019.72,4356.2 -2022.2,4340.42 -2024.68,4406.91 -2027.15,4395.92 -2029.62,4354.85 -2032.09,4384.42 -2034.57,4365.37 -2037.04,4385.66 -2039.51,4353.36 -2041.97,4402.19 -2044.44,4401.75 -2046.91,4380.55 -2049.37,4369.79 -2051.84,4351.95 -2054.31,4381.82 -2056.77,4346.29 -2059.23,4342.7 -2061.69,4419.42 -2064.15,4355.71 -2066.61,4337.32 -2069.07,4334.96 -2071.53,4407.18 -2073.99,4333.61 -2076.45,4412.13 -2078.9,4351.05 -2081.36,4382.86 -2083.81,4313.32 -2086.26,4359.85 -2088.71,4389.69 -2091.17,4327.18 -2093.62,4375.59 -2096.07,4392.29 -2098.52,4401.58 -2100.96,4387.99 -2103.41,4419.02 -2105.86,4383.29 -2108.3,4352.65 -2110.75,4273.14 -2113.19,4364.32 -2115.63,4364.17 -2118.07,4426.14 -2120.52,4441.4 -2122.96,4417.5 -2125.39,4436.17 -2127.83,4347.82 -2130.27,4385.14 -2132.71,4373.54 -2135.14,4394.83 -2137.58,4326.96 -2140.01,4349.19 -2142.45,4347.6 -2144.88,4334.17 -2147.31,4348.79 -2149.74,4337.94 -2152.17,4341.45 -2154.6,4320.91 -2157.03,4370.53 -2159.46,4319.33 -2161.88,4310.7 -2164.31,4331.1 -2166.73,4368.57 -2169.16,4363.47 -2171.58,4366.53 -2174,4392.43 -2176.42,4355.6 -2178.84,4351.24 -2181.26,4385.36 -2183.68,4509.69 -2186.1,4451.93 -2188.52,4459.68 -2190.93,4437.57 -2193.35,4463.2 -2195.76,4388.34 -2198.18,4377.17 -2200.59,4311.4 -2203,4326.21 -2205.41,4345.88 -2207.82,4321.99 -2210.23,4264.63 -2212.64,4280.81 -2215.05,4270.76 -2217.45,4284.34 -2219.86,4251.89 -2222.27,4254.67 -2224.67,4273.56 -2227.07,4246.04 -2229.48,4240.79 -2231.88,4296.89 -2234.28,4272.14 -2236.68,4242.41 -2239.08,4252.81 -2241.48,4205.56 -2243.87,4269.63 -2246.27,4239.99 -2248.67,4245.6 -2251.06,4250.8 -2253.46,4229.87 -2255.85,4219.93 -2258.24,4190.77 -2260.63,4179.94 -2263.02,4212.95 -2265.41,4183.05 -2267.8,4248.87 -2270.19,4193.99 -2272.58,4208.66 -2274.97,4190.34 -2277.35,4124.58 -2279.74,4201.99 -2282.12,4192.63 -2284.5,4187.79 -2286.88,4240.27 -2289.27,4218.91 -2291.65,4175.02 -2294.03,4180.99 -2296.41,4171.44 -2298.78,4152.76 -2301.16,4123.38 -2303.54,4157.46 -2305.91,4124.84 -2308.29,4180.63 -2310.66,4154.39 -2313.03,4203.59 -2315.41,4160.86 -2317.78,4150.83 -2320.15,4154.26 -2322.52,4195.05 -2324.89,4107.9 -2327.26,4116.43 -2329.62,4092.27 -2331.99,4127.3 -2334.36,4163.73 -2336.72,4120.4 -2339.08,4117.1 -2341.45,4079.93 -2343.81,4140.68 -2346.17,4116.92 -2348.53,4093.41 -2350.89,4048.03 -2353.25,4123.54 -2355.61,4139.57 -2357.97,4081.89 -2360.32,4113.43 -2362.68,4079.79 -2365.03,4105.69 -2367.39,4146.24 -2369.74,4107.31 -2372.09,4076.18 -2374.44,4074.17 -2376.8,4108.92 -2379.14,4136.16 -2381.49,4134.89 -2383.84,4099.93 -2386.19,4055.05 -2388.54,4136.2 -2390.88,4029.55 -2393.23,4115.02 -2395.57,4098.34 -2397.91,4140.95 -2400.26,4088.42 -2402.6,4143.36 -2404.94,4164.45 -2407.28,4135.59 -2409.62,4158.89 -2411.96,4175.21 -2414.29,4210.4 -2416.63,4213.85 -2418.96,4211.83 -2421.3,4286.39 -2423.63,4345.39 -2425.97,4383.69 -2428.3,4461.09 -2430.63,4548.49 -2432.96,4623.16 -2435.29,4732.23 -2437.62,4746.77 -2439.95,4772.09 -2442.28,4692.48 -2444.6,4655.95 -2446.93,4579.38 -2449.25,4477.12 -2451.58,4411.65 -2453.9,4315.03 -2456.23,4247.1 -2458.55,4253.34 -2460.87,4214.52 -2463.19,4121.34 -2465.51,4193.91 -2467.83,4163.95 -2470.14,4103.08 -2472.46,4101.75 -2474.78,4121.8 -2477.09,4168.45 -2479.41,4148.28 -2481.72,4122.95 -2484.03,4115.55 -2486.35,4092.52 -2488.66,4153.87 -2490.97,4121.7 -2493.28,4062.25 -2495.58,4059.72 -2497.89,4107.27 -2500.2,4093.77 -2502.51,4117.35 -2504.81,4057.58 -2507.12,4069.03 -2509.42,4025.31 -2511.72,4044.02 -2514.02,4064.78 -2516.33,4129.98 -2518.63,4128.39 -2520.93,4038.05 -2523.23,4076.44 -2525.52,4100.04 -2527.82,4087.37 -2530.12,4083.93 -2532.41,4023.74 -2534.71,4082.63 -2537,4046.63 -2539.3,4041.89 -2541.59,4079.02 -2543.88,4031.96 -2546.17,4065.65 -2548.46,4063.66 -2550.75,4047.05 -2553.04,4063.17 -2555.33,4057.74 -2557.61,4059.17 -2559.9,4031.15 -2562.18,4047.5 -2564.47,4042.74 -2566.75,4042.79 -2569.04,4065.52 -2571.32,4073.48 -2573.6,4080.41 -2575.88,4105.74 -2578.16,4075.31 -2580.44,4160.66 -2582.72,4144.29 -2584.99,4133.13 -2587.27,4206.24 -2589.54,4246.81 -2591.82,4279.13 -2594.09,4298.37 -2596.37,4257.18 -2598.64,4291.16 -2600.91,4255.08 -2603.18,4234.93 -2605.45,4299.17 -2607.72,4300.83 -2609.99,4266.12 -2612.26,4337.95 -2614.52,4331.82 -2616.79,4298.75 -2619.05,4312.77 -2621.32,4302.57 -2623.58,4283.71 -2625.84,4295.16 -2628.11,4264.77 -2630.37,4274.62 -2632.63,4283.22 -2634.89,4297.41 -2637.15,4277.32 -2639.4,4320.57 -2641.66,4332.86 -2643.92,4301.33 -2646.17,4373.54 -2648.43,4399.9 -2650.68,4423.32 -2652.94,4436.07 -2655.19,4429.47 -2657.44,4470.12 -2659.69,4477.75 -2661.94,4440.79 -2664.19,4449.36 -2666.44,4414.17 -2668.69,4379.71 -2670.93,4386.99 -2673.18,4411.18 -2675.43,4399.4 -2677.67,4436.91 -2679.91,4427.59 -2682.16,4423.15 -2684.4,4479.63 -2686.64,4490.84 -2688.88,4541.55 -2691.12,4577.37 -2693.36,4581.42 -2695.6,4673.72 -2697.83,4746.94 -2700.07,4811.85 -2702.31,4836.56 -2704.54,4977.89 -2706.78,5097.73 -2709.01,5257.63 -2711.24,5400.57 -2713.47,5562.59 -2715.7,5707.22 -2717.94,5862.49 -2720.16,6035.53 -2722.39,6098.09 -2724.62,6177.75 -2726.85,6137.36 -2729.08,6056.76 -2731.3,5862.7 -2733.52,5646.08 -2735.75,5377.56 -2737.97,5162.35 -2740.19,4967.94 -2742.42,4852.22 -2744.64,4724.18 -2746.86,4659.15 -2749.08,4561.44 -2751.29,4515.94 -2753.51,4473.24 -2755.73,4430.19 -2757.95,4380 -2760.16,4369.24 -2762.38,4360.05 -2764.59,4320.09 -2766.8,4320.21 -2769.02,4340.11 -2771.23,4296.88 -2773.44,4289.16 -2775.65,4312.34 -2777.86,4352.09 -2780.07,4325.45 -2782.27,4355.89 -2784.48,4363.04 -2786.69,4353.7 -2788.89,4325.12 -2791.1,4365.01 -2793.3,4363.7 -2795.5,4380.01 -2797.7,4393.18 -2799.91,4418.24 -2802.11,4424.72 -2802.79,4422.92 -2805.01,4490.55 -2807.24,4541.68 -2809.47,4587.13 -2811.7,4586.85 -2813.93,4691.87 -2816.15,4779.5 -2818.38,4870.18 -2820.6,4976.44 -2822.83,5223.86 -2825.05,5450.92 -2827.27,5869.27 -2829.49,6335.35 -2831.71,7182.51 -2833.94,8361.08 -2836.15,10035.8 -2838.37,12341.4 -2840.59,15509.3 -2842.81,19642.4 -2845.03,24083.3 -2847.24,27936.6 -2849.46,30599.7 -2851.67,31331.3 -2853.89,30169.5 -2856.1,28014.9 -2858.31,25677.8 -2860.52,23426.3 -2862.74,21585.8 -2864.95,20191.4 -2867.16,19303.7 -2869.36,18798 -2871.57,18532.1 -2873.78,18971.8 -2875.99,20645.9 -2878.2,24614.8 -2880.4,31989.9 -2882.61,38814.6 -2884.81,41238.9 -2887.01,40281.9 -2889.22,35937.3 -2891.42,29166.6 -2893.62,24756.9 -2895.82,22839.9 -2898.02,21630.9 -2900.22,20720.1 -2902.42,20024.5 -2904.62,19142 -2906.82,18187.2 -2909.01,17094.5 -2911.21,16105.7 -2913.41,15248.1 -2915.6,14340 -2917.79,13681.1 -2919.99,13383.7 -2922.18,13050.4 -2924.37,12831.3 -2926.56,12767 -2928.75,12547.5 -2930.94,12473.3 -2933.13,12081.7 -2935.32,11655.2 -2937.51,11070.9 -2939.7,10317.3 -2941.88,9578.88 -2944.07,8630.94 -2946.25,7836.73 -2948.44,7068.38 -2950.62,6344.02 -2952.81,5797.47 -2954.99,5476.22 -2957.17,5208.37 -2959.35,5076.34 -2961.53,4831.98 -2963.71,4746.79 -2965.89,4614.43 -2968.07,4455.67 -2970.25,4447.57 -2972.42,4336.36 -2974.6,4307.28 -2976.77,4274.31 -2978.95,4241.08 -2981.12,4254.55 -2983.3,4178.66 -2985.47,4176.24 -2987.64,4143.05 -2989.81,4101.6 -2991.98,4100.51 -2994.15,4075.13 -2996.32,4020.62 -2998.49,4059.37 +wavenumber,intensity +301.04,26 +304.632,50 +308.221,48 +311.81,45 +315.398,46 +318.983,42 +322.566,45 +326.15,44 +329.732,48 +333.311,46 +336.889,48 +340.467,44 +344.042,49 +347.618,52 +351.19,48 +354.76,53 +358.332,52 +361.9,46 +365.466,41 +369.031,50 +372.597,62 +376.16,57 +379.721,52 +383.28,45 +386.839,55 +390.396,42 +393.951,52 +397.505,44 +401.058,62 +404.61,57 +408.16,50 +411.708,59 +415.256,48 +418.801,47 +422.346,59 +425.887,58 +429.429,58 +432.97,74 +436.507,69 +440.044,78 +443.581,67 +447.116,73 +450.647,59 +454.18,72 +457.708,65 +461.238,53 +464.764,65 +468.291,63 +471.815,61 +475.339,56 +478.859,47 +482.379,52 +485.899,53 +489.416,58 +492.931,58 +496.447,43 +499.96,54 +503.471,55 +506.981,50 +510.489,47 +513.999,55 +517.503,58 +521.009,50 +524.51,47 +528.014,44 +531.513,55 +535.014,56 +538.51,58 +542.008,54 +545.502,52 +548.997,45 +552.488,64 +555.979,62 +559.469,62 +562.955,57 +566.442,55 +569.928,49 +573.411,65 +576.894,62 +580.376,53 +583.854,55 +587.331,59 +590.811,73 +594.286,63 +597.759,63 +601.231,67 +604.705,77 +608.174,74 +611.642,74 +615.109,79 +618.574,66 +622.038,66 +625.503,67 +628.966,60 +632.426,59 +635.884,48 +639.342,44 +642.798,57 +646.252,61 +649.706,59 +653.158,48 +656.608,59 +660.058,59 +663.506,43 +666.953,50 +670.398,49 +673.842,61 +677.285,58 +680.727,64 +684.165,74 +687.602,49 +691.04,64 +694.476,55 +697.911,55 +701.345,62 +704.775,52 +708.204,62 +711.634,55 +715.063,65 +718.49,54 +721.912,52 +725.336,66 +728.76,54 +732.18,54 +735.599,54 +739.018,60 +742.434,55 +745.849,56 +749.265,53 +752.675,61 +756.088,63 +759.498,53 +762.906,50 +766.313,62 +769.719,62 +773.124,70 +776.527,63 +779.929,61 +783.33,55 +786.729,56 +790.127,70 +793.524,62 +796.919,66 +800.312,60 +803.705,56 +807.097,67 +810.485,57 +813.874,64 +817.262,56 +820.647,51 +824.032,56 +827.416,59 +830.797,61 +834.179,53 +837.557,68 +840.936,64 +844.312,51 +847.688,60 +851.061,62 +854.433,62 +857.806,76 +861.175,65 +864.543,53 +867.91,68 +871.278,53 +874.642,51 +878.005,57 +881.367,53 +884.728,63 +888.087,60 +891.445,74 +894.802,59 +898.157,73 +901.511,51 +904.864,74 +908.216,65 +911.567,64 +914.914,59 +918.262,70 +921.609,61 +924.954,42 +928.296,71 +931.639,66 +934.981,52 +938.32,70 +941.659,67 +944.995,58 +948.332,65 +951.665,64 +954.999,66 +958.33,75 +961.66,59 +964.991,60 +968.318,64 +971.644,63 +974.971,68 +978.295,61 +981.617,53 +984.94,60 +988.258,67 +991.578,57 +994.896,74 +998.212,59 +1001.53,64 +1004.84,69 +1008.15,76 +1011.46,59 +1014.77,65 +1018.08,65 +1021.39,75 +1024.7,86 +1028,78 +1031.3,70 +1034.61,76 +1037.91,84 +1041.21,78 +1044.51,69 +1047.8,66 +1051.1,83 +1054.39,90 +1057.68,96 +1060.98,128 +1064.27,143 +1067.56,101 +1070.84,81 +1074.13,76 +1077.42,72 +1080.7,85 +1083.98,79 +1087.27,83 +1090.55,67 +1093.83,79 +1097.1,64 +1100.38,80 +1103.65,72 +1106.93,74 +1110.2,70 +1113.47,81 +1116.74,79 +1120.01,82 +1123.28,95 +1126.55,119 +1129.81,148 +1133.08,94 +1136.34,80 +1139.6,71 +1142.86,82 +1146.12,76 +1149.37,76 +1152.63,76 +1155.89,82 +1159.14,73 +1162.39,66 +1165.64,80 +1168.89,79 +1172.14,101 +1175.39,71 +1178.64,83 +1181.88,75 +1185.12,66 +1188.37,72 +1191.61,76 +1194.85,68 +1198.09,88 +1201.32,81 +1204.56,66 +1207.79,67 +1211.03,68 +1214.26,81 +1217.49,67 +1220.72,69 +1223.95,78 +1227.18,68 +1230.4,64 +1233.63,81 +1236.85,83 +1240.07,78 +1243.3,74 +1246.52,67 +1249.74,64 +1252.95,82 +1256.17,72 +1259.38,79 +1262.6,65 +1265.81,84 +1269.02,76 +1272.23,87 +1275.44,75 +1278.65,76 +1281.85,87 +1285.06,82 +1288.26,101 +1291.46,128 +1294.67,200 +1297.87,143 +1301.07,105 +1304.26,101 +1307.46,87 +1310.66,77 +1313.85,88 +1317.04,76 +1320.23,79 +1323.42,95 +1326.61,76 +1329.8,67 +1332.99,82 +1336.17,82 +1339.36,70 +1342.54,82 +1345.72,72 +1348.9,88 +1352.08,66 +1355.26,68 +1358.44,71 +1361.61,84 +1364.79,74 +1367.96,100 +1371.13,83 +1374.3,69 +1377.47,91 +1380.64,81 +1383.81,77 +1386.97,84 +1390.14,75 +1393.3,86 +1396.47,71 +1399.63,92 +1402.79,94 +1405.95,77 +1409.1,93 +1412.26,93 +1415.41,110 +1418.57,112 +1421.72,103 +1424.87,106 +1428.02,110 +1431.17,125 +1434.32,123 +1437.47,169 +1440.61,178 +1443.76,166 +1446.9,129 +1450.04,117 +1453.18,116 +1456.32,127 +1459.46,136 +1462.59,143 +1465.73,127 +1468.87,114 +1472,101 +1475.13,85 +1478.26,93 +1481.4,98 +1484.52,87 +1487.65,97 +1490.78,94 +1493.9,95 +1497.03,85 +1500.15,94 +1503.27,93 +1506.39,85 +1509.51,93 +1512.63,74 +1515.74,75 +1518.86,81 +1521.97,105 +1525.09,95 +1528.2,105 +1531.31,105 +1534.42,103 +1537.53,86 +1540.64,85 +1543.74,102 +1546.85,75 +1549.95,72 +1553.05,89 +1556.16,96 +1559.26,92 +1562.36,86 +1565.45,85 +1568.55,90 +1571.65,100 +1574.74,91 +1577.83,88 +1580.92,98 +1584.02,83 +1587.11,84 +1590.19,93 +1593.28,97 +1596.37,71 +1599.45,73 +1602.54,88 +1605.62,78 +1608.7,84 +1611.78,90 +1614.86,97 +1617.94,89 +1621.02,88 +1624.09,86 +1627.17,89 +1630.24,89 +1633.31,89 +1636.38,89 +1639.45,96 +1642.52,98 +1645.59,103 +1648.65,94 +1651.72,91 +1654.78,98 +1657.85,108 +1660.91,80 +1663.97,100 +1667.03,88 +1670.09,101 +1673.15,104 +1676.2,91 +1679.26,96 +1682.31,98 +1685.36,102 +1688.41,96 +1691.46,100 +1694.51,82 +1697.56,87 +1700.61,107 +1703.65,94 +1706.7,105 +1709.74,96 +1712.78,105 +1715.83,106 +1718.86,110 +1721.9,96 +1724.94,88 +1727.98,98 +1731.01,94 +1734.05,99 +1737.08,85 +1740.11,99 +1743.14,93 +1746.17,91 +1749.2,96 +1752.23,94 +1755.25,103 +1758.28,100 +1761.3,106 +1764.33,86 +1767.35,105 +1770.37,100 +1773.39,90 +1776.41,102 +1779.42,100 +1782.44,100 +1785.46,121 +1788.47,121 +1791.48,107 +1794.49,97 +1797.5,94 +1800.51,116 +1803.52,105 +1806.53,100 +1809.53,98 +1812.54,102 +1815.54,107 +1818.54,110 +1821.54,100 +1824.55,107 +1827.55,101 +1830.54,114 +1833.54,106 +1836.54,109 +1839.53,115 +1842.52,116 +1845.51,108 +1848.51,114 +1851.5,112 +1854.49,110 +1857.48,112 +1860.46,108 +1863.45,107 +1866.43,109 +1869.41,109 +1872.4,105 +1875.38,102 +1878.36,107 +1881.34,111 +1884.32,109 +1887.29,108 +1890.27,112 +1893.24,108 +1896.22,102 +1899.19,105 +1902.16,110 +1905.13,108 +1908.1,95 +1911.07,107 +1914.03,99 +1917,106 +1919.96,110 +1922.93,115 +1925.89,107 +1928.85,100 +1931.81,108 +1934.77,107 +1937.43,107 +1940.4,113 +1943.36,93 +1946.33,113 +1949.3,124 +1952.27,119 +1955.23,109 +1958.19,101 +1961.16,109 +1964.12,120 +1967.08,112 +1970.04,108 +1972.99,111 +1975.95,116 +1978.91,97 +1981.86,106 +1984.82,114 +1987.77,116 +1990.72,125 +1993.67,109 +1996.62,103 +1999.57,114 +2002.52,122 +2005.46,122 +2008.41,106 +2011.35,124 +2014.29,105 +2017.24,120 +2020.18,105 +2023.12,118 +2026.05,114 +2028.99,105 +2031.93,118 +2034.87,115 +2037.8,112 +2040.73,116 +2043.66,112 +2046.6,132 +2049.52,116 +2052.45,106 +2055.38,115 +2058.31,113 +2061.23,108 +2064.16,136 +2067.08,131 +2070,123 +2072.93,108 +2075.85,120 +2078.77,131 +2081.68,121 +2084.6,126 +2087.52,124 +2090.43,137 +2093.34,130 +2096.26,129 +2099.17,119 +2102.08,126 +2104.99,136 +2107.9,135 +2110.81,135 +2113.71,130 +2116.62,130 +2119.52,124 +2122.43,126 +2125.33,122 +2128.23,131 +2131.13,120 +2134.03,140 +2136.93,120 +2139.82,138 +2142.72,128 +2145.61,127 +2148.51,131 +2151.4,146 +2154.29,133 +2157.18,141 +2160.07,124 +2162.96,123 +2165.85,137 +2168.73,131 +2171.62,127 +2174.5,128 +2177.39,123 +2180.27,135 +2183.15,123 +2186.03,121 +2188.91,128 +2191.79,141 +2194.67,136 +2197.54,136 +2200.42,119 +2203.29,114 +2206.16,122 +2209.03,127 +2211.9,135 +2214.77,138 +2217.64,126 +2220.51,125 +2223.38,118 +2226.24,121 +2229.11,129 +2231.97,133 +2234.83,137 +2237.69,131 +2240.55,118 +2243.41,142 +2246.27,103 +2249.13,113 +2251.98,123 +2254.84,110 +2257.69,135 +2260.54,128 +2263.39,133 +2266.25,121 +2269.1,131 +2271.94,118 +2274.79,115 +2277.64,122 +2280.49,124 +2283.33,114 +2286.17,120 +2289.02,136 +2291.86,129 +2294.7,123 +2297.54,119 +2300.38,119 +2303.21,118 +2306.05,114 +2308.89,116 +2311.72,116 +2314.55,120 +2317.39,108 +2320.22,113 +2323.05,114 +2325.88,129 +2328.7,116 +2331.53,110 +2334.36,109 +2337.18,118 +2340.01,103 +2342.83,121 +2345.65,120 +2348.47,108 +2351.29,108 +2354.11,119 +2356.93,130 +2359.75,104 +2362.56,107 +2365.38,95 +2368.19,110 +2371,96 +2373.82,117 +2376.63,109 +2379.44,111 +2382.25,111 +2385.05,112 +2387.86,113 +2390.67,112 +2393.47,101 +2396.28,110 +2399.08,107 +2401.88,96 +2404.68,99 +2407.48,105 +2410.28,101 +2413.08,99 +2415.87,103 +2418.67,97 +2421.46,96 +2424.26,108 +2427.05,107 +2429.84,103 +2432.63,103 +2435.42,112 +2438.21,103 +2441,115 +2443.79,108 +2446.57,107 +2449.36,102 +2452.14,108 +2454.92,103 +2457.7,102 +2460.48,98 +2463.26,102 +2466.04,98 +2468.82,105 +2471.6,107 +2474.37,95 +2477.15,103 +2479.92,112 +2482.69,112 +2485.47,105 +2488.24,99 +2491.01,105 +2493.77,112 +2496.54,99 +2499.31,99 +2502.07,106 +2504.84,101 +2507.6,97 +2510.37,102 +2513.13,99 +2515.89,99 +2518.65,102 +2521.41,95 +2524.16,109 +2526.92,96 +2529.68,100 +2532.43,99 +2535.19,103 +2537.94,117 +2540.69,100 +2543.44,106 +2546.19,110 +2548.94,96 +2551.69,97 +2554.44,105 +2557.18,98 +2559.93,100 +2562.67,90 +2565.42,94 +2568.16,107 +2570.9,101 +2573.64,92 +2576.38,96 +2579.11,106 +2581.85,105 +2584.59,76 +2587.32,93 +2590.06,104 +2592.79,101 +2595.52,104 +2598.25,95 +2600.98,95 +2603.72,93 +2606.44,81 +2609.17,104 +2611.9,96 +2614.62,101 +2617.35,98 +2620.07,91 +2622.79,103 +2625.51,85 +2628.23,100 +2630.95,87 +2633.67,87 +2636.39,98 +2639.11,100 +2641.82,100 +2644.54,95 +2647.25,114 +2649.96,96 +2652.68,105 +2655.39,106 +2658.1,81 +2660.81,96 +2663.51,104 +2666.22,103 +2668.93,89 +2671.63,108 +2674.34,92 +2677.04,89 +2679.74,96 +2682.44,81 +2685.14,91 +2687.84,100 +2690.54,102 +2693.24,103 +2695.94,99 +2698.63,119 +2701.33,104 +2704.02,97 +2706.71,99 +2709.41,128 +2712.1,116 +2714.79,126 +2717.47,123 +2720.16,130 +2722.85,121 +2725.53,132 +2728.22,115 +2730.9,111 +2733.59,109 +2736.27,102 +2738.95,96 +2741.63,108 +2744.31,104 +2746.99,82 +2749.67,100 +2752.34,107 +2755.02,97 +2757.69,81 +2760.37,100 +2763.04,101 +2765.71,98 +2768.38,98 +2771.05,100 +2773.72,102 +2776.39,97 +2779.06,95 +2781.72,90 +2784.39,93 +2787.05,102 +2789.71,95 +2792.38,103 +2795.04,106 +2797.7,102 +2800.36,99 +2803.02,101 +2805.68,117 +2808.33,101 +2810.99,104 +2813.64,114 +2816.3,112 +2818.95,119 +2821.6,106 +2824.25,116 +2826.9,131 +2829.55,132 +2832.2,148 +2834.85,169 +2837.49,200 +2840.14,303 +2842.78,378 +2845.43,452 +2848.07,489 +2850.71,465 +2853.35,431 +2855.99,388 +2858.63,356 +2861.27,333 +2863.91,318 +2866.54,305 +2869.18,308 +2871.81,305 +2874.45,335 +2877.08,407 +2879.71,576 +2882.34,816 +2884.97,664 +2887.6,494 +2890.23,393 +2892.86,352 +2895.48,318 +2898.11,322 +2900.73,321 +2903.35,308 +2905.97,279 +2908.6,254 +2911.22,257 +2913.84,238 +2916.46,222 +2919.07,214 +2921.69,219 +2924.31,199 +2926.92,207 +2929.54,212 +2932.15,226 +2934.76,206 +2937.37,181 +2939.98,186 +2942.59,162 +2945.2,148 +2947.81,107 +2950.42,119 +2953.02,110 +2955.63,113 +2958.23,101 +2960.83,109 +2963.44,114 +2966.04,105 +2968.64,107 +2971.24,94 +2973.83,91 +2976.43,97 +2979.03,101 +2981.63,87 +2984.22,108 +2986.81,95 +2989.41,103 +2992,95 +2994.59,94 +2997.18,96 +2999.77,89 +3002.36,89 +3004.95,105 +3007.53,94 +3010.12,79 +3012.7,85 +3015.29,99 +3017.87,93 +3020.45,84 +3023.04,77 +3025.62,95 +3028.2,92 +3030.77,73 +3033.35,89 +3035.93,98 +3038.5,72 +3041.08,80 +3043.65,81 +3046.23,98 +3048.8,87 +3051.37,81 +3053.94,86 +3056.51,92 +3059.08,85 +3061.65,86 +3064.21,92 +3066.78,86 +3069.35,82 +3071.91,87 +3074.47,86 +3077.04,93 +3079.6,87 +3082.16,88 +3084.72,87 +3087.28,81 +3089.83,82 +3092.39,90 +3094.95,76 +3097.5,90 +3100.06,83 +3102.61,80 +3105.16,75 +3107.72,82 +3110.27,83 +3112.82,76 +3115.37,74 +3117.91,84 +3120.46,83 +3123.01,73 +3125.56,74 +3128.1,77 +3130.64,77 +3133.19,77 +3135.73,81 +3138.27,80 +3140.81,77 +3143.35,75 +3145.89,68 +3148.43,69 +3150.96,77 +3153.5,77 +3156.03,73 +3158.57,74 +3161.1,81 +3163.64,75 +3166.17,68 +3168.7,73 +3171.23,75 +3173.76,76 +3175.3,77 +3177.84,75 +3180.38,77 +3182.91,71 +3185.45,77 +3187.99,71 +3190.52,71 +3193.06,75 +3195.59,75 +3198.12,67 diff --git a/inst/extdata/raman_hdpe.json b/inst/extdata/raman_hdpe.json new file mode 100644 index 00000000..3b86314d --- /dev/null +++ b/inst/extdata/raman_hdpe.json @@ -0,0 +1 @@ +{"wavenumber":[301.04,304.632,308.221,311.81,315.398,318.983,322.566,326.15,329.732,333.311,336.889,340.467,344.042,347.618,351.19,354.76,358.332,361.9,365.466,369.031,372.597,376.16,379.721,383.28,386.839,390.396,393.951,397.505,401.058,404.61,408.16,411.708,415.256,418.801,422.346,425.887,429.429,432.97,436.507,440.044,443.581,447.116,450.647,454.18,457.708,461.238,464.764,468.291,471.815,475.339,478.859,482.379,485.899,489.416,492.931,496.447,499.96,503.471,506.981,510.489,513.999,517.503,521.009,524.51,528.014,531.513,535.014,538.51,542.008,545.502,548.997,552.488,555.979,559.469,562.955,566.442,569.928,573.411,576.894,580.376,583.854,587.331,590.811,594.286,597.759,601.231,604.705,608.174,611.642,615.109,618.574,622.038,625.503,628.966,632.426,635.884,639.342,642.798,646.252,649.706,653.158,656.608,660.058,663.506,666.953,670.398,673.842,677.285,680.727,684.165,687.602,691.04,694.476,697.911,701.345,704.775,708.204,711.634,715.063,718.49,721.912,725.336,728.76,732.18,735.599,739.018,742.434,745.849,749.265,752.675,756.088,759.498,762.906,766.313,769.719,773.124,776.527,779.929,783.33,786.729,790.127,793.524,796.919,800.312,803.705,807.097,810.485,813.874,817.262,820.647,824.032,827.416,830.797,834.179,837.557,840.936,844.312,847.688,851.061,854.433,857.806,861.175,864.543,867.91,871.278,874.642,878.005,881.367,884.728,888.087,891.445,894.802,898.157,901.511,904.864,908.216,911.567,914.914,918.262,921.609,924.954,928.296,931.639,934.981,938.32,941.659,944.995,948.332,951.665,954.999,958.33,961.66,964.991,968.318,971.644,974.971,978.295,981.617,984.94,988.258,991.578,994.896,998.212,1001.53,1004.84,1008.15,1011.46,1014.77,1018.08,1021.39,1024.7,1028,1031.3,1034.61,1037.91,1041.21,1044.51,1047.8,1051.1,1054.39,1057.68,1060.98,1064.27,1067.56,1070.84,1074.13,1077.42,1080.7,1083.98,1087.27,1090.55,1093.83,1097.1,1100.38,1103.65,1106.93,1110.2,1113.47,1116.74,1120.01,1123.28,1126.55,1129.81,1133.08,1136.34,1139.6,1142.86,1146.12,1149.37,1152.63,1155.89,1159.14,1162.39,1165.64,1168.89,1172.14,1175.39,1178.64,1181.88,1185.12,1188.37,1191.61,1194.85,1198.09,1201.32,1204.56,1207.79,1211.03,1214.26,1217.49,1220.72,1223.95,1227.18,1230.4,1233.63,1236.85,1240.07,1243.3,1246.52,1249.74,1252.95,1256.17,1259.38,1262.6,1265.81,1269.02,1272.23,1275.44,1278.65,1281.85,1285.06,1288.26,1291.46,1294.67,1297.87,1301.07,1304.26,1307.46,1310.66,1313.85,1317.04,1320.23,1323.42,1326.61,1329.8,1332.99,1336.17,1339.36,1342.54,1345.72,1348.9,1352.08,1355.26,1358.44,1361.61,1364.79,1367.96,1371.13,1374.3,1377.47,1380.64,1383.81,1386.97,1390.14,1393.3,1396.47,1399.63,1402.79,1405.95,1409.1,1412.26,1415.41,1418.57,1421.72,1424.87,1428.02,1431.17,1434.32,1437.47,1440.61,1443.76,1446.9,1450.04,1453.18,1456.32,1459.46,1462.59,1465.73,1468.87,1472,1475.13,1478.26,1481.4,1484.52,1487.65,1490.78,1493.9,1497.03,1500.15,1503.27,1506.39,1509.51,1512.63,1515.74,1518.86,1521.97,1525.09,1528.2,1531.31,1534.42,1537.53,1540.64,1543.74,1546.85,1549.95,1553.05,1556.16,1559.26,1562.36,1565.45,1568.55,1571.65,1574.74,1577.83,1580.92,1584.02,1587.11,1590.19,1593.28,1596.37,1599.45,1602.54,1605.62,1608.7,1611.78,1614.86,1617.94,1621.02,1624.09,1627.17,1630.24,1633.31,1636.38,1639.45,1642.52,1645.59,1648.65,1651.72,1654.78,1657.85,1660.91,1663.97,1667.03,1670.09,1673.15,1676.2,1679.26,1682.31,1685.36,1688.41,1691.46,1694.51,1697.56,1700.61,1703.65,1706.7,1709.74,1712.78,1715.83,1718.86,1721.9,1724.94,1727.98,1731.01,1734.05,1737.08,1740.11,1743.14,1746.17,1749.2,1752.23,1755.25,1758.28,1761.3,1764.33,1767.35,1770.37,1773.39,1776.41,1779.42,1782.44,1785.46,1788.47,1791.48,1794.49,1797.5,1800.51,1803.52,1806.53,1809.53,1812.54,1815.54,1818.54,1821.54,1824.55,1827.55,1830.54,1833.54,1836.54,1839.53,1842.52,1845.51,1848.51,1851.5,1854.49,1857.48,1860.46,1863.45,1866.43,1869.41,1872.4,1875.38,1878.36,1881.34,1884.32,1887.29,1890.27,1893.24,1896.22,1899.19,1902.16,1905.13,1908.1,1911.07,1914.03,1917,1919.96,1922.93,1925.89,1928.85,1931.81,1934.77,1937.43,1940.4,1943.36,1946.33,1949.3,1952.27,1955.23,1958.19,1961.16,1964.12,1967.08,1970.04,1972.99,1975.95,1978.91,1981.86,1984.82,1987.77,1990.72,1993.67,1996.62,1999.57,2002.52,2005.46,2008.41,2011.35,2014.29,2017.24,2020.18,2023.12,2026.05,2028.99,2031.93,2034.87,2037.8,2040.73,2043.66,2046.6,2049.52,2052.45,2055.38,2058.31,2061.23,2064.16,2067.08,2070,2072.93,2075.85,2078.77,2081.68,2084.6,2087.52,2090.43,2093.34,2096.26,2099.17,2102.08,2104.99,2107.9,2110.81,2113.71,2116.62,2119.52,2122.43,2125.33,2128.23,2131.13,2134.03,2136.93,2139.82,2142.72,2145.61,2148.51,2151.4,2154.29,2157.18,2160.07,2162.96,2165.85,2168.73,2171.62,2174.5,2177.39,2180.27,2183.15,2186.03,2188.91,2191.79,2194.67,2197.54,2200.42,2203.29,2206.16,2209.03,2211.9,2214.77,2217.64,2220.51,2223.38,2226.24,2229.11,2231.97,2234.83,2237.69,2240.55,2243.41,2246.27,2249.13,2251.98,2254.84,2257.69,2260.54,2263.39,2266.25,2269.1,2271.94,2274.79,2277.64,2280.49,2283.33,2286.17,2289.02,2291.86,2294.7,2297.54,2300.38,2303.21,2306.05,2308.89,2311.72,2314.55,2317.39,2320.22,2323.05,2325.88,2328.7,2331.53,2334.36,2337.18,2340.01,2342.83,2345.65,2348.47,2351.29,2354.11,2356.93,2359.75,2362.56,2365.38,2368.19,2371,2373.82,2376.63,2379.44,2382.25,2385.05,2387.86,2390.67,2393.47,2396.28,2399.08,2401.88,2404.68,2407.48,2410.28,2413.08,2415.87,2418.67,2421.46,2424.26,2427.05,2429.84,2432.63,2435.42,2438.21,2441,2443.79,2446.57,2449.36,2452.14,2454.92,2457.7,2460.48,2463.26,2466.04,2468.82,2471.6,2474.37,2477.15,2479.92,2482.69,2485.47,2488.24,2491.01,2493.77,2496.54,2499.31,2502.07,2504.84,2507.6,2510.37,2513.13,2515.89,2518.65,2521.41,2524.16,2526.92,2529.68,2532.43,2535.19,2537.94,2540.69,2543.44,2546.19,2548.94,2551.69,2554.44,2557.18,2559.93,2562.67,2565.42,2568.16,2570.9,2573.64,2576.38,2579.11,2581.85,2584.59,2587.32,2590.06,2592.79,2595.52,2598.25,2600.98,2603.72,2606.44,2609.17,2611.9,2614.62,2617.35,2620.07,2622.79,2625.51,2628.23,2630.95,2633.67,2636.39,2639.11,2641.82,2644.54,2647.25,2649.96,2652.68,2655.39,2658.1,2660.81,2663.51,2666.22,2668.93,2671.63,2674.34,2677.04,2679.74,2682.44,2685.14,2687.84,2690.54,2693.24,2695.94,2698.63,2701.33,2704.02,2706.71,2709.41,2712.1,2714.79,2717.47,2720.16,2722.85,2725.53,2728.22,2730.9,2733.59,2736.27,2738.95,2741.63,2744.31,2746.99,2749.67,2752.34,2755.02,2757.69,2760.37,2763.04,2765.71,2768.38,2771.05,2773.72,2776.39,2779.06,2781.72,2784.39,2787.05,2789.71,2792.38,2795.04,2797.7,2800.36,2803.02,2805.68,2808.33,2810.99,2813.64,2816.3,2818.95,2821.6,2824.25,2826.9,2829.55,2832.2,2834.85,2837.49,2840.14,2842.78,2845.43,2848.07,2850.71,2853.35,2855.99,2858.63,2861.27,2863.91,2866.54,2869.18,2871.81,2874.45,2877.08,2879.71,2882.34,2884.97,2887.6,2890.23,2892.86,2895.48,2898.11,2900.73,2903.35,2905.97,2908.6,2911.22,2913.84,2916.46,2919.07,2921.69,2924.31,2926.92,2929.54,2932.15,2934.76,2937.37,2939.98,2942.59,2945.2,2947.81,2950.42,2953.02,2955.63,2958.23,2960.83,2963.44,2966.04,2968.64,2971.24,2973.83,2976.43,2979.03,2981.63,2984.22,2986.81,2989.41,2992,2994.59,2997.18,2999.77,3002.36,3004.95,3007.53,3010.12,3012.7,3015.29,3017.87,3020.45,3023.04,3025.62,3028.2,3030.77,3033.35,3035.93,3038.5,3041.08,3043.65,3046.23,3048.8,3051.37,3053.94,3056.51,3059.08,3061.65,3064.21,3066.78,3069.35,3071.91,3074.47,3077.04,3079.6,3082.16,3084.72,3087.28,3089.83,3092.39,3094.95,3097.5,3100.06,3102.61,3105.16,3107.72,3110.27,3112.82,3115.37,3117.91,3120.46,3123.01,3125.56,3128.1,3130.64,3133.19,3135.73,3138.27,3140.81,3143.35,3145.89,3148.43,3150.96,3153.5,3156.03,3158.57,3161.1,3163.64,3166.17,3168.7,3171.23,3173.76,3175.3,3177.84,3180.38,3182.91,3185.45,3187.99,3190.52,3193.06,3195.59,3198.12],"spectra":{"intensity":[26,50,48,45,46,42,45,44,48,46,48,44,49,52,48,53,52,46,41,50,62,57,52,45,55,42,52,44,62,57,50,59,48,47,59,58,58,74,69,78,67,73,59,72,65,53,65,63,61,56,47,52,53,58,58,43,54,55,50,47,55,58,50,47,44,55,56,58,54,52,45,64,62,62,57,55,49,65,62,53,55,59,73,63,63,67,77,74,74,79,66,66,67,60,59,48,44,57,61,59,48,59,59,43,50,49,61,58,64,74,49,64,55,55,62,52,62,55,65,54,52,66,54,54,54,60,55,56,53,61,63,53,50,62,62,70,63,61,55,56,70,62,66,60,56,67,57,64,56,51,56,59,61,53,68,64,51,60,62,62,76,65,53,68,53,51,57,53,63,60,74,59,73,51,74,65,64,59,70,61,42,71,66,52,70,67,58,65,64,66,75,59,60,64,63,68,61,53,60,67,57,74,59,64,69,76,59,65,65,75,86,78,70,76,84,78,69,66,83,90,96,128,143,101,81,76,72,85,79,83,67,79,64,80,72,74,70,81,79,82,95,119,148,94,80,71,82,76,76,76,82,73,66,80,79,101,71,83,75,66,72,76,68,88,81,66,67,68,81,67,69,78,68,64,81,83,78,74,67,64,82,72,79,65,84,76,87,75,76,87,82,101,128,200,143,105,101,87,77,88,76,79,95,76,67,82,82,70,82,72,88,66,68,71,84,74,100,83,69,91,81,77,84,75,86,71,92,94,77,93,93,110,112,103,106,110,125,123,169,178,166,129,117,116,127,136,143,127,114,101,85,93,98,87,97,94,95,85,94,93,85,93,74,75,81,105,95,105,105,103,86,85,102,75,72,89,96,92,86,85,90,100,91,88,98,83,84,93,97,71,73,88,78,84,90,97,89,88,86,89,89,89,89,96,98,103,94,91,98,108,80,100,88,101,104,91,96,98,102,96,100,82,87,107,94,105,96,105,106,110,96,88,98,94,99,85,99,93,91,96,94,103,100,106,86,105,100,90,102,100,100,121,121,107,97,94,116,105,100,98,102,107,110,100,107,101,114,106,109,115,116,108,114,112,110,112,108,107,109,109,105,102,107,111,109,108,112,108,102,105,110,108,95,107,99,106,110,115,107,100,108,107,107,113,93,113,124,119,109,101,109,120,112,108,111,116,97,106,114,116,125,109,103,114,122,122,106,124,105,120,105,118,114,105,118,115,112,116,112,132,116,106,115,113,108,136,131,123,108,120,131,121,126,124,137,130,129,119,126,136,135,135,130,130,124,126,122,131,120,140,120,138,128,127,131,146,133,141,124,123,137,131,127,128,123,135,123,121,128,141,136,136,119,114,122,127,135,138,126,125,118,121,129,133,137,131,118,142,103,113,123,110,135,128,133,121,131,118,115,122,124,114,120,136,129,123,119,119,118,114,116,116,120,108,113,114,129,116,110,109,118,103,121,120,108,108,119,130,104,107,95,110,96,117,109,111,111,112,113,112,101,110,107,96,99,105,101,99,103,97,96,108,107,103,103,112,103,115,108,107,102,108,103,102,98,102,98,105,107,95,103,112,112,105,99,105,112,99,99,106,101,97,102,99,99,102,95,109,96,100,99,103,117,100,106,110,96,97,105,98,100,90,94,107,101,92,96,106,105,76,93,104,101,104,95,95,93,81,104,96,101,98,91,103,85,100,87,87,98,100,100,95,114,96,105,106,81,96,104,103,89,108,92,89,96,81,91,100,102,103,99,119,104,97,99,128,116,126,123,130,121,132,115,111,109,102,96,108,104,82,100,107,97,81,100,101,98,98,100,102,97,95,90,93,102,95,103,106,102,99,101,117,101,104,114,112,119,106,116,131,132,148,169,200,303,378,452,489,465,431,388,356,333,318,305,308,305,335,407,576,816,664,494,393,352,318,322,321,308,279,254,257,238,222,214,219,199,207,212,226,206,181,186,162,148,107,119,110,113,101,109,114,105,107,94,91,97,101,87,108,95,103,95,94,96,89,89,105,94,79,85,99,93,84,77,95,92,73,89,98,72,80,81,98,87,81,86,92,85,86,92,86,82,87,86,93,87,88,87,81,82,90,76,90,83,80,75,82,83,76,74,84,83,73,74,77,77,77,81,80,77,75,68,69,77,77,73,74,81,75,68,73,75,76,77,75,77,71,77,71,71,75,75,67]},"metadata":{"x":[1],"y":[1],"user_name":["Win Cowger"],"spectrum_type":["Raman"],"spectrum_identity":["HDPE"],"organization":["Horiba Scientific"],"license":["CC BY-NC"],"session_id":["5728ddde4f649fd71f6f487fc5ad8d80/dc85257201307a131e71d9ec24aaccbf"],"file_id":["cb06ce2846b119d932fb6696479a445b"]}} diff --git a/inst/extdata/raman_hdpe.rds b/inst/extdata/raman_hdpe.rds new file mode 100644 index 00000000..21ae7a84 Binary files /dev/null and b/inst/extdata/raman_hdpe.rds differ diff --git a/inst/extdata/raman_hdpe.yml b/inst/extdata/raman_hdpe.yml new file mode 100644 index 00000000..5d04a7d2 --- /dev/null +++ b/inst/extdata/raman_hdpe.yml @@ -0,0 +1,1941 @@ +wavenumber: +- 301.04 +- 304.632 +- 308.221 +- 311.81 +- 315.398 +- 318.983 +- 322.566 +- 326.15 +- 329.732 +- 333.311 +- 336.889 +- 340.467 +- 344.042 +- 347.618 +- 351.19 +- 354.76 +- 358.332 +- 361.9 +- 365.466 +- 369.031 +- 372.597 +- 376.16 +- 379.721 +- 383.28 +- 386.839 +- 390.396 +- 393.951 +- 397.505 +- 401.058 +- 404.61 +- 408.16 +- 411.708 +- 415.256 +- 418.801 +- 422.346 +- 425.887 +- 429.429 +- 432.97 +- 436.507 +- 440.044 +- 443.581 +- 447.116 +- 450.647 +- 454.18 +- 457.708 +- 461.238 +- 464.764 +- 468.291 +- 471.815 +- 475.339 +- 478.859 +- 482.379 +- 485.899 +- 489.416 +- 492.931 +- 496.447 +- 499.96 +- 503.471 +- 506.981 +- 510.489 +- 513.999 +- 517.503 +- 521.009 +- 524.51 +- 528.014 +- 531.513 +- 535.014 +- 538.51 +- 542.008 +- 545.502 +- 548.997 +- 552.488 +- 555.979 +- 559.469 +- 562.955 +- 566.442 +- 569.928 +- 573.411 +- 576.894 +- 580.376 +- 583.854 +- 587.331 +- 590.811 +- 594.286 +- 597.759 +- 601.231 +- 604.705 +- 608.174 +- 611.642 +- 615.109 +- 618.574 +- 622.038 +- 625.503 +- 628.966 +- 632.426 +- 635.884 +- 639.342 +- 642.798 +- 646.252 +- 649.706 +- 653.158 +- 656.608 +- 660.058 +- 663.506 +- 666.953 +- 670.398 +- 673.842 +- 677.285 +- 680.727 +- 684.165 +- 687.602 +- 691.04 +- 694.476 +- 697.911 +- 701.345 +- 704.775 +- 708.204 +- 711.634 +- 715.063 +- 718.49 +- 721.912 +- 725.336 +- 728.76 +- 732.18 +- 735.599 +- 739.018 +- 742.434 +- 745.849 +- 749.265 +- 752.675 +- 756.088 +- 759.498 +- 762.906 +- 766.313 +- 769.719 +- 773.124 +- 776.527 +- 779.929 +- 783.33 +- 786.729 +- 790.127 +- 793.524 +- 796.919 +- 800.312 +- 803.705 +- 807.097 +- 810.485 +- 813.874 +- 817.262 +- 820.647 +- 824.032 +- 827.416 +- 830.797 +- 834.179 +- 837.557 +- 840.936 +- 844.312 +- 847.688 +- 851.061 +- 854.433 +- 857.806 +- 861.175 +- 864.543 +- 867.91 +- 871.278 +- 874.642 +- 878.005 +- 881.367 +- 884.728 +- 888.087 +- 891.445 +- 894.802 +- 898.157 +- 901.511 +- 904.864 +- 908.216 +- 911.567 +- 914.914 +- 918.262 +- 921.609 +- 924.954 +- 928.296 +- 931.639 +- 934.981 +- 938.32 +- 941.659 +- 944.995 +- 948.332 +- 951.665 +- 954.999 +- 958.33 +- 961.66 +- 964.991 +- 968.318 +- 971.644 +- 974.971 +- 978.295 +- 981.617 +- 984.94 +- 988.258 +- 991.578 +- 994.896 +- 998.212 +- 1001.53 +- 1004.84 +- 1008.15 +- 1011.46 +- 1014.77 +- 1018.08 +- 1021.39 +- 1024.7 +- 1028.0 +- 1031.3 +- 1034.61 +- 1037.91 +- 1041.21 +- 1044.51 +- 1047.8 +- 1051.1 +- 1054.39 +- 1057.68 +- 1060.98 +- 1064.27 +- 1067.56 +- 1070.84 +- 1074.13 +- 1077.42 +- 1080.7 +- 1083.98 +- 1087.27 +- 1090.55 +- 1093.83 +- 1097.1 +- 1100.38 +- 1103.65 +- 1106.93 +- 1110.2 +- 1113.47 +- 1116.74 +- 1120.01 +- 1123.28 +- 1126.55 +- 1129.81 +- 1133.08 +- 1136.34 +- 1139.6 +- 1142.86 +- 1146.12 +- 1149.37 +- 1152.63 +- 1155.89 +- 1159.14 +- 1162.39 +- 1165.64 +- 1168.89 +- 1172.14 +- 1175.39 +- 1178.64 +- 1181.88 +- 1185.12 +- 1188.37 +- 1191.61 +- 1194.85 +- 1198.09 +- 1201.32 +- 1204.56 +- 1207.79 +- 1211.03 +- 1214.26 +- 1217.49 +- 1220.72 +- 1223.95 +- 1227.18 +- 1230.4 +- 1233.63 +- 1236.85 +- 1240.07 +- 1243.3 +- 1246.52 +- 1249.74 +- 1252.95 +- 1256.17 +- 1259.38 +- 1262.6 +- 1265.81 +- 1269.02 +- 1272.23 +- 1275.44 +- 1278.65 +- 1281.85 +- 1285.06 +- 1288.26 +- 1291.46 +- 1294.67 +- 1297.87 +- 1301.07 +- 1304.26 +- 1307.46 +- 1310.66 +- 1313.85 +- 1317.04 +- 1320.23 +- 1323.42 +- 1326.61 +- 1329.8 +- 1332.99 +- 1336.17 +- 1339.36 +- 1342.54 +- 1345.72 +- 1348.9 +- 1352.08 +- 1355.26 +- 1358.44 +- 1361.61 +- 1364.79 +- 1367.96 +- 1371.13 +- 1374.3 +- 1377.47 +- 1380.64 +- 1383.81 +- 1386.97 +- 1390.14 +- 1393.3 +- 1396.47 +- 1399.63 +- 1402.79 +- 1405.95 +- 1409.1 +- 1412.26 +- 1415.41 +- 1418.57 +- 1421.72 +- 1424.87 +- 1428.02 +- 1431.17 +- 1434.32 +- 1437.47 +- 1440.61 +- 1443.76 +- 1446.9 +- 1450.04 +- 1453.18 +- 1456.32 +- 1459.46 +- 1462.59 +- 1465.73 +- 1468.87 +- 1472.0 +- 1475.13 +- 1478.26 +- 1481.4 +- 1484.52 +- 1487.65 +- 1490.78 +- 1493.9 +- 1497.03 +- 1500.15 +- 1503.27 +- 1506.39 +- 1509.51 +- 1512.63 +- 1515.74 +- 1518.86 +- 1521.97 +- 1525.09 +- 1528.2 +- 1531.31 +- 1534.42 +- 1537.53 +- 1540.64 +- 1543.74 +- 1546.85 +- 1549.95 +- 1553.05 +- 1556.16 +- 1559.26 +- 1562.36 +- 1565.45 +- 1568.55 +- 1571.65 +- 1574.74 +- 1577.83 +- 1580.92 +- 1584.02 +- 1587.11 +- 1590.19 +- 1593.28 +- 1596.37 +- 1599.45 +- 1602.54 +- 1605.62 +- 1608.7 +- 1611.78 +- 1614.86 +- 1617.94 +- 1621.02 +- 1624.09 +- 1627.17 +- 1630.24 +- 1633.31 +- 1636.38 +- 1639.45 +- 1642.52 +- 1645.59 +- 1648.65 +- 1651.72 +- 1654.78 +- 1657.85 +- 1660.91 +- 1663.97 +- 1667.03 +- 1670.09 +- 1673.15 +- 1676.2 +- 1679.26 +- 1682.31 +- 1685.36 +- 1688.41 +- 1691.46 +- 1694.51 +- 1697.56 +- 1700.61 +- 1703.65 +- 1706.7 +- 1709.74 +- 1712.78 +- 1715.83 +- 1718.86 +- 1721.9 +- 1724.94 +- 1727.98 +- 1731.01 +- 1734.05 +- 1737.08 +- 1740.11 +- 1743.14 +- 1746.17 +- 1749.2 +- 1752.23 +- 1755.25 +- 1758.28 +- 1761.3 +- 1764.33 +- 1767.35 +- 1770.37 +- 1773.39 +- 1776.41 +- 1779.42 +- 1782.44 +- 1785.46 +- 1788.47 +- 1791.48 +- 1794.49 +- 1797.5 +- 1800.51 +- 1803.52 +- 1806.53 +- 1809.53 +- 1812.54 +- 1815.54 +- 1818.54 +- 1821.54 +- 1824.55 +- 1827.55 +- 1830.54 +- 1833.54 +- 1836.54 +- 1839.53 +- 1842.52 +- 1845.51 +- 1848.51 +- 1851.5 +- 1854.49 +- 1857.48 +- 1860.46 +- 1863.45 +- 1866.43 +- 1869.41 +- 1872.4 +- 1875.38 +- 1878.36 +- 1881.34 +- 1884.32 +- 1887.29 +- 1890.27 +- 1893.24 +- 1896.22 +- 1899.19 +- 1902.16 +- 1905.13 +- 1908.1 +- 1911.07 +- 1914.03 +- 1917.0 +- 1919.96 +- 1922.93 +- 1925.89 +- 1928.85 +- 1931.81 +- 1934.77 +- 1937.43 +- 1940.4 +- 1943.36 +- 1946.33 +- 1949.3 +- 1952.27 +- 1955.23 +- 1958.19 +- 1961.16 +- 1964.12 +- 1967.08 +- 1970.04 +- 1972.99 +- 1975.95 +- 1978.91 +- 1981.86 +- 1984.82 +- 1987.77 +- 1990.72 +- 1993.67 +- 1996.62 +- 1999.57 +- 2002.52 +- 2005.46 +- 2008.41 +- 2011.35 +- 2014.29 +- 2017.24 +- 2020.18 +- 2023.12 +- 2026.05 +- 2028.99 +- 2031.93 +- 2034.87 +- 2037.8 +- 2040.73 +- 2043.66 +- 2046.6 +- 2049.52 +- 2052.45 +- 2055.38 +- 2058.31 +- 2061.23 +- 2064.16 +- 2067.08 +- 2070.0 +- 2072.93 +- 2075.85 +- 2078.77 +- 2081.68 +- 2084.6 +- 2087.52 +- 2090.43 +- 2093.34 +- 2096.26 +- 2099.17 +- 2102.08 +- 2104.99 +- 2107.9 +- 2110.81 +- 2113.71 +- 2116.62 +- 2119.52 +- 2122.43 +- 2125.33 +- 2128.23 +- 2131.13 +- 2134.03 +- 2136.93 +- 2139.82 +- 2142.72 +- 2145.61 +- 2148.51 +- 2151.4 +- 2154.29 +- 2157.18 +- 2160.07 +- 2162.96 +- 2165.85 +- 2168.73 +- 2171.62 +- 2174.5 +- 2177.39 +- 2180.27 +- 2183.15 +- 2186.03 +- 2188.91 +- 2191.79 +- 2194.67 +- 2197.54 +- 2200.42 +- 2203.29 +- 2206.16 +- 2209.03 +- 2211.9 +- 2214.77 +- 2217.64 +- 2220.51 +- 2223.38 +- 2226.24 +- 2229.11 +- 2231.97 +- 2234.83 +- 2237.69 +- 2240.55 +- 2243.41 +- 2246.27 +- 2249.13 +- 2251.98 +- 2254.84 +- 2257.69 +- 2260.54 +- 2263.39 +- 2266.25 +- 2269.1 +- 2271.94 +- 2274.79 +- 2277.64 +- 2280.49 +- 2283.33 +- 2286.17 +- 2289.02 +- 2291.86 +- 2294.7 +- 2297.54 +- 2300.38 +- 2303.21 +- 2306.05 +- 2308.89 +- 2311.72 +- 2314.55 +- 2317.39 +- 2320.22 +- 2323.05 +- 2325.88 +- 2328.7 +- 2331.53 +- 2334.36 +- 2337.18 +- 2340.01 +- 2342.83 +- 2345.65 +- 2348.47 +- 2351.29 +- 2354.11 +- 2356.93 +- 2359.75 +- 2362.56 +- 2365.38 +- 2368.19 +- 2371.0 +- 2373.82 +- 2376.63 +- 2379.44 +- 2382.25 +- 2385.05 +- 2387.86 +- 2390.67 +- 2393.47 +- 2396.28 +- 2399.08 +- 2401.88 +- 2404.68 +- 2407.48 +- 2410.28 +- 2413.08 +- 2415.87 +- 2418.67 +- 2421.46 +- 2424.26 +- 2427.05 +- 2429.84 +- 2432.63 +- 2435.42 +- 2438.21 +- 2441.0 +- 2443.79 +- 2446.57 +- 2449.36 +- 2452.14 +- 2454.92 +- 2457.7 +- 2460.48 +- 2463.26 +- 2466.04 +- 2468.82 +- 2471.6 +- 2474.37 +- 2477.15 +- 2479.92 +- 2482.69 +- 2485.47 +- 2488.24 +- 2491.01 +- 2493.77 +- 2496.54 +- 2499.31 +- 2502.07 +- 2504.84 +- 2507.6 +- 2510.37 +- 2513.13 +- 2515.89 +- 2518.65 +- 2521.41 +- 2524.16 +- 2526.92 +- 2529.68 +- 2532.43 +- 2535.19 +- 2537.94 +- 2540.69 +- 2543.44 +- 2546.19 +- 2548.94 +- 2551.69 +- 2554.44 +- 2557.18 +- 2559.93 +- 2562.67 +- 2565.42 +- 2568.16 +- 2570.9 +- 2573.64 +- 2576.38 +- 2579.11 +- 2581.85 +- 2584.59 +- 2587.32 +- 2590.06 +- 2592.79 +- 2595.52 +- 2598.25 +- 2600.98 +- 2603.72 +- 2606.44 +- 2609.17 +- 2611.9 +- 2614.62 +- 2617.35 +- 2620.07 +- 2622.79 +- 2625.51 +- 2628.23 +- 2630.95 +- 2633.67 +- 2636.39 +- 2639.11 +- 2641.82 +- 2644.54 +- 2647.25 +- 2649.96 +- 2652.68 +- 2655.39 +- 2658.1 +- 2660.81 +- 2663.51 +- 2666.22 +- 2668.93 +- 2671.63 +- 2674.34 +- 2677.04 +- 2679.74 +- 2682.44 +- 2685.14 +- 2687.84 +- 2690.54 +- 2693.24 +- 2695.94 +- 2698.63 +- 2701.33 +- 2704.02 +- 2706.71 +- 2709.41 +- 2712.1 +- 2714.79 +- 2717.47 +- 2720.16 +- 2722.85 +- 2725.53 +- 2728.22 +- 2730.9 +- 2733.59 +- 2736.27 +- 2738.95 +- 2741.63 +- 2744.31 +- 2746.99 +- 2749.67 +- 2752.34 +- 2755.02 +- 2757.69 +- 2760.37 +- 2763.04 +- 2765.71 +- 2768.38 +- 2771.05 +- 2773.72 +- 2776.39 +- 2779.06 +- 2781.72 +- 2784.39 +- 2787.05 +- 2789.71 +- 2792.38 +- 2795.04 +- 2797.7 +- 2800.36 +- 2803.02 +- 2805.68 +- 2808.33 +- 2810.99 +- 2813.64 +- 2816.3 +- 2818.95 +- 2821.6 +- 2824.25 +- 2826.9 +- 2829.55 +- 2832.2 +- 2834.85 +- 2837.49 +- 2840.14 +- 2842.78 +- 2845.43 +- 2848.07 +- 2850.71 +- 2853.35 +- 2855.99 +- 2858.63 +- 2861.27 +- 2863.91 +- 2866.54 +- 2869.18 +- 2871.81 +- 2874.45 +- 2877.08 +- 2879.71 +- 2882.34 +- 2884.97 +- 2887.6 +- 2890.23 +- 2892.86 +- 2895.48 +- 2898.11 +- 2900.73 +- 2903.35 +- 2905.97 +- 2908.6 +- 2911.22 +- 2913.84 +- 2916.46 +- 2919.07 +- 2921.69 +- 2924.31 +- 2926.92 +- 2929.54 +- 2932.15 +- 2934.76 +- 2937.37 +- 2939.98 +- 2942.59 +- 2945.2 +- 2947.81 +- 2950.42 +- 2953.02 +- 2955.63 +- 2958.23 +- 2960.83 +- 2963.44 +- 2966.04 +- 2968.64 +- 2971.24 +- 2973.83 +- 2976.43 +- 2979.03 +- 2981.63 +- 2984.22 +- 2986.81 +- 2989.41 +- 2992.0 +- 2994.59 +- 2997.18 +- 2999.77 +- 3002.36 +- 3004.95 +- 3007.53 +- 3010.12 +- 3012.7 +- 3015.29 +- 3017.87 +- 3020.45 +- 3023.04 +- 3025.62 +- 3028.2 +- 3030.77 +- 3033.35 +- 3035.93 +- 3038.5 +- 3041.08 +- 3043.65 +- 3046.23 +- 3048.8 +- 3051.37 +- 3053.94 +- 3056.51 +- 3059.08 +- 3061.65 +- 3064.21 +- 3066.78 +- 3069.35 +- 3071.91 +- 3074.47 +- 3077.04 +- 3079.6 +- 3082.16 +- 3084.72 +- 3087.28 +- 3089.83 +- 3092.39 +- 3094.95 +- 3097.5 +- 3100.06 +- 3102.61 +- 3105.16 +- 3107.72 +- 3110.27 +- 3112.82 +- 3115.37 +- 3117.91 +- 3120.46 +- 3123.01 +- 3125.56 +- 3128.1 +- 3130.64 +- 3133.19 +- 3135.73 +- 3138.27 +- 3140.81 +- 3143.35 +- 3145.89 +- 3148.43 +- 3150.96 +- 3153.5 +- 3156.03 +- 3158.57 +- 3161.1 +- 3163.64 +- 3166.17 +- 3168.7 +- 3171.23 +- 3173.76 +- 3175.3 +- 3177.84 +- 3180.38 +- 3182.91 +- 3185.45 +- 3187.99 +- 3190.52 +- 3193.06 +- 3195.59 +- 3198.12 +spectra: + intensity: + - 26 + - 50 + - 48 + - 45 + - 46 + - 42 + - 45 + - 44 + - 48 + - 46 + - 48 + - 44 + - 49 + - 52 + - 48 + - 53 + - 52 + - 46 + - 41 + - 50 + - 62 + - 57 + - 52 + - 45 + - 55 + - 42 + - 52 + - 44 + - 62 + - 57 + - 50 + - 59 + - 48 + - 47 + - 59 + - 58 + - 58 + - 74 + - 69 + - 78 + - 67 + - 73 + - 59 + - 72 + - 65 + - 53 + - 65 + - 63 + - 61 + - 56 + - 47 + - 52 + - 53 + - 58 + - 58 + - 43 + - 54 + - 55 + - 50 + - 47 + - 55 + - 58 + - 50 + - 47 + - 44 + - 55 + - 56 + - 58 + - 54 + - 52 + - 45 + - 64 + - 62 + - 62 + - 57 + - 55 + - 49 + - 65 + - 62 + - 53 + - 55 + - 59 + - 73 + - 63 + - 63 + - 67 + - 77 + - 74 + - 74 + - 79 + - 66 + - 66 + - 67 + - 60 + - 59 + - 48 + - 44 + - 57 + - 61 + - 59 + - 48 + - 59 + - 59 + - 43 + - 50 + - 49 + - 61 + - 58 + - 64 + - 74 + - 49 + - 64 + - 55 + - 55 + - 62 + - 52 + - 62 + - 55 + - 65 + - 54 + - 52 + - 66 + - 54 + - 54 + - 54 + - 60 + - 55 + - 56 + - 53 + - 61 + - 63 + - 53 + - 50 + - 62 + - 62 + - 70 + - 63 + - 61 + - 55 + - 56 + - 70 + - 62 + - 66 + - 60 + - 56 + - 67 + - 57 + - 64 + - 56 + - 51 + - 56 + - 59 + - 61 + - 53 + - 68 + - 64 + - 51 + - 60 + - 62 + - 62 + - 76 + - 65 + - 53 + - 68 + - 53 + - 51 + - 57 + - 53 + - 63 + - 60 + - 74 + - 59 + - 73 + - 51 + - 74 + - 65 + - 64 + - 59 + - 70 + - 61 + - 42 + - 71 + - 66 + - 52 + - 70 + - 67 + - 58 + - 65 + - 64 + - 66 + - 75 + - 59 + - 60 + - 64 + - 63 + - 68 + - 61 + - 53 + - 60 + - 67 + - 57 + - 74 + - 59 + - 64 + - 69 + - 76 + - 59 + - 65 + - 65 + - 75 + - 86 + - 78 + - 70 + - 76 + - 84 + - 78 + - 69 + - 66 + - 83 + - 90 + - 96 + - 128 + - 143 + - 101 + - 81 + - 76 + - 72 + - 85 + - 79 + - 83 + - 67 + - 79 + - 64 + - 80 + - 72 + - 74 + - 70 + - 81 + - 79 + - 82 + - 95 + - 119 + - 148 + - 94 + - 80 + - 71 + - 82 + - 76 + - 76 + - 76 + - 82 + - 73 + - 66 + - 80 + - 79 + - 101 + - 71 + - 83 + - 75 + - 66 + - 72 + - 76 + - 68 + - 88 + - 81 + - 66 + - 67 + - 68 + - 81 + - 67 + - 69 + - 78 + - 68 + - 64 + - 81 + - 83 + - 78 + - 74 + - 67 + - 64 + - 82 + - 72 + - 79 + - 65 + - 84 + - 76 + - 87 + - 75 + - 76 + - 87 + - 82 + - 101 + - 128 + - 200 + - 143 + - 105 + - 101 + - 87 + - 77 + - 88 + - 76 + - 79 + - 95 + - 76 + - 67 + - 82 + - 82 + - 70 + - 82 + - 72 + - 88 + - 66 + - 68 + - 71 + - 84 + - 74 + - 100 + - 83 + - 69 + - 91 + - 81 + - 77 + - 84 + - 75 + - 86 + - 71 + - 92 + - 94 + - 77 + - 93 + - 93 + - 110 + - 112 + - 103 + - 106 + - 110 + - 125 + - 123 + - 169 + - 178 + - 166 + - 129 + - 117 + - 116 + - 127 + - 136 + - 143 + - 127 + - 114 + - 101 + - 85 + - 93 + - 98 + - 87 + - 97 + - 94 + - 95 + - 85 + - 94 + - 93 + - 85 + - 93 + - 74 + - 75 + - 81 + - 105 + - 95 + - 105 + - 105 + - 103 + - 86 + - 85 + - 102 + - 75 + - 72 + - 89 + - 96 + - 92 + - 86 + - 85 + - 90 + - 100 + - 91 + - 88 + - 98 + - 83 + - 84 + - 93 + - 97 + - 71 + - 73 + - 88 + - 78 + - 84 + - 90 + - 97 + - 89 + - 88 + - 86 + - 89 + - 89 + - 89 + - 89 + - 96 + - 98 + - 103 + - 94 + - 91 + - 98 + - 108 + - 80 + - 100 + - 88 + - 101 + - 104 + - 91 + - 96 + - 98 + - 102 + - 96 + - 100 + - 82 + - 87 + - 107 + - 94 + - 105 + - 96 + - 105 + - 106 + - 110 + - 96 + - 88 + - 98 + - 94 + - 99 + - 85 + - 99 + - 93 + - 91 + - 96 + - 94 + - 103 + - 100 + - 106 + - 86 + - 105 + - 100 + - 90 + - 102 + - 100 + - 100 + - 121 + - 121 + - 107 + - 97 + - 94 + - 116 + - 105 + - 100 + - 98 + - 102 + - 107 + - 110 + - 100 + - 107 + - 101 + - 114 + - 106 + - 109 + - 115 + - 116 + - 108 + - 114 + - 112 + - 110 + - 112 + - 108 + - 107 + - 109 + - 109 + - 105 + - 102 + - 107 + - 111 + - 109 + - 108 + - 112 + - 108 + - 102 + - 105 + - 110 + - 108 + - 95 + - 107 + - 99 + - 106 + - 110 + - 115 + - 107 + - 100 + - 108 + - 107 + - 107 + - 113 + - 93 + - 113 + - 124 + - 119 + - 109 + - 101 + - 109 + - 120 + - 112 + - 108 + - 111 + - 116 + - 97 + - 106 + - 114 + - 116 + - 125 + - 109 + - 103 + - 114 + - 122 + - 122 + - 106 + - 124 + - 105 + - 120 + - 105 + - 118 + - 114 + - 105 + - 118 + - 115 + - 112 + - 116 + - 112 + - 132 + - 116 + - 106 + - 115 + - 113 + - 108 + - 136 + - 131 + - 123 + - 108 + - 120 + - 131 + - 121 + - 126 + - 124 + - 137 + - 130 + - 129 + - 119 + - 126 + - 136 + - 135 + - 135 + - 130 + - 130 + - 124 + - 126 + - 122 + - 131 + - 120 + - 140 + - 120 + - 138 + - 128 + - 127 + - 131 + - 146 + - 133 + - 141 + - 124 + - 123 + - 137 + - 131 + - 127 + - 128 + - 123 + - 135 + - 123 + - 121 + - 128 + - 141 + - 136 + - 136 + - 119 + - 114 + - 122 + - 127 + - 135 + - 138 + - 126 + - 125 + - 118 + - 121 + - 129 + - 133 + - 137 + - 131 + - 118 + - 142 + - 103 + - 113 + - 123 + - 110 + - 135 + - 128 + - 133 + - 121 + - 131 + - 118 + - 115 + - 122 + - 124 + - 114 + - 120 + - 136 + - 129 + - 123 + - 119 + - 119 + - 118 + - 114 + - 116 + - 116 + - 120 + - 108 + - 113 + - 114 + - 129 + - 116 + - 110 + - 109 + - 118 + - 103 + - 121 + - 120 + - 108 + - 108 + - 119 + - 130 + - 104 + - 107 + - 95 + - 110 + - 96 + - 117 + - 109 + - 111 + - 111 + - 112 + - 113 + - 112 + - 101 + - 110 + - 107 + - 96 + - 99 + - 105 + - 101 + - 99 + - 103 + - 97 + - 96 + - 108 + - 107 + - 103 + - 103 + - 112 + - 103 + - 115 + - 108 + - 107 + - 102 + - 108 + - 103 + - 102 + - 98 + - 102 + - 98 + - 105 + - 107 + - 95 + - 103 + - 112 + - 112 + - 105 + - 99 + - 105 + - 112 + - 99 + - 99 + - 106 + - 101 + - 97 + - 102 + - 99 + - 99 + - 102 + - 95 + - 109 + - 96 + - 100 + - 99 + - 103 + - 117 + - 100 + - 106 + - 110 + - 96 + - 97 + - 105 + - 98 + - 100 + - 90 + - 94 + - 107 + - 101 + - 92 + - 96 + - 106 + - 105 + - 76 + - 93 + - 104 + - 101 + - 104 + - 95 + - 95 + - 93 + - 81 + - 104 + - 96 + - 101 + - 98 + - 91 + - 103 + - 85 + - 100 + - 87 + - 87 + - 98 + - 100 + - 100 + - 95 + - 114 + - 96 + - 105 + - 106 + - 81 + - 96 + - 104 + - 103 + - 89 + - 108 + - 92 + - 89 + - 96 + - 81 + - 91 + - 100 + - 102 + - 103 + - 99 + - 119 + - 104 + - 97 + - 99 + - 128 + - 116 + - 126 + - 123 + - 130 + - 121 + - 132 + - 115 + - 111 + - 109 + - 102 + - 96 + - 108 + - 104 + - 82 + - 100 + - 107 + - 97 + - 81 + - 100 + - 101 + - 98 + - 98 + - 100 + - 102 + - 97 + - 95 + - 90 + - 93 + - 102 + - 95 + - 103 + - 106 + - 102 + - 99 + - 101 + - 117 + - 101 + - 104 + - 114 + - 112 + - 119 + - 106 + - 116 + - 131 + - 132 + - 148 + - 169 + - 200 + - 303 + - 378 + - 452 + - 489 + - 465 + - 431 + - 388 + - 356 + - 333 + - 318 + - 305 + - 308 + - 305 + - 335 + - 407 + - 576 + - 816 + - 664 + - 494 + - 393 + - 352 + - 318 + - 322 + - 321 + - 308 + - 279 + - 254 + - 257 + - 238 + - 222 + - 214 + - 219 + - 199 + - 207 + - 212 + - 226 + - 206 + - 181 + - 186 + - 162 + - 148 + - 107 + - 119 + - 110 + - 113 + - 101 + - 109 + - 114 + - 105 + - 107 + - 94 + - 91 + - 97 + - 101 + - 87 + - 108 + - 95 + - 103 + - 95 + - 94 + - 96 + - 89 + - 89 + - 105 + - 94 + - 79 + - 85 + - 99 + - 93 + - 84 + - 77 + - 95 + - 92 + - 73 + - 89 + - 98 + - 72 + - 80 + - 81 + - 98 + - 87 + - 81 + - 86 + - 92 + - 85 + - 86 + - 92 + - 86 + - 82 + - 87 + - 86 + - 93 + - 87 + - 88 + - 87 + - 81 + - 82 + - 90 + - 76 + - 90 + - 83 + - 80 + - 75 + - 82 + - 83 + - 76 + - 74 + - 84 + - 83 + - 73 + - 74 + - 77 + - 77 + - 77 + - 81 + - 80 + - 77 + - 75 + - 68 + - 69 + - 77 + - 77 + - 73 + - 74 + - 81 + - 75 + - 68 + - 73 + - 75 + - 76 + - 77 + - 75 + - 77 + - 71 + - 77 + - 71 + - 71 + - 75 + - 75 + - 67 +metadata: + x: 1 + 'y': 1 + user_name: Win Cowger + spectrum_type: Raman + spectrum_identity: HDPE + organization: Horiba Scientific + license: CC BY-NC + session_id: 5728ddde4f649fd71f6f487fc5ad8d80/dc85257201307a131e71d9ec24aaccbf + file_id: cb06ce2846b119d932fb6696479a445b diff --git a/inst/extdata/testdata_zipped.zip b/inst/extdata/testdata_zipped.zip new file mode 100644 index 00000000..d91314f2 Binary files /dev/null and b/inst/extdata/testdata_zipped.zip differ diff --git a/inst/shiny/.gitignore b/inst/shiny/.gitignore deleted file mode 100644 index 407ab6a4..00000000 --- a/inst/shiny/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -raman_*.rds -ftir_*.rds -test_*.rds -augmented_*.rds -augmented_*.rds -subset_*.RDS -test_*.RDS -google-analytics.js -droptoken.rds -rsconnect/ -.httr-oauth -.db_url -loggit.log diff --git a/inst/shiny/config.yml b/inst/shiny/config.yml deleted file mode 100644 index 501dadb0..00000000 --- a/inst/shiny/config.yml +++ /dev/null @@ -1,14 +0,0 @@ -default: - library_path: 'system' - share: 'system' - log: TRUE - -run_app: - library_path: !expr shiny::getShinyOption("library_path") - share: !expr file.path(shiny::getShinyOption("library_path"), "user_spectra") - log: !expr shiny::getShinyOption("log") - -shinyapps: - library_path: 'data/library' - share: 'dropbox' - log: TRUE diff --git a/inst/shiny/data/namekey.RData b/inst/shiny/data/namekey.RData deleted file mode 100644 index c2f0b315..00000000 Binary files a/inst/shiny/data/namekey.RData and /dev/null differ diff --git a/inst/shiny/server.R b/inst/shiny/server.R deleted file mode 100644 index 6ecdd87a..00000000 --- a/inst/shiny/server.R +++ /dev/null @@ -1,643 +0,0 @@ -#' Shiny app server function -#' -#' @param input provided by shiny -#' @param output provided by shiny -#' -#' -# Check for Auth Tokens and setup, you can change these to test the triggering -# of functions without removing the files. -droptoken <- file.exists("data/droptoken.rds") -db <- file.exists(".db_url") #reminder, this will break if you login to a new wifi network even with the token. -translate <- file.exists("www/googletranslate.html") - -# Libraries ---- -library(shiny) -library(shinyjs) -library(dplyr) -library(plotly) -# library(viridis) -library(data.table) -library(DT) -library(digest) -library(curl) -library(config) -library(mongolite) -library(loggit) -if(droptoken) library(rdrop2) - -#devtools::install_github("wincowgerDEV/OpenSpecy") -library(OpenSpecy) - -#library(future) -#library(bslib) - -# Global config ---- -conf <- config::get() #Add config = "shinyapps" for ec2 server - -# Logging ---- -if(conf$log) { - if(db) { - database <- mongo(url = readLines(".db_url")) - } else { - set_logfile(file.path(tempdir(), "OpenSpecy.log")) - } -} - -# Load all data ---- -load_data <- function() { - - testdata <- raman_hdpe - - tweets <- c("https://twitter.com/EnviroMichaela/status/1471622640183959555", - #"https://twitter.com/OpenSpecy/status/1472361269093023744", - "https://twitter.com/DSemensatto/status/1461038613903380484", - "https://twitter.com/SETAC_plastics/status/1460738878101356544", - "https://twitter.com/AliciaMateos_/status/1460197329760313344", - "https://twitter.com/Irreverent_KUP/status/1454418069036568578", - "https://twitter.com/PeterPuskic/status/1454267818166210561", - "https://twitter.com/JannesJegminat/status/1427257468384681985", - "https://twitter.com/pnwmicroplastic/status/1415730821730734080", - #"https://twitter.com/OpenSpecy/status/1408391168745000961", - "https://twitter.com/ToMExApp/status/1399859256615079936", - "https://twitter.com/kat_lasdin/status/1399576094622175241", - "https://twitter.com/an_chem/status/1397621113421803521", - "https://twitter.com/WarrierAnish/status/1395245636967014401", - "https://twitter.com/EnviroMichaela/status/1395199312645300233", - "https://twitter.com/SocAppSpec/status/1392883693027430400", - #"https://twitter.com/zsteinmetz_/status/1387677422028480512", - #"https://twitter.com/OpenSpecy/status/1382820319635775488", - #"https://twitter.com/zsteinmetz_/status/1377222029250822146", - #"https://twitter.com/OpenSpecy/status/1318214558549372928", - "https://twitter.com/YokotaLimnoLab/status/1311069417892184065") %>% - sample(2) - - goals <- tibble( - Status = c("Revolutionizing", - "Thriving", - "Maintaining", - "Supporting", - "Saving"), - Description = c("A paid team that is pushing Open Specy closer to the ultimate goal of 100% accurate spectral identification and deep spectral diagnostics with a single click", - "A single paid staff person working to update and build the community and the tool", - "Maintenance costs and minor ad-hoc updates and bug fixes", - "Keeping the app online and essential maintenance", - "Long term storage only"), - 'Annual Need' = c(">100,000$", - "10,000–100,000$", - "1,000–10,000$", - "100–1,000$", - "<100$") - ) - # Check if spectral library is present and load - test_lib <- class(tryCatch(check_lib(path = conf$library_path), - warning = function(w) {w})) - - if(any(test_lib == "warning")) get_lib(path = conf$library_path) - - spec_lib <- load_lib(path = conf$library_path) - - if(droptoken) { - drop_auth(rdstoken = "data/droptoken.rds") - } - - # Name keys for human readable column names - load("data/namekey.RData") - - # Inject variables into the parent environment - invisible(list2env(as.list(environment()), parent.frame())) -} - -# This is the actual server functions, all functions before this point are not -# reactive -server <- shinyServer(function(input, output, session) { - #For theming - #bs_themer() - session_id <- digest(runif(10)) - - # Loading overlay - load_data() - hide(id = "loading_overlay", anim = TRUE, animType = "fade") - show("app_content") - -# For desktop version of the app. -# if (!interactive()) { -# session$onSessionEnded(function() { -# stopApp() -# q("no") -# }) -# } - - #brks <- seq(5, 320000, 1000) - clrs <- colorRampPalette(c("white", "#6baed6"))(5 + 1) - - output$event_goals <- DT::renderDataTable({ - datatable(goals, - options = list( - dom = "t", - ordering = FALSE, - paging = FALSE, - searching = FALSE - #sDom = '<"top">lrt<"bottom">ip', - - ), - caption = "Progress (current status selected)", - style = "bootstrap", - class = 'row-border', - escape = FALSE, - rownames = FALSE, - #formatStyle(c("Annual Need"), backgroundColor = styleColorBar(color = clrs)), - selection = list(mode = "single", selected = c(2))) - }) - - #Reading Data and Startup ---- - # Sharing ID - id <- reactive({ - if (!is.null(input$fingerprint)) { - paste(input$fingerprint, session_id, sep = "/") - } else { - paste(digest(Sys.info()), digest(sessionInfo()), sep = "/") - } - }) - - # Save the metadata and data submitted upon pressing the button - observeEvent(input$submit, { - if (input$share_decision & !is.null(data()) & curl::has_internet()) { - withProgress(message = "Sharing Metadata", - value = 3/3, { - sout <- tryCatch(share_spec( - data = preprocessed_data(), - metadata = sapply(names(namekey)[c(1:24,32)], function(x) input[[x]]), - share = conf$share, - id = id()), - warning = function(w) {w}, error = function(e) {e}) - - if (inherits(sout, "simpleWarning") | inherits(sout, "simpleError")) - mess <- sout$message - - if (is.null(sout)) { - show_alert( - title = "Thank you for sharing your data!", - text = "Your data will soon be available at https://osf.io/stmv4/", - type = "success" - ) - } else { - show_alert( - title = "Something went wrong :-(", - text = paste0("All mandatory data added? R says: '", mess, "'. ", - "Try again."), - type = "warning" - ) - } - }) - } - }) - - # Read in data when uploaded based on the file type - preprocessed_data <- reactive({ - req(input$file1) - file <- input$file1 - filename <- as.character(file$datapath) - - if (!grepl("(\\.csv$)|(\\.asp$)|(\\.spa$)|(\\.spc$)|(\\.jdx$)|(\\.[0-9]$)", - ignore.case = T, filename)) { - show_alert( - title = "Data type not supported!", - text = paste0("Uploaded data type is not currently supported; please - check tooltips and 'About' tab for details."), - type = "warning") - return(NULL) - } - - if (input$share_decision & curl::has_internet()) { - share <- conf$share - progm <- "Sharing Spectrum to Community Library" - } else { - share <- NULL - progm <- "Reading Spectrum" - } - - withProgress(message = progm, value = 3/3, { - if(grepl("\\.csv$", ignore.case = T, filename)) { - rout <- tryCatch(read_text(filename, method = "fread", - share = share, - id = id()), - error = function(e) {e}) - } - else if(grepl("\\.[0-9]$", ignore.case = T, filename)) { - rout <- tryCatch(read_0(filename, share = share, id = id()), - error = function(e) {e}) - } - else { - ex <- strsplit(basename(filename), split="\\.")[[1]] - - rout <- tryCatch(do.call(paste0("read_", tolower(ex[-1])), - list(filename, share = share, id = id())), - error = function(e) {e}) - } - - if (inherits(rout, "simpleError")) { - reset("file1") - show_alert( - title = "Something went wrong :-(", - text = paste0("R says: '", rout$message, "'. ", - "If you uploaded a text/csv file, make sure that the ", - "columns are numeric and named 'wavenumber' and ", - "'intensity'."), - type = "error" - ) - return(NULL) - } else { - rout - } - }) - }) - - # Corrects spectral intensity units using the user specified correction - data <- reactive({ - req(preprocessed_data()) - adj_intens(preprocessed_data(), type = input$intensity_corr) - }) - - #Preprocess Spectra ---- - # All cleaning of the data happens here. Smoothing and Baseline removing - baseline_data <- reactive({ - req(data()) - - testdata <- data() %>% dplyr::filter(wavenumber > input$MinRange & - wavenumber < input$MaxRange) - test <- nrow(testdata) < 3 - if (test) { - data() %>% - mutate(intensity = if(input$smooth_decision) { - smooth_intens(.$wavenumber, .$intensity, p = input$smoother)$intensity - } else .$intensity) %>% - mutate(intensity = if(input$baseline_decision) { - subtr_bg(.$wavenumber, .$intensity, degree = input$baseline)$intensity - } else .$intensity) - } else { - data() %>% - dplyr::filter( - if(input$range_decision) {wavenumber > input$MinRange & - wavenumber < input$MaxRange} else { - wavenumber == wavenumber}) %>% - mutate(intensity = if(input$smooth_decision) { - smooth_intens(.$wavenumber, .$intensity, p = input$smoother)$intensity - } else .$intensity) %>% - mutate(intensity = if(input$baseline_decision & input$baseline_selection == "Polynomial") { - subtr_bg(.$wavenumber, .$intensity, degree = input$baseline)$intensity - } - else if(input$baseline_decision & input$baseline_selection == "Manual" & !is.null(trace$data)){ - make_rel(.$intensity - approx(trace$data$wavenumber, trace$data$intensity, xout = .$wavenumber, rule = 2, method = "linear", ties = mean)$y) - } else .$intensity) - } - }) - - # Create file view and preprocess view - output$MyPlot <- renderPlotly({ - plot_ly(data(), type = 'scatter', mode = 'lines') %>% - add_trace(x = ~wavenumber, y = ~intensity, name = 'Uploaded Spectrum', - line = list(color = 'rgba(240,236,19, 0.8)')) %>% - layout(yaxis = list(title = "absorbance intensity [-]"), - xaxis = list(title = "wavenumber [cm-1]", - autorange = "reversed"), - plot_bgcolor = 'rgb(17,0,73)', - paper_bgcolor = 'rgba(0,0,0,0.5)', - font = list(color = '#FFFFFF')) - }) - - output$MyPlotB <- renderPlotly({ - plot_ly(type = 'scatter', mode = 'lines', source = "B") %>% - add_trace(data = baseline_data(), x = ~wavenumber, y = ~intensity, - name = 'Processed Spectrum', - line = list(color = 'rgb(240,19,207)')) %>% - add_trace(data = data(), x = ~wavenumber, y = ~intensity, - name = 'Uploaded Spectrum', - line = list(color = 'rgba(240,236,19,0.8)')) %>% - # Dark blue rgb(63,96,130) - # https://www.rapidtables.com/web/color/RGB_Color.html https://www.color-hex.com/color-names.html - layout(yaxis = list(title = "absorbance intensity [-]"), - xaxis = list(title = "wavenumber [cm-1]", - autorange = "reversed"), - plot_bgcolor = 'rgb(17,0,73)', - paper_bgcolor = 'rgba(0,0,0,0.5)', - font = list(color = '#FFFFFF')) %>% - config(modeBarButtonsToAdd = list("drawopenpath", "eraseshape" )) - }) - -trace <- reactiveValues(data = NULL) - -observeEvent(input$go, { - pathinfo <- event_data(event = "plotly_relayout", source = "B")$shapes$path - if (is.null(pathinfo)) trace$data <- NULL - else { - nodes <- unlist(strsplit( - gsub("(L)|(M)", "_", - paste(unlist(pathinfo), collapse = "")), - "(,)|(_)")) - nodes = nodes[-1] - df <- as.data.frame(matrix(nodes, ncol = 2, byrow = T)) - names(df) <- c("wavenumber", "intensity") - trace$data <- df - } -}) - -observeEvent(input$reset, { - #js$resetClick() - #runjs("Shiny.setInputValue('plotly_selected-B', null);") - trace$data <- NULL -}) - -# output$text <- renderPrint({ -# trace$data# -# }) - - # Choose which spectrum to use - DataR <- reactive({ - if(input$Data == "uploaded") { - data() - } - else if(input$Data == "processed") { - baseline_data() - } - }) - - # Identify Spectra function ---- - # Joins their spectrum to the internal database and computes correlation. - MatchSpectra <- reactive ({ - req(input$tabs == "tab3") - input - withProgress(message = 'Analyzing Spectrum', value = 1/3, { - - incProgress(1/3, detail = "Finding Match") - - Lib <- match_spec(DataR(), - library = spec_lib, which = input$Spectra, - type = input$Library, top_n = 100) - - incProgress(1/3, detail = "Making Plot") - - }) - return(Lib) - }) - - # Create the data tables - output$event <- DT::renderDataTable({ - datatable(MatchSpectra() %>% - dplyr::rename("Material" = spectrum_identity) %>% - dplyr::select(-sample_name) %>% - dplyr::rename("Pearson's r" = rsq, - "Organization" = organization), - options = list(searchHighlight = TRUE, - sDom = '<"top">lrt<"bottom">ip', - lengthChange = FALSE, pageLength = 5), - filter = "top", caption = "Selectable Matches", - style = "bootstrap", - selection = list(mode = "single", selected = c(1))) - }) - - - output$eventmetadata <- DT::renderDataTable({ - # Default to first row if not yet clicked - id_select <- ifelse(is.null(input$event_rows_selected), - 1, - MatchSpectra()[[input$event_rows_selected, - "sample_name"]]) - # Get data from find_spec - current_meta <- find_spec(sample_name == id_select, spec_lib, - which = input$Spectra) - names(current_meta) <- namekey[names(current_meta)] - - datatable(current_meta, - escape = FALSE, rownames = F, - options = list(dom = 't', bSort = F, lengthChange = FALSE, - rownames = FALSE, info = FALSE), - style = 'bootstrap', caption = "Selection Metadata", - selection = list(mode = 'none')) - }) - - # Display matches based on table selection ---- - output$MyPlotC <- renderPlotly({ - if(!length(input$event_rows_selected)) { - plot_ly(DataR()) %>% - add_lines(x = ~wavenumber, y = ~intensity, - line = list(color = 'rgba(255,255,255,0.8)')) %>% - layout(yaxis = list(title = "absorbance intensity [-]"), - xaxis = list(title = "wavenumber [cm-1]", - autorange = "reversed"), - plot_bgcolor='rgb(17,0, 73)', - paper_bgcolor= 'rgba(0,0,0,0.5)', - font = list(color = '#FFFFFF')) - } - else if(length(input$event_rows_selected)) { - # Default to first row if not yet clicked - id_select <- ifelse(is.null(input$event_rows_selected), - 1, - MatchSpectra()[[input$event_rows_selected, - "sample_name"]]) - # Get data from find_spec - current_spectrum <- find_spec(sample_name == id_select, - spec_lib, which = input$Spectra, - type = input$Library) - - TopTens <- current_spectrum %>% - inner_join(MatchSpectra()[input$event_rows_selected,,drop = FALSE], - by = "sample_name") %>% - select(wavenumber, intensity, spectrum_identity) - - OGData <- DataR() %>% - select(wavenumber, intensity) %>% - mutate(spectrum_identity = "Spectrum to Analyze") - - plot_ly(TopTens, x = ~wavenumber, y = ~Intensity) %>% - add_lines(data = TopTens, x = ~wavenumber, y = ~intensity, - color = ~factor(spectrum_identity), colors = "#FF0000") %>% - # viridisLite::plasma(7, begin = 0.2, end = 0.8) - add_lines(data = OGData, x = ~wavenumber, y = ~intensity, - line = list(color = "rgba(255,255,255,0.8)"), - name = "Spectrum to Analyze") %>% - layout(yaxis = list(title = "absorbance intensity [-]"), - xaxis = list(title = "wavenumber [cm-1]", - autorange = "reversed"), - plot_bgcolor = "rgb(17,0, 73)", - paper_bgcolor = 'rgba(0,0,0,0.5)', - font = list(color = "#FFFFFF")) - }}) - - # Data Download options - output$downloadData5 <- downloadHandler( - filename = function() {"ftir_library.csv"}, - content = function(file) {fwrite(spec_lib[["ftir"]][["library"]], file)} - ) - - output$downloadData6 <- downloadHandler( - filename = function() {"raman_library.csv"}, - content = function(file) {fwrite(spec_lib[["raman"]][["library"]], file)} - ) - - output$downloadData4 <- downloadHandler( - filename = function() {"raman_metadata.csv"}, - content = function(file) {fwrite(spec_lib[["raman"]][["metadata"]], file)} - ) - - output$downloadData3 <- downloadHandler( - filename = function() {"ftir_metadata.csv"}, - content = function(file) {fwrite(spec_lib[["ftir"]][["metadata"]], file)} - ) - - output$download_testdata <- downloadHandler( - filename = function() {"testdata.csv"}, - content = function(file) {fwrite(testdata, file)} - ) - - ## Download own data ---- - output$downloadData <- downloadHandler( - filename = function() {paste('data-', human_ts(), '.csv', sep='')}, - content = function(file) {fwrite(baseline_data(), file)} - ) - - ## Sharing data ---- - # Hide functions which shouldn't exist when there is no internet or - # when the API token doesn't exist - observe({ - if((conf$share == "dropbox" & droptoken) | curl::has_internet()) { - show("share_decision") - show("share_meta") - } - else { - hide("share_decision") - hide("share_meta") - } - }) - - observe({ - if (input$share_decision) { - show("share_meta") - } else { - hide("share_meta") - sapply(names(namekey)[c(1:24,32)], function(x) hide(x)) - hide("submit") - } - }) - - - observe({ - if (input$baseline_selection == "Polynomial") { - show("baseline") - hide("go") - hide("reset") - } else { - hide("baseline") - show("go") - show("reset") - } - }) - - - observe({ - if (is.null(preprocessed_data())) { - show("placeholder1") - show("placeholder2") - show("placeholder3") - } else { - hide("placeholder1") - hide("placeholder2") - hide("placeholder3") - } - }) - - #This toggles the hidden metadata input layers. - observeEvent(input$share_meta, { - sapply(names(namekey)[c(1:24,32)], function(x) toggle(x)) - toggle("submit") - }) - - output$translate <- renderUI({ - if(translate & curl::has_internet()) { - includeHTML("www/googletranslate.html") - } - }) - - render_tweet <- function(x){renderUI({ - div(class = "inline-block", - style = "display:inline-block; margin-left:4px;", - tags$blockquote(class = "twitter-tweet", `data-theme` = "dark", - style = "width: 600px; display:inline-block;" , - tags$a(href = x)), - tags$script('twttr.widgets.load(document.getElementById("tweet"));') - ) - }) - } - - output$tweet1 <- renderUI({ - render_tweet(tweets[1]) - }) - - output$tweet2 <- renderUI({ - render_tweet(tweets[2]) - }) - - - # Log events ---- - - observeEvent(input$go, { - if(conf$log) { - if(db) { - database$insert(data.frame(user_name = input$fingerprint, - session_name = session_id, - wavenumber = trace$data$wavenumber, - intensity = trace$data$intensity, - data_id = digest::digest(preprocessed_data(), - algo = "md5"), - ipid = input$ipid, - time = human_ts())) - } - } - }) - - observe({ - req(input$file1) - req(input$share_decision) - if(conf$log) { - if(db) { - database$insert(data.frame(user_name = input$fingerprint, - session_name = session_id, - intensity_adj = input$intensity_corr, - smoother = input$smoother, - smooth_decision = input$smooth_decision, - baseline = input$baseline, - baseline_decision = input$baseline_decision, - max_range = input$MinRange, - min_range = input$MaxRange, - range_decision = input$range_decision, - data_id = digest::digest(preprocessed_data(), - algo = "md5"), - spectra_type = input$Spectra, - analyze_type = input$Data, - region_type = input$Library, - ipid = input$ipid, - time = human_ts())) - } else { - loggit("INFO", "trigger", - user_name = input$fingerprint, - session_name = session_id, - intensity_adj = input$intensity_corr, - smoother = input$smoother, - smooth_decision = input$smooth_decision, - baseline = input$baseline, - baseline_decision = input$baseline_decision, - max_range = input$MinRange, - min_range = input$MaxRange, - range_decision = input$range_decision, - data_id = digest::digest(preprocessed_data(), algo = "md5"), - spectra_type = input$Spectra, - analyze_type = input$Data, - region_type = input$Library, - ipid = input$ipid, - time = human_ts()) - } - } - - }) - -}) - diff --git a/inst/shiny/ui.R b/inst/shiny/ui.R deleted file mode 100644 index 112e0851..00000000 --- a/inst/shiny/ui.R +++ /dev/null @@ -1,892 +0,0 @@ -#' Shiny app server object -#' -#' @importFrom graphics hist -#' @import shiny -#' -# Libraries ---- -library(shiny) -library(shinyjs) -library(shinythemes) -library(shinyWidgets) -library(shinyBS) -library(dplyr) -library(plotly) -library(DT) - -# Name keys for human readable column names ---- -load("data/namekey.RData") - -version <- paste0("Open Specy v", packageVersion("OpenSpecy")) -citation <- HTML( - "Cowger W, Steinmetz Z, Gray A, Munno K, Lynch J, Hapich H, Primpke S, De - Frond H, Rochman C, Herodotou O (2021). “Microplastic Spectral - Classification Needs an Open Source Community: Open Specy to the Rescue!” - Analytical Chemistry, 93(21), 7543–7548. doi: - 10.1021/acs.analchem.1c00123." -) - -# Functions ---- -labelMandatory <- function(label) { - tagList( - label, - span("*", class = "mandatory_star") - ) -} - -inputUserid <- function(inputId, value="") { - # print(paste(inputId, "=", value)) - tagList( - singleton(tags$head(tags$script(src = "js/md5.js", - type="text/javascript"))), - singleton(tags$head(tags$script(src = "js/shinyBindings.js", - type="text/javascript"))), - tags$body(onload="setvalues()"), - tags$input(id = inputId, class = "userid", value=as.character(value), - type = "text", style = "display:none;") - ) -} - -inputIp <- function(inputId, value=""){ - tagList( - singleton(tags$head(tags$script(src = "js/md5.js", - type="text/javascript"))), - singleton(tags$head(tags$script(src = "js/shinyBindings.js", - type="text/javascript"))), - tags$body(onload="setvalues()"), - tags$input(id = inputId, class = "ipaddr", value=as.character(value), - type = "text", style = "display:none;") - ) -} - -css <- HTML( - "body { - color: #fff; - } - .nav-tabs > li[class=active] > a, - .nav-tabs > li[class=active] > a:focus, - .nav-tabs > li[class=active] > a:hover - { - background-color: #000; - }" -) - -# CSS for star -appCSS <- - ".mandatory_star { color: red; } - #loading_overlay { - position: absolute; - margin-top: 10%; - background: #000000; - opacity: 0.9; - z-index: 100; - left: 0; - right: 0; - height: 100%; - text-align: center; - color: #f7f7f9; - }" - -containerfunction <- function(...) { - div( - style = "padding:5rem", - div(class = "jumbotron jumbotron-fluid", - style = "border:solid #f7f7f9;background-color:rgba(0, 0, 0, 0.5)", - align = "justify", ... )) -} - -plotcontainerfunction <- function(...) { - div( - #style = "padding:0.1rem", - div(class = "jumbotron jumbotron-fluid", - style = "border:solid #f7f7f9;background-color:rgba(0, 0, 0, 0.5);padding:1rem", - align = "justify", - ...) - ) -} - -columnformat <- function() { - # 'background-color:rgba(0, 0, 0, 0.5); - # padding-bottom: 2rem' -} - -bodyformat <- function() { - # 'background-color:rgba(0, 0, 0, 0.5); - # padding-bottom: 2rem' -} - -#linefunction <- function(...){ -# hr(style = "color:#f7f7f9", ...) -#} - -# UI ---- -ui <- fluidPage( - - # Script for all pages ---- - # Required for any of the shinyjs functions. - shinyjs::useShinyjs(), - #extendShinyjs(text = "shinyjs.resetClick = function() { Shiny.onInputChange('.clientValue-plotly_click-A', 'null'); }", functions = "resetClick"), - inputIp("ipid"), - inputUserid("fingerprint"), - # tags$head(uiOutput("name_get")),singleton(tags$head()), - - tags$head(tags$style(css), - tags$script(async = NA, src = "https://platform.twitter.com/widgets.js"), - tags$script(async = T, src = "https://buttons.github.io/buttons.js"), - tags$style(HTML(" - .shiny-output-error-validation { - color: green; font-size: 300%; - } - ")), - tags$link(rel = "icon", type = "image/png", href = "favicon.png") - #This is for the error messages. - ), - #theme = bs_theme(fg = "#F9FBFA", bootswatch = "cyborg", bg = "#060606"), - theme = shinytheme("cyborg"), - # Change this for other themes - setBackgroundImage("jumbotron.png"), - - shinyjs::inlineCSS(appCSS), - - # Startup ---- - - #Title Panel ---- - titlePanel( - - fluidRow( - column(10, align = "left", img(src = "logo.png", width = 300, height = 75)), - column(2, align = "right", - div(style = "width: 90%; - padding: 15px; - font-size: 14pt; - border-radius: 0; - outline: none; - border: none; - text-align:left !important;", - uiOutput("translate") - # Google Translate - ) - ) - ), windowTitle = "Open Specy" - ), - # About Tab ---- - tabsetPanel(id = "tabs", - tabPanel("About", value = "tab0", - #Popovers ---- - - bsPopover( - id = "download_testdata", - title = "Sample Data Help", - content = "This is a sample spectrum that can be uploaded to the tool for testing it out and understanding how the csv files should be formatted.", - placement = "bottom", - trigger = "hover" - ), - bsPopover( - id = "share_meta", - title = "Metadata Help", - content = "We share any uploaded spectra and metadata with the spectroscopy community if you fill out the metadata here and select share. Uploaded spectra and metadata will appear here: https://osf.io/rjg3c", - placement = "bottom", - trigger = "hover" - ), - bsPopover(id = "downloadData", - title = "Download Help", - content = "Some users may wish to save a copy of their processed spectrum. This button downloads the processed spectrum as a csv file.", - placement = "bottom", - trigger = "hover"), - bsPopover( - id = "smooth_decision", - title = "Smoothing Help", - content = "This smoother can enhance the signal to noise ratio of the data and uses a Savitzky-Golay filter with 12 running data points and the polynomial specified.", - placement = "bottom", - trigger = "hover" - ), - bsPopover( - id = "baseline_decision", - title = "Baseline Correction Help", - content = "This baseline correction routine has two options for baseline correction, 1) the polynomial imodpolyfit procedure to itteratively find the baseline of the spectrum using a polynomial fit to the entire region of the spectra. 2) manual lines can be drawn using the line tool on the plot and the correct button will use the lines to subtract the baseline.", - placement = "bottom", - trigger = "hover" - ), - bsPopover( - id = "range_decision", - title = "Spectral Range Help", - content = "Restricting the spectral range can remove regions of spectrum where no peaks exist and improve matching", - placement = "bottom", - trigger = "hover" - ), - bsPopover( - id = "smooth_tools", - title = "Smoothing Help", - content = "Toggle advanced smoothing options", - placement = "bottom", - trigger = "hover" - ), - bsPopover( - id = "baseline_tools", - title = "Baseline Correction Help", - content = "Toggle advanced options for baseline corrections", - placement = "bottom", - trigger = "hover" - ), - bsPopover( - id = "range_tools", - title = "Spectral Range Help", - content = "Toggle advanced range selection", - placement = "bottom", - trigger = "hover" - ), - bsPopover( - id = "Spectra", - title = "Spectrum Type Help", - content = "This selection will determine whether the FTIR or Raman matching library is used. Choose the spectrum type that was uploaded.", - placement = "bottom", - trigger = "hover" - ), - bsPopover( - id = "Data", - title = "Spectrum to Analyze Help", - content = "This selection will determine whether the uploaded (not processed) spectrum or the spectrum processed using the processing tab is used in the spectrum match.", - placement = "bottom", - trigger = "hover" - ), - bsPopover( - id = "Library", - title = "Region to Match Help", - content = "This selection will determine whether the library you are matching to consists of the full spectrum or only spectrum peaks.", - placement = "bottom", - trigger = "hover" - ), - bsPopover( - id = "share_decision", - title = "Share Help", - content = "If you like, we share your uploaded spectra and settings with the spectroscopy community. By default, all data will be licensed under Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0). Uploaded spectra will appear here: https://osf.io/rjg3c", - placement = "bottom", - trigger = "hover" - ), - bsPopover( - id = "file1", - title = "Upload Help", - content = "Upload Raman or FTIR spectrum files as a csv, jdx, spc, or spa. A csv file is preferred. If a csv, the file must contain one column labeled wavenumber in units of (1/cm) and another column labeled intensity in absorbance units. If jdx, spc, spa, or 0 the file should be a single absorbance spectrum with wavenumber in (1/cm). These files will not always work perfectly because they are tricky to read so double check them in another software. Hit the Test Data button to download a sample Raman spectrum.", - placement = "bottom", - trigger = "click" - ), - bsPopover( - id = "intensity_corr", - title = "Intensity Correction Help", - content = "If the uploaded spectrum is not in absorbance units, use this input to specify the units to convert from.The transmittance adjustment uses the log10(1/T) calculation which does not correct for system and particle characteristics. The reflectance adjustment uses the Kubelka-Munk equation (1-R)2/(2*R). We assume that the reflectance is formatted as a percent from 1-100 and first correct the intensity by dividing by 100 so that it fits the form expected by the equation. If none is selected, Open Specy assumes that the uploaded data is an absorbance spectrum.", - placement = "bottom", - trigger = "hover" - ), - containerfunction( - h2("Welcome"), - fluidRow( - column(9, - p(class = "lead", "Join the hundreds of - researchers from around the world who are part of - the Open Specy community by - analyzing, sharing, processing, and identifying - their Raman and IR spectra."), - p(class = "lead", - HTML(" - on Twitter") - ), - p(class = "lead", - HTML("Watch - us develop Open Specy on GitHub, file an - Issue, - or request a feature") - ), - p(class = "lead", - HTML("Or just e-mail - wincowger@gmail.com - to be added to the Open Specy mailing list"), - ), - br(), - h3("Citation"), - p(class = "lead", citation), - br(),br(), - p(class = "lead", "Open Specy is free and open - source thanks to our partners:")), - column(3, img(src = "dancing.jpg", width = "100%") - ) - ), - fluidRow( - column(6, - h3("Monetary Partners"), - panel(style = "align: centre", - div(class = "jumbotron", - style = "padding:0rem 1rem 0rem; - border:solid #f7f7f9; - background-color:rgb(205, 127, 50, 0.5)", - h3("Thriving (10,000–100,000$)"), - img(src = "https://mooreplasticresearch.org/wp-content/uploads/2021/06/HorizontalLogo-FullName-1.png", style = "padding:1rem", height = 100), - h4("Mcpike Zima Charitable Foundation") - ), - div(class = "jumbotron", - style = "padding:0rem 1rem 0rem; - border:solid #f7f7f9; - background-color:rgb(3, 252, 15, 0.5)", - h3("Maintaining (1,000–10,000$)"), - img(src = "https://upload.wikimedia.org/wikipedia/commons/thumb/a/aa/UC_Riverside_logo.svg/1024px-UC_Riverside_logo.svg.png", style = "padding:1rem", height = 50), - img(src = "https://upload.wikimedia.org/wikipedia/commons/7/7e/NSF_logo.png", style = "padding:1rem", height = 50), - img(src = "https://www.awi.de/typo3conf/ext/sms_boilerplate/Resources/Public/Images/AWI/awi_logo.svg", style = "padding:1rem", height = 50), - img(src = "https://www.hpu.edu/_global/images/header-logo.png", style = "padding:1rem", height = 50), - img(src = "https://www.nist.gov/libraries/nist-component-library/dist/img/logo/nist_logo_sidestack_rev.svg", style = "padding:1rem", height = 50), - img(src = "https://www.utoronto.ca/sites/all/themes/uoft_stark/img/U-of-T-logo.svg", style = "padding:1rem", height = 50), - img(src = "https://www.uni-koblenz-landau.de/logo.png", style = "padding:1rem", height = 50), - img(src = "https://upload.wikimedia.org/wikipedia/commons/thumb/5/50/Thermo_Fisher_Scientific_logo.svg/2560px-Thermo_Fisher_Scientific_logo.svg.png", style = "padding:1rem", height = 50) - ), - div(class = "jumbotron", - style = "padding:0rem 1rem 0rem; - border:solid #f7f7f9; - background-color:rgb(0, 0, 255, 0.5)", - h3("Supporting (100–1,000$)"), - h5( "Jennifer Gadd") - ), - div(class = "jumbotron", - style = "padding:0rem 1rem 0rem; - border:solid #f7f7f9; - background-color:rgb(128, 0, 128, 0.5)", - h3("Saving (<100$)"), - h6("Susanne Brander (Oregon State University), Jeremy Conkle (TEXAS A&M UNIVERSITY CORPUS CHRISTI)") - ) - ) - ), - column(6, - h3("In-Kind Partners"), - panel(style = "align: centre", - div(class = "jumbotron", - style = "padding:0rem 1rem 0rem; - border:solid #f7f7f9; - background-color:rgb(205, 127, 50, 0.5)", - h3("Thriving (10,000–100,000$)"), - h4("Win Cowger, Zacharias Steinmetz") - ), - div(class = "jumbotron", - style = "padding:0rem 1rem 0rem; - border:solid #f7f7f9; - background-color:rgb(3, 252, 15, 0.5)", - h3("Maintaining (1,000–10,000$)"), - h5("Sebastian Primpke, Andrew Gray, Chelsea Rochman, Orestis Herodotu, Hannah De Frond, Keenan Munno, Hannah Hapich, Jennifer Lynch") - ), - div(class = "jumbotron", - style = "padding:0rem 1rem 0rem; - border:solid #f7f7f9; - background-color:rgb(0, 0, 255, 0.5)", - h3("Supporting (100–1,000$)"), - h6( "Shreyas Patankar, Andrea Faltynkova, Alexandre Dehaut, Gabriel Erni Cassola, Aline Carvalho") - ) - )) - ) - ), - containerfunction( - h2("Testimonials"), - fluidRow( - column(6, uiOutput("tweet1")), - column(6, uiOutput("tweet2")) - ) - ), - - containerfunction( - h2("Quick Video Tutorial"), - HTML("") - ), - containerfunction( - h2("Instructions"), - fluidRow( - column(6, - HTML("") - ), - column(6, - p(class = "lead", "In Brief: To use the tool upload a csv, asp, jdx, spc, or spa file to the upload file tab. - If csv, one column should be named 'wavenumber' (in units of 1/cm) and another named 'intensity'. - You can smooth your data using an SG filter, baseline correct your data using the polynomial order of iModPolyFit, and restrict the wavelength range for the match. - The result will be compared to an internal Raman or FTIR spectra library. The strongest 1000 matches along with your - uploaded or processed data will be presented in an interactive plot and table. For more details click the button below - or watch the detailed instructional video."), - a("Detailed Standard Operating Procedure", - onclick = "window.open('https://cran.r-project.org/web/packages/OpenSpecy/vignettes/sop.html', '_blank')", - class="btn btn-primary btn-lg") - ) - ) - ), - - containerfunction( - h2("Download Open Data"), - p(class = "lead", "Reference spectra was sourced from open access resources - online, peer reviewed publications, and corporate donations. In the future, - spectra that is uploaded to the tool will be incorporated to the reference - library to make it even better."), - div( - downloadButton("downloadData6", "Raman Reference Library", style = "background-color: #2a9fd6;"), - downloadButton("downloadData5", "FTIR Reference Library", style = "background-color: #2a9fd6;"), - downloadButton("downloadData4", "Raman Reference Library Metadata", style = "background-color: #2a9fd6;"), - downloadButton("downloadData3", "FTIR Reference Library Metadata", style = "background-color: #2a9fd6;") - ) - ), - - containerfunction( - h2("Contribute Spectra"), - p(class = "lead", "To share spectra upload a file to the upload file tab. - If you selected Share a copy of your spectra will be sent to the Community - Data Warehouse on Open Science Framework. To add additional metadata, - fill in the avaliable metadata fields and click -Share Data-. The - spectra file that you uploaded along with your responses will be copied - to the a -With Metadata- subfolder at the link below. All shared data holds - a Creative Commons Attribution License 4.0."), - div( - a("Community Data Warehouse", - onclick = "window.open('https://osf.io/rjg3c/', '_blank')", - class="btn btn-primary btn-lg") - ) - ), - - containerfunction( - h2("Tool Validation"), - p(class = "lead", "All parameters in this tool are tested to validate that - the tool is functioning as best as possible and determine the best default - parameters to use. Our current validation proceedure includes correcting - duplicated entries in the reference libraries, checking for spectra in - metadata that isn't in the spectral library, and ensuring the the default - parameters provide over 80% accuracy in the first match." - ), - div( - a("Detailed Validation Procedure", - onclick = "window.open('https://docs.google.com/document/d/1Zd2GY4bWIwegGeE4JpX8O0S5l_IYju0sLDl1ddTTMxU/edit?usp=sharing', '_blank')", - class="btn btn-primary btn-lg") - ) - ), - - containerfunction( - h2("Useful Links"), - a(href = "https://simple-plastics.eu/", "Free FTIR Software: siMPle microplastic IR spectral identification software", class = "lead"), - p(), - a(href = "https://www.thermofisher.com/us/en/home/industrial/spectroscopy-elemental-isotope-analysis/spectroscopy-elemental-isotope-analysis-learning-center/molecular-spectroscopy-information.html", "Free Spectroscopy Learning Academy from ThermoFisher", class = "lead"), - p(), - a(href = "https://micro.magnet.fsu.edu/primer/", "Free Optical Microscopy Learning Resource from Florida State University", class = "lead"), - p(), - a(href = "https://www.effemm2.de/spectragryph/index.html", "Free desktop application for spectral analysis and links to reference databases.", class = "lead") - ), - - containerfunction( - h2("Terms And Conditions"), - pre(includeText("www/TOS.txt")) - ), - - containerfunction( - h2("Privacy Policy"), - pre(includeText("www/privacy_policy.txt")) - ), - ), - - #Upload File Tab ---- - tabPanel("Upload File", value = "tab1", - titlePanel(h4("Upload, View, and Share Spectra")), - br(), - fluidRow( - column(3, style = columnformat(), - tags$label("Choose .csv (preferred), .asp, .jdx, .spc, .spa, or .0 File"), - - prettySwitch("share_decision", - label = "Share Your Data?", - inline = T, - value = T, - status = "success", - fill = T), - fileInput("file1", NULL, - placeholder = ".csv, .asp, .jdx, .spc, .spa, .0", - accept=c("text/csv", - "text/comma-separated-values,text/plain", - ".csv", ".asp", ".spc", ".jdx", ".spa", ".0")), - - - radioButtons("intensity_corr", "Intensity Adjustment", - c("None" = "none", - "Transmittance" = "transmittance", "Reflectance" = "reflectance")), - tags$br(), - - tags$div(downloadButton("download_testdata", - "Sample File", - style = "background-color: #2a9fd6;")), - - - tags$br(), - - actionButton("share_meta", "Metadata Input", style = "background-color: #2a9fd6;"), - - - hidden( - textInput(names(namekey)[1], - labelMandatory(namekey[1]), - placeholder = "e.g. Win Cowger"), - textInput(names(namekey)[2], - label = namekey[2], - placeholder = "e.g. 1-513-673-8956, wincowger@gmail.com"), - textInput(names(namekey)[3], - label = namekey[3], - placeholder = "e.g. University of California, Riverside"), - textInput(names(namekey)[4], - label = namekey[4], - placeholder = "e.g. Primpke, S., Wirth, M., Lorenz, C., & Gerdts, G. (2018). Reference database design for the automated analysis of microplastic samples based on Fourier transform infrared (FTIR) spectroscopy. Analytical and Bioanalytical Chemistry. doi: 10.1007/s00216-018-1156-x"), - textInput(names(namekey)[5], - labelMandatory(namekey[5]), - placeholder = "Raman or FTIR"), - textInput(names(namekey)[6], - labelMandatory(namekey[6]), - placeholder = "e.g. polystyrene"), - textInput(names(namekey)[7], - label = namekey[7], - placeholder = "e.g. textile fiber, rubber band, sphere, granule"), - textInput(names(namekey)[8], - label = namekey[8], - placeholder = "liquid, gas, solid"), - textInput(names(namekey)[9], - label = namekey[9], - placeholder = "e.g. Dow" ), - textInput(names(namekey)[10], - label = namekey[10], - placeholder = "e.g. 99.98%"), - textInput(names(namekey)[11], - label = namekey[11], - placeholder = "consumer product, manufacturer material, analytical standard, environmental sample"), - textInput(names(namekey)[12], - label = namekey[12], - placeholder = "e.g. blue, #0000ff, (0, 0, 255)"), - textInput(names(namekey)[13], - label = namekey[13], - placeholder = "e.g. 5 µm diameter fibers, 1 mm spherical particles"), - textInput(names(namekey)[14], - label = namekey[14], - placeholder = "9003-53-6"), - textInput(names(namekey)[15], - label = namekey[15], - placeholder = "Horiba LabRam"), - textInput(names(namekey)[16], - label = namekey[16], - placeholder = "Focal Plane Array, CCD"), - textInput(names(namekey)[17], - label = namekey[17], - placeholder = "transmission, reflectance"), - textInput(names(namekey)[18], - label = namekey[18], - placeholder = "e.g. 4/cm"), - textInput(names(namekey)[19], - label = namekey[19], - placeholder = "e.g. 785 nm" ), - textInput(names(namekey)[20], - label = namekey[20], - placeholder = "e.g. 5"), - textInput(names(namekey)[21], - label = namekey[21], - placeholder = "10 s"), - textInput(names(namekey)[22], - label = namekey[22], - placeholder = "spikefilter, baseline correction, none"), - textInput(names(namekey)[23], - label = namekey[23], - placeholder = "e.g. 99%"), - textInput(names(namekey)[24], label = "Other information"), - selectInput(names(namekey)[32], - label = namekey[32], - selected = "CC BY-NC", - choices = c("CC0", "CC BY", - "CC BY-SA", - "CC BY-NC", - "CC BY-ND", - "CC BY-NC-SA", - "CC BY-NC-ND")), - - tags$br(), - actionButton("submit", "Share Metadata", class = "btn-primary") - ) - ), - - - column(9, - plotcontainerfunction(h4(id = "placeholder1", "Upload some data to get started..."), plotlyOutput("MyPlot")), - style = bodyformat() - - ), - ), - hr(), - fluidRow( - column(3), - column(6, align = "center", - tags$p(citation), - tags$p(version) - ), - column(3) - - )), - - - #Preprocess Spectrum Tab ---- - tabPanel("Preprocess Spectrum", value = "tab2", - titlePanel(h4("Smooth, Baseline Correct, and Download Processed Spectra")), - br(), - fluidRow( - column(3, style = columnformat(), - fluidRow( - column(12, - downloadButton("downloadData", "Download (recommended)", - style = "background-color: #2a9fd6;") - - ) - ), - br(), - fluidRow( - column(9, ""), - column(3, align = "center", tags$label("Options")) - ), - br(), - fluidRow( - column(9, - prettySwitch(inputId = "smooth_decision", - label = "Smoothing", - inline = T, - value = T, - status = "success", - fill = T) - ), - column(3, align = "center", - prettyToggle("smooth_tools", - icon_on = icon("eye"), - icon_off = icon("eye-slash"), - label_on = NULL, label_off = NULL, - status_on = "success", - status_off = "default", - outline = TRUE, - plain = TRUE, - bigger = T), - ) - ), - - fluidRow( - column(9, - prettySwitch("baseline_decision", - label = "Baseline Correction", - inline = T, - value = T, - status = "success", - fill = T), - - ), - column(3, align = "center", - prettyToggle("baseline_tools", - icon_on = icon("eye"), - icon_off = icon("eye-slash"), - label_on = NULL, label_off = NULL, - status_on = "success", - status_off = "default", - outline = TRUE, - plain = TRUE, - bigger = T), - ) - ), - fluidRow( - column(9, - prettySwitch("range_decision", - label = "Range Selection", - inline = T, - value = T, - status = "success", - fill = T) - ), - column(3, align = "center", - prettyToggle("range_tools", - icon_on = icon("eye"), - icon_off = icon("eye-slash"), - label_on = NULL, label_off = NULL, - status_on = "success", - status_off = "default", - outline = TRUE, - plain = TRUE, - bigger = T), - ) - ), - br(), - fluidRow(column(12, - conditionalPanel("input.smooth_tools == true & input.smooth_decision == true", - plotcontainerfunction(sliderInput("smoother", "Smoothing Polynomial", min = 0, max = 7, value = 3) - )), - conditionalPanel("input.baseline_tools == true & input.baseline_decision == true", - plotcontainerfunction( - selectInput(inputId = "baseline_selection", label = "Baseline Correction Technique", choices = c("Polynomial", "Manual")), - sliderInput("baseline", "Baseline Correction Polynomial", min = 1, max = 20, value = 8), - fluidRow( - column(6, - actionButton("go", "Correct With Trace"), - ), - column(6, - actionButton("reset", "Reset"), - ) - ) - ) - - ), - conditionalPanel("input.range_tools == true & input.range_decision == true", - plotcontainerfunction( - numericInput( - "MaxRange", - "Maximum Spectral Range", - value = 6000, - min = NA, - max = NA, - step = NA, - width = NULL - ), - numericInput( - "MinRange", - "Minimum Spectral Range", - value = 0, - min = NA, - max = NA, - step = NA, - width = NULL - ) - ) - ) - ) - ) - ), - - - column(9, - plotcontainerfunction(h4(id = "placeholder2", "Upload some data to get started..."), plotlyOutput("MyPlotB")), - #verbatimTextOutput(outputId = "text"), - style = bodyformat() - - )), - hr(), - fluidRow( - column(3), - column(6, align = "center", - tags$p(citation), - tags$p(version) - ), - column(3) - - )), - - #Match Spectrum Tab ---- - tabPanel("Identify Spectrum",value = "tab3", - titlePanel(h4("Identify Spectrum Using the Reference Library")), - br(), - fluidRow( - column(3, style = columnformat(), - fluidRow( - column(4, - radioButtons("Spectra", "Type", - c("Raman" = "raman", - "FTIR" = "ftir")) - ), - column(4, - radioButtons("Data", "Analysis", - c("Processed" = "processed", - "Uploaded" = "uploaded" - )) - ), - column(4, - radioButtons("Library", "Region", - c("Full" = "full", - "Peaks" = "peaks")) - - ) - ), - fluidRow(style = "padding:1rem", - DT::dataTableOutput("event") - ) - ), - - column(9, - plotcontainerfunction(h4(id = "placeholder3", "Upload some data to get started..."), plotlyOutput("MyPlotC"), - DT::dataTableOutput("eventmetadata")), - style = bodyformat() - - ) - ), - - - hr(), - fluidRow( - column(3), - column(6, align = "center", - tags$p(citation), - tags$p(version) - ), - column(3) - - )), - - - #Partner With Us tab ---- - tabPanel("Partner With Us", - titlePanel(h4("Help us reach our goal to revolutionize spectroscopy.")), - br(), - fluidRow( - column(1), - column(3, - plotcontainerfunction( - tags$h3("Donate Cash"), - icon = icon("shopping-cart"), - img(src = "https://p.turbosquid.com/ts-thumb/rX/Wm1eqB/t5/currencysymbolsgoldensetc4dmodel000/jpg/1613802168/300x300/sharp_fit_q85/a31625492ce9c8009ab3e4281ad752006e1163ec/currencysymbolsgoldensetc4dmodel000.jpg", style = "padding:1rem; background-color:rgba(255,255,255, 0.9)", width = "100%"), - actionButton(inputId = "ab1", label = "Donate", style="padding:4px; background-color: #2a9fd6; font-size:200%", width = "100%", - icon = icon("donate"), - onclick = "window.open('https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=wincowger@gmail.com&lc=US&item_name=Donation+to+Open+Specy&no_note=0&cn=¤cy_code=USD&bn=PP-DonationsBF:btn_donateCC_LG.gif:NonHosted', '_blank')") - )), - column(3, - plotcontainerfunction(tags$h3("Buy From Swag Store"), - img(src = "https://image.spreadshirtmedia.com/image-server/v1/products/T813A823PA3132PT17X42Y46D1038541132FS4033/views/1,width=650,height=650,appearanceId=823/updated-logo-for-open-specy-designed-by-alex-mcgoran.jpg", style = "padding:1rem; background-color:rgba(255,255,255, 0.9)", width = "100%"), - actionButton(inputId = "ab2", label = "Shop", style="padding:4px; background-color: #2a9fd6; font-size:200%", width = "100%", - icon = icon("shopping-cart"), - onclick ="window.open('https://shop.spreadshirt.com/openspecy/all', '_blank')") - )), - column(3, - plotcontainerfunction( - h2("Contribute time"), - #p(class = "lead", "We are looking for coders, moderators, spectroscopy experts, microplastic researchers, industry, government, and others to join the Open Specy team. Please contact Win at wincowger@gmail.com"), - img(src = "https://health.sunnybrook.ca/wp-content/uploads/2020/02/healthy-hands-810x424.jpg", style = "padding:1rem; background-color:rgba(255,255,255, 0.9)", width = "100%"), - actionButton(inputId = "ab3", label = "Guidelines", style="padding:4px; background-color: #2a9fd6; font-size:200%", width = "100%", - icon = icon("clock"), - onclick ="window.open('https://docs.google.com/document/d/1SaFgAYKsLbMSYdJClR5s42TyGmPRWihLQcf5zun_yfo/edit?usp=sharing', '_blank')") - ) - ), - column(2) - ), - fluidRow( - column(1), - column(9, - div(style = "font-size:150%", - DT::dataTableOutput("event_goals"), - br() - ), - ), - column(2) - ), - hr(), - fluidRow( - column(3), - column(6, align = "center", - tags$p(citation), - tags$p(version) - ), - column(3) - - )) - ) -) - - -#Ideas -# see https://stackoverflow.com/questions/36412407/shiny-add-link-to-another-tabpanel-in-another-tabpanel/36426258 diff --git a/inst/shiny/www/TOS.txt b/inst/shiny/www/TOS.txt deleted file mode 100644 index 3c12f711..00000000 --- a/inst/shiny/www/TOS.txt +++ /dev/null @@ -1,49 +0,0 @@ -1. Who May Use the Services -You may use the Services only if you agree to form a binding contract with Open Specy by accepting these Terms. If you are accepting these Terms and using the Services on behalf of a company, organization, government, or other legal entity, you represent and warrant that you are authorized to do so and have the authority to bind such entity to these Terms, in which case the words “you” and “your” as used in these Terms shall refer to such entity. - -2. Content on the Services -You are responsible for your use of the Services and for any Content you provide, including compliance with applicable laws, rules, and regulations. You should only provide Content that you are comfortable sharing with others. - -Any use or reliance on any Content or materials posted via the Services or obtained by you through the Services is at your own risk. We do not endorse, support, represent or guarantee the completeness, truthfulness, accuracy, or reliability of any Content or communications posted via the Services. You understand that by using the Services, you may be exposed to Content that might be offensive, harmful, inaccurate or otherwise inappropriate, or in some cases, postings that have been mislabeled or are otherwise deceptive. All Content is the sole responsibility of the person who originated such Content. We may not monitor or control the Content posted via the Services and, we cannot take responsibility for such Content. - -We reserve the right to remove Content that violates the User Agreement, including for example, copyright or trademark violations or other intellectual property misappropriation, impersonation, unlawful conduct, or harassment. Information regarding specific policies and the process for reporting or appealing violations can be found by emailing Win at wincowger@gmail.com. - -If you believe that your Content has been copied in a way that constitutes copyright infringement, please report this by emailing Win at wincowger@gmail.com. - -3. Your Rights and Grant of Rights in the Content -You retain your rights to any Content you submit, post or display on or through the Services. What’s yours is yours — you own your Content (and your incorporated audio, photos and videos are considered part of the Content). - -By submitting, posting or displaying Content on or through the Services, you grant us a worldwide, non-exclusive, royalty-free license (with the right to sublicense) to use, copy, reproduce, process, adapt, modify, publish, transmit, display and distribute such Content in any and all media or distribution methods now known or later developed (for clarity, these rights include, for example, curating, transforming, and translating). This license authorizes us to make your Content available to the rest of the world and to let others do the same. You agree that this license includes the right for Open Specy to provide, promote, and improve the Services and to make Content submitted to or through the Services available to other companies, organizations or individuals for the syndication, broadcast, distribution, Retweet, promotion or publication of such Content on other media and services, subject to our terms and conditions for such Content use. Such additional uses by Open Specy, or other companies, organizations or individuals, is made with no compensation paid to you with respect to the Content that you submit, post, transmit or otherwise make available through the Services as the use of the Services by you is hereby agreed as being sufficient compensation for the Content and grant of rights herein. - -You understand that we may modify or adapt your Content as it is distributed, syndicated, published, or broadcast by us and our partners and/or make changes to your Content in order to adapt the Content to different media. - -You represent and warrant that you have, or have obtained, all rights, licenses, consents, permissions, power and/or authority necessary to grant the rights granted herein for any Content that you submit, post or display on or through the Services. You agree that such Content will not contain material subject to copyright or other proprietary rights, unless you have necessary permission or are otherwise legally entitled to post the material and to grant Open Specy the license described above. - - -4. Using the Services - -Our Services evolve constantly. As such, the Services may change from time to time, at our discretion. We may stop (permanently or temporarily) providing the Services or any features within the Services to you or to users generally. We also retain the right to create limits on use and storage at our sole discretion at any time. We may also remove or refuse to distribute any Content on the Services, limit distribution or visibility of any Content on the service. - -In consideration for Open Specy granting you access to and use of the Services, you agree that Open Specy and its third-party providers and partners may place advertising on the Services or in connection with the display of Content or information from the Services whether submitted by you or others. You also agree not to misuse our Services, for example, by interfering with them. You may not do any of the following while accessing or using the Services: (i) access, tamper with, or use non-public areas of the Services, Open Specy’s computer systems, or the technical delivery systems of Open Specy’s providers; (ii) probe, scan, or test the vulnerability of any system or network or breach or circumvent any security or authentication measures; (iii) access or search or attempt to access or search the Services by any means (automated or otherwise) other than through our currently available, published interfaces that are provided by Open Specy (and only pursuant to the applicable terms and conditions), unless you have been specifically allowed to do so in a separate agreement with Open Specy; (iv) forge any TCP/IP packet header or any part of the header information in any email or posting, or in any way use the Services to send altered, deceptive or false source-identifying information; or (v) interfere with, or disrupt, (or attempt to do so), the access of any user, host or network, including, without limitation, sending a virus, overloading, flooding, spamming, mail-bombing the Services, or by scripting the creation of Content in such a manner as to interfere with or create an undue burden on the Services. We also reserve the right to access, read, preserve, and disclose any information as we reasonably believe is necessary to (i) satisfy any applicable law, regulation, legal process or governmental request, (ii) enforce the Terms, including investigation of potential violations hereof, (iii) detect, prevent, or otherwise address fraud, security or technical issues, (iv) respond to user support requests, or (v) protect the rights, property or safety of Open Specy, its users and the public. Open Specy does not disclose personally-identifying information to third parties except in accordance with our Privacy Policy. - -Your Account -You may need to create an account to use some of our Services. You are responsible for safeguarding your account, so use a strong password and limit its use to this account. We cannot and will not be liable for any loss or damage arising from your failure to comply with the above. -You can control most communications from the Services. We may need to provide you with certain communications, such as service announcements and administrative messages. These communications are considered part of the Services and your account, and you may not be able to opt-out from receiving them. - -Your License to Use the Services -Open Specy gives you a personal, worldwide, royalty-free, non-assignable and non-exclusive license to use the software provided to you as part of the Services. This license has the sole purpose of enabling you to use and enjoy the benefit of the Services as provided by Open Specy, in the manner permitted by these Terms. -The Services are protected by copyright, trademark, and other laws of both the United States and other countries. Nothing in the Terms gives you a right to use the Open Specy name or any of the Open Specy trademarks, logos, domain names, other distinctive brand features, and other proprietary rights. All right, title, and interest in and to the Services (excluding Content provided by users) are and will remain the exclusive property of Open Specy and its licensors. Any feedback, comments, or suggestions you may provide regarding Open Specy, or the Services is entirely voluntary and we will be free to use such feedback, comments or suggestions as we see fit and without any obligation to you. - -Ending These Terms -You may end your legal agreement with Open Specy at any time by deactivating your accounts and discontinuing your use of the Services. - -5. Limitations of Liability -By using the Services you agree that Open Specy, its parents, affiliates, related companies, officers, directors, employees, agents representatives, partners and licensors, liability is limited to the maximum extent permissible in your country of residence. - -6. General -We may revise these Terms from time to time. The changes will not be retroactive, and the most current version of the Terms, will govern our relationship with you. By continuing to access or use the Services after those revisions become effective, you agree to be bound by the revised Terms. - -In the event that any provision of these Terms is held to be invalid or unenforceable, then that provision will be limited or eliminated to the minimum extent necessary, and the remaining provisions of these Terms will remain in full force and effect. Open Specy’s failure to enforce any right or provision of these Terms will not be deemed a waiver of such right or provision. - -These Terms are an agreement between you and Open Specy. If you have any questions about these Terms, please contact us. - diff --git a/inst/shiny/www/dancing.jpg b/inst/shiny/www/dancing.jpg deleted file mode 100644 index 261d28d5..00000000 Binary files a/inst/shiny/www/dancing.jpg and /dev/null differ diff --git a/inst/shiny/www/favicon.png b/inst/shiny/www/favicon.png deleted file mode 100644 index b422c90b..00000000 Binary files a/inst/shiny/www/favicon.png and /dev/null differ diff --git a/inst/shiny/www/googletranslate.html b/inst/shiny/www/googletranslate.html deleted file mode 100644 index 7792e618..00000000 --- a/inst/shiny/www/googletranslate.html +++ /dev/null @@ -1,13 +0,0 @@ -
\ No newline at end of file diff --git a/inst/shiny/www/js/jqfp.js b/inst/shiny/www/js/jqfp.js deleted file mode 100644 index fd9e5d49..00000000 --- a/inst/shiny/www/js/jqfp.js +++ /dev/null @@ -1,79 +0,0 @@ -// Browser fingerprinting is a technique to "mark" anonymous users using JS -// (or other things). To build an "identity" of sorts the browser is queried -// for a list of its plugins, the screen size and several other things, then -// hashes them. The idea is that these bits of information produce an unique -// "fingerprint" of sorts; the more elaborate the list of data points is, the -// more unique this fingerprint becomes. And you wouldn't even need to set a -// cookie to recognize this user when she visits again. -// -// For more information on this topic consult -// [Ars Technica](http://arstechnica.com/tech-policy/news/2010/05/how-your-web-browser-rats-you-out-online.ars) -// or the [EFF](http://panopticlick.eff.org/). There is a lot of potential -// for undesirable shenanigans, and I strictly oppose using this technique for -// marketing and ad-related tracking purposes. -// -// Anyways, I needed a really simple fingerprinting library, so I wrote a -// quick and dirty jQuery plugin. This is by no means a complete and -// watertight implementation -- it is merely the scratch for a particular itch -// I was having. YMMV. -// -// This library was written by Carlo Zottmann, carlo@municode.de, has its home -// on [Github](http://github.com/carlo/jquery-browser-fingerprint) and is -// WTF-licensed (see LICENSE.txt). - -( function($) { - - // Calling `jQuery.fingerprint()` will return an MD5 hash, i.e. said - // fingerprint. - - $.fingerprint = function() { - - // This function, `_raw()`, uses several browser details which are - // available to JS here to build a string, namely... - // - // * the user agent - // * screen size - // * color depth - // * the timezone offset - // * sessionStorage support - // * localStorage support - // * the list of all installed plugins (we're using their names, - // descriptions, mime types and file name extensions here) - function _raw() { - // That string is the return value. - return [ - navigator.userAgent, - [ screen.height, screen.width, screen.colorDepth ].join("x"), - ( new Date() ).getTimezoneOffset(), - !!window.sessionStorage, - !!window.localStorage, - $.map( navigator.plugins, function(p) { - return [ - p.name, - p.description, - $.map( p, function(mt) { - return [ mt.type, mt.suffixes ].join("~"); - }).join(",") - ].join("::"); - }).join(";") - ].join("###"); - } - - // `_md5()` computes a MD5 hash using [md5-js](http://github.com/wbond/md5-js/). - function _md5() { - if ( typeof window.md5 === "function" ) { - // The return value is the hashed fingerprint string. - return md5( _raw() ); - } - else { - // If `window.md5()` isn't available, an error is thrown. - throw "md5 unavailable, please get it from http://github.com/wbond/md5-js/"; - } - } - - // And, since I'm lazy, calling `$.fingerprint()` will return the hash - // right away, without the need for any other calls. - return _md5(); - } - -})(jQuery); diff --git a/inst/shiny/www/js/md5.js b/inst/shiny/www/js/md5.js deleted file mode 100644 index bebcdacc..00000000 --- a/inst/shiny/www/js/md5.js +++ /dev/null @@ -1,180 +0,0 @@ -/*! - * Joseph Myer's md5() algorithm wrapped in a self-invoked function to prevent - * global namespace polution, modified to hash unicode characters as UTF-8. - * - * Copyright 1999-2010, Joseph Myers, Paul Johnston, Greg Holt, Will Bond - * http://www.myersdaily.org/joseph/javascript/md5-text.html - * http://pajhome.org.uk/crypt/md5 - * - * Released under the BSD license - * http://www.opensource.org/licenses/bsd-license - */ -(function() { - function md5cycle(x, k) { - var a = x[0], b = x[1], c = x[2], d = x[3]; - - a = ff(a, b, c, d, k[0], 7, -680876936); - d = ff(d, a, b, c, k[1], 12, -389564586); - c = ff(c, d, a, b, k[2], 17, 606105819); - b = ff(b, c, d, a, k[3], 22, -1044525330); - a = ff(a, b, c, d, k[4], 7, -176418897); - d = ff(d, a, b, c, k[5], 12, 1200080426); - c = ff(c, d, a, b, k[6], 17, -1473231341); - b = ff(b, c, d, a, k[7], 22, -45705983); - a = ff(a, b, c, d, k[8], 7, 1770035416); - d = ff(d, a, b, c, k[9], 12, -1958414417); - c = ff(c, d, a, b, k[10], 17, -42063); - b = ff(b, c, d, a, k[11], 22, -1990404162); - a = ff(a, b, c, d, k[12], 7, 1804603682); - d = ff(d, a, b, c, k[13], 12, -40341101); - c = ff(c, d, a, b, k[14], 17, -1502002290); - b = ff(b, c, d, a, k[15], 22, 1236535329); - - a = gg(a, b, c, d, k[1], 5, -165796510); - d = gg(d, a, b, c, k[6], 9, -1069501632); - c = gg(c, d, a, b, k[11], 14, 643717713); - b = gg(b, c, d, a, k[0], 20, -373897302); - a = gg(a, b, c, d, k[5], 5, -701558691); - d = gg(d, a, b, c, k[10], 9, 38016083); - c = gg(c, d, a, b, k[15], 14, -660478335); - b = gg(b, c, d, a, k[4], 20, -405537848); - a = gg(a, b, c, d, k[9], 5, 568446438); - d = gg(d, a, b, c, k[14], 9, -1019803690); - c = gg(c, d, a, b, k[3], 14, -187363961); - b = gg(b, c, d, a, k[8], 20, 1163531501); - a = gg(a, b, c, d, k[13], 5, -1444681467); - d = gg(d, a, b, c, k[2], 9, -51403784); - c = gg(c, d, a, b, k[7], 14, 1735328473); - b = gg(b, c, d, a, k[12], 20, -1926607734); - - a = hh(a, b, c, d, k[5], 4, -378558); - d = hh(d, a, b, c, k[8], 11, -2022574463); - c = hh(c, d, a, b, k[11], 16, 1839030562); - b = hh(b, c, d, a, k[14], 23, -35309556); - a = hh(a, b, c, d, k[1], 4, -1530992060); - d = hh(d, a, b, c, k[4], 11, 1272893353); - c = hh(c, d, a, b, k[7], 16, -155497632); - b = hh(b, c, d, a, k[10], 23, -1094730640); - a = hh(a, b, c, d, k[13], 4, 681279174); - d = hh(d, a, b, c, k[0], 11, -358537222); - c = hh(c, d, a, b, k[3], 16, -722521979); - b = hh(b, c, d, a, k[6], 23, 76029189); - a = hh(a, b, c, d, k[9], 4, -640364487); - d = hh(d, a, b, c, k[12], 11, -421815835); - c = hh(c, d, a, b, k[15], 16, 530742520); - b = hh(b, c, d, a, k[2], 23, -995338651); - - a = ii(a, b, c, d, k[0], 6, -198630844); - d = ii(d, a, b, c, k[7], 10, 1126891415); - c = ii(c, d, a, b, k[14], 15, -1416354905); - b = ii(b, c, d, a, k[5], 21, -57434055); - a = ii(a, b, c, d, k[12], 6, 1700485571); - d = ii(d, a, b, c, k[3], 10, -1894986606); - c = ii(c, d, a, b, k[10], 15, -1051523); - b = ii(b, c, d, a, k[1], 21, -2054922799); - a = ii(a, b, c, d, k[8], 6, 1873313359); - d = ii(d, a, b, c, k[15], 10, -30611744); - c = ii(c, d, a, b, k[6], 15, -1560198380); - b = ii(b, c, d, a, k[13], 21, 1309151649); - a = ii(a, b, c, d, k[4], 6, -145523070); - d = ii(d, a, b, c, k[11], 10, -1120210379); - c = ii(c, d, a, b, k[2], 15, 718787259); - b = ii(b, c, d, a, k[9], 21, -343485551); - - x[0] = add32(a, x[0]); - x[1] = add32(b, x[1]); - x[2] = add32(c, x[2]); - x[3] = add32(d, x[3]); - } - - function cmn(q, a, b, x, s, t) { - a = add32(add32(a, q), add32(x, t)); - return add32((a << s) | (a >>> (32 - s)), b); - } - - function ff(a, b, c, d, x, s, t) { - return cmn((b & c) | ((~b) & d), a, b, x, s, t); - } - - function gg(a, b, c, d, x, s, t) { - return cmn((b & d) | (c & (~d)), a, b, x, s, t); - } - - function hh(a, b, c, d, x, s, t) { - return cmn(b ^ c ^ d, a, b, x, s, t); - } - - function ii(a, b, c, d, x, s, t) { - return cmn(c ^ (b | (~d)), a, b, x, s, t); - } - - function md51(s) { - // Converts the string to UTF-8 "bytes" when necessary - if (/[\x80-\xFF]/.test(s)) { - s = unescape(encodeURI(s)); - } - txt = ''; - var n = s.length, state = [1732584193, -271733879, -1732584194, 271733878], i; - for (i = 64; i <= s.length; i += 64) { - md5cycle(state, md5blk(s.substring(i - 64, i))); - } - s = s.substring(i - 64); - var tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - for (i = 0; i < s.length; i++) - tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3); - tail[i >> 2] |= 0x80 << ((i % 4) << 3); - if (i > 55) { - md5cycle(state, tail); - for (i = 0; i < 16; i++) tail[i] = 0; - } - tail[14] = n * 8; - md5cycle(state, tail); - return state; - } - - function md5blk(s) { /* I figured global was faster. */ - var md5blks = [], i; /* Andy King said do it this way. */ - for (i = 0; i < 64; i += 4) { - md5blks[i >> 2] = s.charCodeAt(i) + - (s.charCodeAt(i + 1) << 8) + - (s.charCodeAt(i + 2) << 16) + - (s.charCodeAt(i + 3) << 24); - } - return md5blks; - } - - var hex_chr = '0123456789abcdef'.split(''); - - function rhex(n) { - var s = '', j = 0; - for (; j < 4; j++) - s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + - hex_chr[(n >> (j * 8)) & 0x0F]; - return s; - } - - function hex(x) { - for (var i = 0; i < x.length; i++) - x[i] = rhex(x[i]); - return x.join(''); - } - - md5 = function (s) { - return hex(md51(s)); - } - - /* this function is much faster, so if possible we use it. Some IEs are the - only ones I know of that need the idiotic second function, generated by an - if clause. */ - function add32(a, b) { - return (a + b) & 0xFFFFFFFF; - } - - if (md5('hello') != '5d41402abc4b2a76b9719d911017c592') { - function add32(x, y) { - var lsw = (x & 0xFFFF) + (y & 0xFFFF), - msw = (x >> 16) + (y >> 16) + (lsw >> 16); - return (msw << 16) | (lsw & 0xFFFF); - } - } -})(); \ No newline at end of file diff --git a/inst/shiny/www/js/shinyBindings.js b/inst/shiny/www/js/shinyBindings.js deleted file mode 100644 index ae1a5b67..00000000 --- a/inst/shiny/www/js/shinyBindings.js +++ /dev/null @@ -1,163 +0,0 @@ - // Calling `jQuery.fingerprint()` will return an MD5 hash, i.e. said - // fingerprint. - - $.fingerprint = function() { - - // This function, `_raw()`, uses several browser details which are - // available to JS here to build a string, namely... - // - // * the user agent - // * screen size - // * color depth - // * the timezone offset - // * sessionStorage support - // * localStorage support - // * the list of all installed plugins (we're using their names, - // descriptions, mime types and file name extensions here) - function _raw() { - // That string is the return value. - return [ - navigator.userAgent,getip(), - [ screen.height, screen.width, screen.colorDepth ].join("x"), - ( new Date() ).getTimezoneOffset(), - !!window.sessionStorage, - !!window.localStorage, - $.map( navigator.plugins, function(p) { - return [ - p.name, - p.description, - $.map( p, function(mt) { - return [ mt.type, mt.suffixes ].join("~"); - }).join(",") - ].join("::"); - }).join(";") - ].join("###"); - } - - // `_md5()` computes a MD5 hash using [md5-js](http://github.com/wbond/md5-js/). - function _md5() { - if ( typeof window.md5 === "function" ) { - // The return value is the hashed fingerprint string. - return md5( _raw() ); - } - else { - // If `window.md5()` isn't available, an error is thrown. - throw "md5 unavailable, please get it from http://github.com/wbond/md5-js/"; - } - } - - // And, since I'm lazy, calling `$.fingerprint()` will return the hash - // right away, without the need for any other calls. - return _md5(); - } - - - /* - var outputUserid = new Shiny.OutputBinding(); - $.extend(outputUserid, { - find: function(scope) { - return $.find('.userid'); - }, - renderError: function(el,error) { - console.log("Foe"); - }, - renderValue: function(el,data) { - updateView(data); - console.log("Friend"); - - } - }); - Shiny.outputBindings.register(outputUserid); - */ - - var inputUseridBinding = new Shiny.InputBinding(); - $.extend(inputUseridBinding, { - find: function(scope) { - return $.find('.userid'); - }, - getValue: function(el) { - return $(el).val(); - }, - setValue: function(el, values) { - $(el).attr("value", $.fingerprint()); - $(el).trigger("change"); - }, - subscribe: function(el, callback) { - $(el).on("change.inputUseridBinding", function(e) { - callback(); - }); - }, - unsubscribe: function(el) { - $(el).off(".inputUseridBinding"); - } - }); - Shiny.inputBindings.register(inputUseridBinding); - - //setuid(); - -//A unique ID generated from the fingerprint of -// several browser characteristics. -shiny_uid=$.fingerprint(); - - -/* - * Set the uid fingerprint into the DOM elements that need to know about it. - * Do not call before the form loads, or the selectors won't find anything. - */ -function setuid() { - var fph = $('.userid'); - fph.attr("value", shiny_uid); - fph.trigger("change"); -} - -function setvalues(){ - getip(); - setuid(); -} -/* - * Set the uid fingerprint into the DOM elements that need to know about it. - * Do not call before the form loads, or the selectors won't find anything. - */ - -var inputIpBinding = new Shiny.InputBinding(); -$.extend(inputIpBinding, { - find: function(scope) { - return $.find('.ipaddr'); - }, - getValue: function(el) { - return $(el).val(); - }, - setValue: function(el, values) { - $(el).attr("value", getip()) - $(el).trigger("change"); - }, - subscribe: function(el, callback) { - $(el).on("change.inputIpBinding", function(e) { - callback(); - }); - }, - unsubscribe: function(el) { - $(el).off(".inputIpBinding"); - } -}); -Shiny.inputBindings.register(inputIpBinding); - - -function getip() { -ip = null; -$.getJSON("https://jsonip.com?callback=?", - function(data){ - ip = data.ip; - callback(ip); - $(".ipaddr").attr("value", ip); - $(".ipaddr").trigger("change"); - //return ip address correctly - }); -//alert(ip); //undefined or null -} - -function callback(tempip) -{ -ip=tempip; -// alert(ip); //undefined or null -} diff --git a/inst/shiny/www/jumbotron.png b/inst/shiny/www/jumbotron.png deleted file mode 100644 index 5602bdeb..00000000 Binary files a/inst/shiny/www/jumbotron.png and /dev/null differ diff --git a/inst/shiny/www/logo.png b/inst/shiny/www/logo.png deleted file mode 100644 index 71dec32c..00000000 Binary files a/inst/shiny/www/logo.png and /dev/null differ diff --git a/inst/shiny/www/privacy_policy.txt b/inst/shiny/www/privacy_policy.txt deleted file mode 100644 index 59a7202d..00000000 --- a/inst/shiny/www/privacy_policy.txt +++ /dev/null @@ -1,5 +0,0 @@ -Log Data -We receive information when you view content on or otherwise interact with our services, which we refer to as “Log Data,” even if you have not created an account. For example, when you visit our websites, sign into our services, interact with our email notifications, or visit a third-party service that includes Open Specy content, we may receive information. This Log Data includes information such as your IP address, browser type, operating system, the referring web page, pages visited, location, your mobile carrier, device information (including device and application IDs), search terms (including those not submitted as queries), and cookie information. We also receive Log Data when you click on, view, or interact with links and buttons on our services, including when you install another application through Open Specy. We use Log Data to operate our services and ensure their secure, reliable, and robust performance. For example, we use Log Data to protect the security of accounts and to determine what content is popular on our services. We also use this data to improve the functionality of the tools on the site, the content we show you, including ads and to improve the effectiveness of our own marketing. - -Non-Personal Information -We share or disclose non-personal data, such as aggregated information like the total number of times people visited the site, the number of people who clicked on a particular link, some inferred interests, or reports to advertisers about how many people saw or clicked on their ads. \ No newline at end of file diff --git a/man/OpenSpecy-package.Rd b/man/OpenSpecy-package.Rd index dfd6f8c0..03bcc458 100644 --- a/man/OpenSpecy-package.Rd +++ b/man/OpenSpecy-package.Rd @@ -7,7 +7,7 @@ \alias{openspecy} \title{OpenSpecy: Analyze, Process, Identify, and Share Raman and (FT)IR Spectra} \description{ -Raman and (FT)IR spectral analysis tool for plastic particles and other environmental samples (Cowger et al. 2021, \doi{10.1021/acs.analchem.1c00123}). Supported features include reading spectral data files (.asp, .csv, .jdx, .spc, .spa, .0), Savitzky-Golay smoothing of spectral intensities with smooth_intens(), correcting background noise with subtr_bg() in accordance with Zhao et al. (2007) \doi{10.1366/000370207782597003}, and identifying spectra using an onboard reference library (Cowger et al. 2020, \doi{10.1177/0003702820929064}). Analyzed spectra can be shared with the Open Specy community. A Shiny app is available via run_app() or online at \url{https://openanalysis.org/openspecy/}. +Raman and (FT)IR spectral analysis tool for plastic particles and other environmental samples (Cowger et al. 2021, \doi{10.1021/acs.analchem.1c00123}). With read_any(), Open Specy provides a single function for reading individual, batch, or map spectral data files like .asp, .csv, .jdx, .spc, .spa, .0, and .zip. process_spec() simplifies preprocessing spectra, including smoothing, baseline correction, range restriction and flattening, intensity conversions, wavenumber alignment, and min-max normalization. Spectra can be identified in batch using an onboard reference library (Cowger et al. 2020, \doi{10.1177/0003702820929064}) using match_spec(). A Shiny app is available via run_app() or online at \url{https://openanalysis.org/openspecy/}. } \references{ Chabuka BK, Kalivas JH (2020). “Application of a Hybrid Fusion Classification @@ -53,23 +53,31 @@ Useful links: } \author{ -\strong{Maintainer}: Win Cowger \email{wincowger@gmail.com} (\href{https://orcid.org/0000-0001-9226-3104}{ORCID}) +\strong{Maintainer}: Win Cowger \email{wincowger@gmail.com} (\href{https://orcid.org/0000-0001-9226-3104}{ORCID}) [data contributor] Authors: \itemize{ - \item Zacharias Steinmetz \email{steinmetz-z@uni-landau.de} (\href{https://orcid.org/0000-0001-6675-5033}{ORCID}) + \item Zacharias Steinmetz \email{z.steinmetz@rptu.de} (\href{https://orcid.org/0000-0001-6675-5033}{ORCID}) + \item Nick Leong (\href{https://orcid.org/0009-0008-3313-4132}{ORCID}) + \item Andrea Faltynkova (\href{https://orcid.org/0000-0003-2523-3137}{ORCID}) [data contributor] } Other contributors: \itemize{ - \item Andrew Gray (\href{https://orcid.org/0000-0003-2252-7367}{ORCID}) [contributor] + \item Andrew B Gray (\href{https://orcid.org/0000-0003-2252-7367}{ORCID}) [contributor] \item Hannah Hapich (\href{https://orcid.org/0000-0003-0000-6632}{ORCID}) [contributor] \item Jennifer Lynch (\href{https://orcid.org/0000-0003-3572-8782}{ORCID}) [contributor, data contributor] \item Hannah De Frond (\href{https://orcid.org/0000-0003-1199-0727}{ORCID}) [contributor, data contributor] \item Keenan Munno (\href{https://orcid.org/0000-0003-2916-5944}{ORCID}) [contributor, data contributor] \item Chelsea Rochman (\href{https://orcid.org/0000-0002-7624-711X}{ORCID}) [contributor, data contributor] \item Sebastian Primpke (\href{https://orcid.org/0000-0001-7633-8524}{ORCID}) [contributor, data contributor] - \item Orestis Herodotou [contributor, data contributor] + \item Orestis Herodotou [contributor] + \item Mary C Norris [contributor] + \item Christine M Knauss (\href{https://orcid.org/0000-0003-4404-8922}{ORCID}) [contributor] + \item Aleksandra Karapetrova (\href{https://orcid.org/0000-0002-9856-1644}{ORCID}) [contributor, data contributor, reviewer] + \item Vesna Teofilovic (\href{https://orcid.org/0000-0002-3557-1482}{ORCID}) [contributor] + \item Laura A. T. Markley (\href{https://orcid.org/0000-0003-0620-8366}{ORCID}) [contributor] + \item Shreyas Patankar [contributor, data contributor] } } diff --git a/man/adj_intens.Rd b/man/adj_intens.Rd index cc4caf88..5007a61c 100644 --- a/man/adj_intens.Rd +++ b/man/adj_intens.Rd @@ -2,30 +2,18 @@ % Please edit documentation in R/adj_intens.R \name{adj_intens} \alias{adj_intens} -\alias{adj_intens.formula} -\alias{adj_intens.data.frame} \alias{adj_intens.default} -\title{Adjust spectral intensities to absorbance units} +\alias{adj_intens.OpenSpecy} +\title{Adjust spectral intensities to absorbance units.} \usage{ adj_intens(x, ...) -\method{adj_intens}{formula}(formula, data = NULL, ...) +\method{adj_intens}{default}(x, ...) -\method{adj_intens}{data.frame}(x, ...) - -\method{adj_intens}{default}(x, y, type = "none", make_rel = TRUE, ...) +\method{adj_intens}{OpenSpecy}(x, type = "none", make_rel = TRUE, ...) } \arguments{ -\item{x}{a numeric vector containing the spectral wavenumbers; alternatively -a data frame containing spectral data as \code{"wavenumber"} and -\code{"intensity"} can be supplied.} - -\item{formula}{an object of class '\code{\link[stats]{formula}}' of the form -\code{intensity ~ wavenumber}.} - -\item{data}{a data frame containing the variables in \code{formula}.} - -\item{y}{a numeric vector containing the spectral intensities.} +\item{x}{a list object of class \code{OpenSpecy}.} \item{type}{a character string specifying whether the input spectrum is in absorbance units (\code{"none"}, default) or needs additional conversion @@ -34,7 +22,9 @@ from \code{"reflectance"} or \code{"transmittance"} data.} \item{make_rel}{logical; if \code{TRUE} spectra are automatically normalized with \code{\link{make_rel}()}.} -\item{\ldots}{further arguments passed to the submethods.} +\item{\ldots}{further arguments passed to submethods; this is +to \code{\link{adj_neg}()} for \code{adj_intens()} and +to \code{\link{conform_res}()} for \code{conform_intens()}.} } \value{ \code{adj_intens()} returns a data frame containing two columns @@ -45,8 +35,7 @@ Converts reflectance or transmittance intensity units to absorbance units. } \details{ Many of the Open Specy functions will assume that the spectrum is in -absorbance units. For example, see \code{\link{match_spec}()} and -\code{\link{subtr_bg}()}. +absorbance units. For example, see \code{\link{subtr_baseline}()}. To run those functions properly, you will need to first convert any spectra from transmittance or reflectance to absorbance using this function. The transmittance adjustment uses the \eqn{log10(1 / T)} @@ -63,9 +52,7 @@ adj_intens(raman_hdpe) } \seealso{ -\code{\link{subtr_bg}()} for spectral background correction; -\code{\link{match_spec}()} matches spectra with the Open Specy or other -reference libraries +\code{\link{subtr_baseline}()} for spectral background correction. } \author{ Win Cowger, Zacharias Steinmetz diff --git a/man/adj_range.Rd b/man/adj_range.Rd new file mode 100644 index 00000000..7d7eecb1 --- /dev/null +++ b/man/adj_range.Rd @@ -0,0 +1,70 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/adj_range.R +\name{restrict_range} +\alias{restrict_range} +\alias{restrict_range.default} +\alias{restrict_range.OpenSpecy} +\alias{flatten_range} +\alias{flatten_range.default} +\alias{flatten_range.OpenSpecy} +\title{Range restriction and flattening for spectra} +\usage{ +restrict_range(x, ...) + +\method{restrict_range}{default}(x, ...) + +\method{restrict_range}{OpenSpecy}(x, min, max, make_rel = TRUE, ...) + +flatten_range(x, ...) + +\method{flatten_range}{default}(x, ...) + +\method{flatten_range}{OpenSpecy}(x, min = 2200, max = 2400, make_rel = TRUE, ...) +} +\arguments{ +\item{x}{an \code{OpenSpecy} object.} + +\item{min}{a vector of minimum values for the range to be flattened.} + +\item{max}{a vector of maximum values for the range to be flattened.} + +\item{make_rel}{logical; should the output intensities be normalized to the +range [0, 1] using \code{make_rel()} function?} + +\item{\ldots}{additional arguments passed to subfunctions; currently not +in use.} +} +\value{ +An \code{OpenSpecy} object with the spectral intensities within specified +ranges restricted or flattened. +} +\description{ +\code{restrict_range()} restricts wavenumber ranges to user specified values. +Multiple ranges can be specified by inputting a series of max and min +values in order. +\code{flatten_range()} will flatten ranges of the spectra that should have no +peaks. +Multiple ranges can be specified by inputting the series of max and min +values in order. +} +\examples{ +test_noise <- as_OpenSpecy(x = seq(400,4000, by = 10), + spectra = data.frame(intensity = rnorm(361))) +plot(test_noise) + +restrict_range(test_noise, min = 1000, max = 2000) + +flattened_intensities <- flatten_range(test_noise, min = c(1000, 2000), + max = c(1500, 2500)) +plot(flattened_intensities) + +} +\seealso{ +\code{\link{conform_spec}()} for conforming wavenumbers to be matched with +a reference library; +\code{\link{adj_intens}()} for log transformation functions; +\code{\link[base]{min}()} and \code{\link[base]{round}()} +} +\author{ +Win Cowger, Zacharias Steinmetz +} diff --git a/man/as_OpenSpecy.Rd b/man/as_OpenSpecy.Rd new file mode 100644 index 00000000..e70cee70 --- /dev/null +++ b/man/as_OpenSpecy.Rd @@ -0,0 +1,194 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/as_OpenSpecy.R +\name{as_OpenSpecy} +\alias{as_OpenSpecy} +\alias{as_OpenSpecy.OpenSpecy} +\alias{as_OpenSpecy.list} +\alias{as_OpenSpecy.hyperSpec} +\alias{as_OpenSpecy.data.frame} +\alias{as_OpenSpecy.default} +\alias{is_OpenSpecy} +\alias{check_OpenSpecy} +\alias{OpenSpecy} +\alias{gen_grid} +\title{Create \code{OpenSpecy} objects} +\usage{ +as_OpenSpecy(x, ...) + +\method{as_OpenSpecy}{OpenSpecy}(x, session_id = FALSE, ...) + +\method{as_OpenSpecy}{list}(x, ...) + +\method{as_OpenSpecy}{hyperSpec}(x, ...) + +\method{as_OpenSpecy}{data.frame}(x, colnames = list(wavenumber = NULL, spectra = NULL), ...) + +\method{as_OpenSpecy}{default}( + x, + spectra, + metadata = list(file_name = NULL, user_name = NULL, contact_info = NULL, organization = + NULL, citation = NULL, spectrum_type = NULL, spectrum_identity = NULL, material_form + = NULL, material_phase = NULL, material_producer = NULL, material_purity = NULL, + material_quality = NULL, material_color = NULL, material_other = NULL, cas_number = + NULL, instrument_used = NULL, instrument_accessories = NULL, instrument_mode = NULL, + intensity_units = NULL, spectral_resolution = NULL, laser_light_used = NULL, + number_of_accumulations = NULL, + total_acquisition_time_s = NULL, + data_processing_procedure = NULL, level_of_confidence_in_identification = NULL, + other_info = NULL, license = "CC BY-NC"), + coords = "gen_grid", + session_id = FALSE, + ... +) + +is_OpenSpecy(x) + +check_OpenSpecy(x) + +OpenSpecy(x, ...) + +gen_grid(n) +} +\arguments{ +\item{x}{depending on the method, a list with all OpenSpecy parameters, +a vector with the wavenumbers for all spectra, or a data.frame with a full +spectrum in the classic Open Specy format.} + +\item{session_id}{logical. Whether to add a session ID to the metadata. +The session ID is based on current session info so metadata of the same +spectra will not return equal if session info changes. Sometimes that is +desirable.} + +\item{colnames}{names of the wavenumber column and spectra column, makes +assumptions based on column names or placement if \code{NULL}.} + +\item{spectra}{spectral intensities formatted as a data.table with one column +per spectrum.} + +\item{metadata}{metadata for each spectrum with one row per spectrum, +see details.} + +\item{coords}{spatial coordinates for the spectra.} + +\item{n}{number of spectra to generate the spatial coordinate grid with.} + +\item{\ldots}{additional arguments passed to submethods.} +} +\value{ +\code{as_OpenSpecy()} and \code{OpenSpecy()} returns three part lists +described in details. +\code{is_OpenSpecy()} returns \code{TRUE} if the object is an OpenSpecy and +\code{FALSE} if not. +\code{gen_grid()} returns a \code{data.table} with \code{x} and \code{y} +coordinates to use for generating a spatial grid for the spectra if one is +not specified in the data. +} +\description{ +Functions to check if an object is an OpenSpecy, or coerce it if +possible. +} +\details{ +\code{as_OpenSpecy()} converts spectral datasets to a three part list; +the first with a vector of the wavenumbers of the spectra, +the second with a \code{data.table} of all spectral intensities ordered as +columns, +the third item is another \code{data.table} with any metadata the user +provides or is harvested from the files themselves. + +The \code{metadata} argument may contain a named list with the following +details (\code{*} = minimum recommended): + +\tabular{ll}{ +\code{file_name*}: \tab The file name, defaults to +\code{\link[base]{basename}()} if not specified\cr +\code{user_name*}: \tab User name, e.g. "Win Cowger"\cr +\code{contact_info}: \tab Contact information, e.g. "1-513-673-8956, +wincowger@gmail.com"\cr +\code{organization}: \tab Affiliation, e.g. "University of California, +Riverside"\cr +\code{citation}: \tab Data citation, e.g. "Primpke, S., Wirth, M., Lorenz, +C., & Gerdts, G. (2018). Reference database design for the automated analysis +of microplastic samples based on Fourier transform infrared (FTIR) +spectroscopy. \emph{Analytical and Bioanalytical Chemistry}. +\doi{10.1007/s00216-018-1156-x}"\cr +\code{spectrum_type*}: \tab Raman or FTIR\cr +\code{spectrum_identity*}: \tab Material/polymer analyzed, e.g. +"Polystyrene"\cr +\code{material_form}: \tab Form of the material analyzed, e.g. textile fiber, +rubber band, sphere, granule \cr +\code{material_phase}: \tab Phase of the material analyzed (liquid, gas, +solid) \cr +\code{material_producer}: \tab Producer of the material analyzed, +e.g. Dow \cr +\code{material_purity}: \tab Purity of the material analyzed, e.g. 99.98\% +\cr +\code{material_quality}: \tab Quality of the material analyzed, e.g. +consumer product, manufacturer material, analytical standard, +environmental sample \cr +\code{material_color}: \tab Color of the material analyzed, +e.g. blue, #0000ff, (0, 0, 255) \cr +\code{material_other}: \tab Other material description, e.g. 5 µm diameter +fibers, 1 mm spherical particles \cr +\code{cas_number}: \tab CAS number, e.g. 9003-53-6 \cr +\code{instrument_used}: \tab Instrument used, e.g. Horiba LabRam \cr +\code{instrument_accessories}: \tab Instrument accessories, e.g. +Focal Plane Array, CCD\cr +\code{instrument_mode}: \tab Instrument modes/settings, e.g. +transmission, reflectance \cr +\code{intensity_units*}: \tab Units of the intensity values for the spectrum, options +transmittance, reflectance, absorbance \cr +\code{spectral_resolution}: \tab Spectral resolution, e.g. 4/cm \cr +\code{laser_light_used}: \tab Wavelength of the laser/light used, e.g. +785 nm \cr +\code{number_of_accumulations}: \tab Number of accumulations, e.g 5 \cr +\code{total_acquisition_time_s}: \tab Total acquisition time (s), e.g. 10 s +\cr +\code{data_processing_procedure}: \tab Data processing procedure, +e.g. spikefilter, baseline correction, none \cr +\code{level_of_confidence_in_identification}: \tab Level of confidence in +identification, e.g. 99\% \cr +\code{other_info}: \tab Other information \cr +\code{license}: \tab The license of the shared spectrum; defaults to +\code{"CC BY-NC"} (see +\url{https://creativecommons.org/licenses/by-nc/4.0/} for details). Any other +creative commons license is allowed, for example, CC0 or CC BY \cr +\code{session_id}: \tab A unique user and session identifier; populated +automatically with \code{paste(digest(Sys.info()), digest(sessionInfo()), +sep = "/")}\cr +\code{file_id}: \tab A unique file identifier; populated automatically +with \code{digest(object[c("wavenumber", "spectra")])}\cr +} +} +\examples{ +data("raman_hdpe") + +# Inspect the spectra +raman_hdpe # See how OpenSpecy objects print. +raman_hdpe$wavenumber # Look at just the wavenumbers of the spectra. +raman_hdpe$spectra # Look at just the spectral intensities data.table. +raman_hdpe$metadata # Look at just the metadata of the spectra. + +# Creating a list and transforming to OpenSpecy +as_OpenSpecy(list(wavenumber = raman_hdpe$wavenumber, + spectra = raman_hdpe$spectra, + metadata = raman_hdpe$metadata[,-c("x", "y")])) + +# If you try to produce an OpenSpecy using an OpenSpecy it will just return +# the same object. +as_OpenSpecy(raman_hdpe) + +# Creating an OpenSpecy from a data.frame +as_OpenSpecy(x = data.frame(wavenumber = raman_hdpe$wavenumber, + spectra = raman_hdpe$spectra$intensity)) + +# Test that the spectrum is formatted as an OpenSpecy object. +is_OpenSpecy(raman_hdpe) #should be TRUE +is_OpenSpecy(raman_hdpe$spectra) #should be FALSE + +} +\seealso{ +\code{\link{read_spec}()} for reading \code{OpenSpecy} objects. +} +\author{ +Zacharias Steinmetz, Win Cowger +} diff --git a/man/conform_spec.Rd b/man/conform_spec.Rd new file mode 100644 index 00000000..ab8b7f7d --- /dev/null +++ b/man/conform_spec.Rd @@ -0,0 +1,50 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/conform_spec.R +\name{conform_spec} +\alias{conform_spec} +\alias{conform_spec.default} +\alias{conform_spec.OpenSpecy} +\title{Conform spectra to a standard wavenumber series} +\usage{ +conform_spec(x, ...) + +\method{conform_spec}{default}(x, ...) + +\method{conform_spec}{OpenSpecy}(x, range = NULL, res = 5, type = "interp", ...) +} +\arguments{ +\item{x}{a list object of class \code{OpenSpecy}.} + +\item{range}{a vector of new wavenumber values, can be just supplied as a +min and max value.} + +\item{res}{spectral resolution adjusted to or \code{NULL} if the raw range +should be used.} + +\item{type}{the type of wavenumber adjustment to make. \code{"interp"} +results in linear interpolation while \code{"roll"} conducts a nearest +rolling join of the wavenumbers.} + +\item{\ldots}{further arguments passed to \code{\link[stats]{approx}()}} +} +\value{ +\code{adj_intens()} returns a data frame containing two columns +named \code{"wavenumber"} and \code{"intensity"} +} +\description{ +Spectra can be conformed to a standard suite of wavenumbers to be compared +with a reference library or to be merged to other spectra. +} +\examples{ +data("raman_hdpe") +conform_spec(raman_hdpe, c(1000, 2000)) + +} +\seealso{ +\code{\link{restrict_range}()} and \code{\link{flatten_range}()} for +adjusting wavenumber ranges; +\code{\link{subtr_baseline}()} for spectral background correction +} +\author{ +Win Cowger, Zacharias Steinmetz +} diff --git a/man/data_norm.Rd b/man/data_norm.Rd index a57054d6..117a1dde 100644 --- a/man/data_norm.Rd +++ b/man/data_norm.Rd @@ -1,49 +1,79 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/data_norm.R -\name{adj_neg} +\name{adj_res} +\alias{adj_res} +\alias{conform_res} \alias{adj_neg} \alias{make_rel} -\title{Normalization of spectral data} +\alias{mean_replace} +\alias{is_empty_vector} +\title{Normalization and conversion of spectral data} \usage{ -adj_neg(x, na.rm = FALSE) +adj_res(x, res = 1, fun = round) -make_rel(x, na.rm = FALSE) +conform_res(x, res = 5) + +adj_neg(y, na.rm = FALSE) + +make_rel(y, na.rm = FALSE) + +mean_replace(y, na.rm = TRUE) + +is_empty_vector(x) } \arguments{ \item{x}{a numeric vector or an \R object which is coercible to one by -\code{as.vector(x, "numeric")}; \code{x} should be \code{intensity} data.} +\code{as.vector(x, "numeric")}; \code{x} should contain the spectral +wavenumbers.} + +\item{res}{spectral resolution supplied to \code{fun}.} + +\item{fun}{the function to be applied to each element of \code{x}; defaults +to \code{\link[base]{round}()} to round to a specific resolution \code{res}.} + +\item{y}{a numeric vector containing the spectral intensities.} \item{na.rm}{logical. Should missing values be removed?} } \value{ +\code{adj_res()} and \code{conform_res()} return a numeric vector with +resolution-conformed wavenumbers. \code{adj_neg()} and \code{make_rel()} return numeric vectors -with the normalized data. +with the normalized intensity data. } \description{ -\code{adj_neg()} converts numeric values \code{x} < 1 into values ->= 1, keeping absolute differences between values by shifting intensity -values with the value of the smallest number. -\code{make_rel()} converts values \code{x} into relative values between +\code{adj_res()} and \code{conform_res()} are helper functions to align +wavenumbers in terms of their spectral resolution. +\code{adj_neg()} converts numeric intensities \code{y} < 1 into values >= 1, +keeping absolute differences between intensity values by shifting each value +by the minimum intensity. +\code{make_rel()} converts intensities \code{y} into relative values between 0 and 1 using the standard normalization equation. If \code{na.rm} is \code{TRUE}, missing values are removed before the computation proceeds. } \details{ -\code{adj_neg()} is used in Open Specy to avoid errors that could -arise from log transforming spectra when using -\code{\link{adj_intens}()} and other functions. -\code{make_rel()} is used in Open Specy to retain the relative -height proportions between spectra while avoiding the large numbers that can -result from some spectral instruments. +\code{adj_res()} and \code{conform_res()} are used in Open Specy to +facilitate comparisons of spectra with different resolutions. +\code{adj_neg()} is used to avoid errors that could arise from log +transforming spectra when using \code{\link{adj_intens}()} and other +functions. +\code{make_rel()} is used to retain the relative height proportions between +spectra while avoiding the large numbers that can result from some spectral +instruments. } \examples{ +adj_res(seq(500, 4000, 4), 5) +conform_res(seq(500, 4000, 4)) adj_neg(c(-1000, -1, 0, 1, 10)) make_rel(c(-1000, -1, 0, 1, 10)) } \seealso{ -\code{\link[base]{min}()} for the calculation of minima; -\code{\link{adj_intens}()} for log transformation functions +\code{\link[base]{min}()} and \code{\link[base]{round}()}; +\code{\link{adj_intens}()} for log transformation functions; +\code{\link{conform_spec}()} for conforming wavenumbers of an +\code{OpenSpecy} object to be matched with a reference library } \author{ Win Cowger, Zacharias Steinmetz diff --git a/man/def_features.Rd b/man/def_features.Rd new file mode 100644 index 00000000..9bd9291a --- /dev/null +++ b/man/def_features.Rd @@ -0,0 +1,68 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/def_features.R +\name{collapse_spec} +\alias{collapse_spec} +\alias{collapse_spec.default} +\alias{collapse_spec.OpenSpecy} +\alias{def_features} +\alias{def_features.default} +\alias{def_features.OpenSpecy} +\title{Define features} +\usage{ +collapse_spec(x, ...) + +\method{collapse_spec}{default}(x, ...) + +\method{collapse_spec}{OpenSpecy}(x, ...) + +def_features(x, ...) + +\method{def_features}{default}(x, ...) + +\method{def_features}{OpenSpecy}(x, features, ...) +} +\arguments{ +\item{x}{an \code{OpenSpecy} object} + +\item{features}{a logical vector or character vector describing which of the +spectra are of features (\code{TRUE}) and which are not (\code{FALSE}). +If a character vector is provided, it should represent the different feature +types present in the spectra.} + +\item{\ldots}{additional arguments passed to subfunctions.} +} +\value{ +An \code{OpenSpecy} object appended with metadata about the features or +collapsed for the features. +} +\description{ +Functions for analyzing features, like particles, fragments, or fibers, in +spectral map oriented \code{OpenSpecy} object. +} +\details{ +\code{def_features()} accepts an \code{OpenSpecy} object and a logical or +character vector describing which pixels correspond to particles. +\code{collapse_spec()} takes an \code{OpenSpecy} object with particle-specific +metadata (from \code{def_features()}) and collapses the spectra to median +intensities for each unique particle. +It also updates the metadata with centroid coordinates, while preserving the +feature information on area and Feret max. +} +\examples{ +# Logical input +map <- read_extdata("CA_tiny_map.zip") |> read_any() +map$metadata$features <- map$metadata$x == 0 +identified_map <- def_features(map, map$metadata$features) +test_collapsed <- collapse_spec(identified_map) + +# Character input +map <- read_extdata("CA_tiny_map.zip") |> read_any() +map$metadata$features <- ifelse(map$metadata$x == 1, "particle", + "not_particle") +identified_map <- def_features(map, map$metadata$features) +test_collapsed <- collapse_spec(identified_map) + +} +\author{ +Win Cowger, Zacharias Steinmetz +} diff --git a/man/gen_OpenSpecy.Rd b/man/gen_OpenSpecy.Rd new file mode 100644 index 00000000..bca31257 --- /dev/null +++ b/man/gen_OpenSpecy.Rd @@ -0,0 +1,63 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/gen_OpenSpecy.R +\name{head.OpenSpecy} +\alias{head.OpenSpecy} +\alias{print.OpenSpecy} +\alias{plot.OpenSpecy} +\alias{lines.OpenSpecy} +\alias{summary.OpenSpecy} +\title{Generic Open Specy Methods} +\usage{ +\method{head}{OpenSpecy}(x, ...) + +\method{print}{OpenSpecy}(x, ...) + +\method{plot}{OpenSpecy}(x, ...) + +\method{lines}{OpenSpecy}(x, ...) + +\method{summary}{OpenSpecy}(object, ...) +} +\arguments{ +\item{x}{an \code{OpenSpecy} object.} + +\item{object}{an \code{OpenSpecy} object.} + +\item{\ldots}{further arguments passed to the respective default method.} +} +\value{ +\code{head()}, \code{print()}, and \code{summary()} return a textual +representation of an \code{OpenSpecy} object. +\code{plot()} and \code{lines()} return a plot. +} +\description{ +Methods to visualize \code{OpenSpecy} objects. +} +\details{ +\code{head()} shows the first few lines of an \code{OpenSpecy} object. +\code{print()} prints the contents of an \code{OpenSpecy} object. +\code{summary()} produces a result summary of an \code{OpenSpecy} object. +\code{plot()} produces a \code{\link[graphics]{matplot}()} of an OpenSpecy +object; \code{lines()} adds new spectra to it. +} +\examples{ +data("raman_hdpe") + +# Printing the OpenSpecy object +print(raman_hdpe) + +# Displaying the first few lines of the OpenSpecy object +head(raman_hdpe) + +# Plotting the spectra +plot(raman_hdpe) + +} +\seealso{ +\code{\link[utils]{head}()}, \code{\link[base]{print}()}, +\code{\link[base]{summary}()}, \code{\link[graphics]{matplot}()}, and +\code{\link[graphics]{matlines}()} +} +\author{ +Zacharias Steinmetz, Win Cowger +} diff --git a/man/human_ts.Rd b/man/human_ts.Rd index 830ddf1a..a09dfd04 100644 --- a/man/human_ts.Rd +++ b/man/human_ts.Rd @@ -26,5 +26,5 @@ human_ts() \code{\link[base]{format.Date}} for date conversion functions } \author{ -Win Cowger +Win Cowger, Zacharias Steinmetz } diff --git a/man/interactive_plots.Rd b/man/interactive_plots.Rd new file mode 100644 index 00000000..42398219 --- /dev/null +++ b/man/interactive_plots.Rd @@ -0,0 +1,137 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/interactive_plots.R +\name{plotly_spec} +\alias{plotly_spec} +\alias{plotly_spec.default} +\alias{plotly_spec.OpenSpecy} +\alias{heatmap_spec} +\alias{heatmap_spec.default} +\alias{heatmap_spec.OpenSpecy} +\alias{interactive_plot} +\alias{interactive_plot.default} +\alias{interactive_plot.OpenSpecy} +\title{Interactive plots for OpenSpecy objects} +\usage{ +plotly_spec(x, ...) + +\method{plotly_spec}{default}(x, ...) + +\method{plotly_spec}{OpenSpecy}( + x, + x2 = NULL, + line = list(color = "rgb(255, 255, 255)"), + line2 = list(dash = "dot", color = "rgb(255,0,0)"), + font = list(color = "#FFFFFF"), + plot_bgcolor = "rgb(17, 0, 73)", + paper_bgcolor = "rgb(0, 0, 0)", + ... +) + +heatmap_spec(x, ...) + +\method{heatmap_spec}{default}(x, ...) + +\method{heatmap_spec}{OpenSpecy}( + x, + z = NULL, + sn = NULL, + cor = NULL, + min_sn = NULL, + min_cor = NULL, + select = NULL, + font = list(color = "#FFFFFF"), + plot_bgcolor = "rgba(17, 0, 73, 0)", + paper_bgcolor = "rgb(0, 0, 0)", + colorscale = "Viridis", + ... +) + +interactive_plot(x, ...) + +\method{interactive_plot}{default}(x, ...) + +\method{interactive_plot}{OpenSpecy}( + x, + x2 = NULL, + select = NULL, + line = list(color = "rgb(255, 255, 255)"), + line2 = list(dash = "dot", color = "rgb(255,0,0)"), + font = list(color = "#FFFFFF"), + plot_bgcolor = "rgba(17, 0, 73, 0)", + paper_bgcolor = "rgb(0, 0, 0)", + colorscale = "Viridis", + ... +) +} +\arguments{ +\item{x}{an \code{OpenSpecy} object containing metadata and spectral data for +the first group.} + +\item{x2}{an optional second \code{OpenSpecy} object containing metadata and +spectral data for the second group.} + +\item{line}{list; \code{line} parameter for \code{x}; passed to +\code{\link[plotly]{add_trace}()}.} + +\item{line2}{list; \code{line} parameter for \code{x2}; passed to} + +\item{font}{list; passed to \code{\link[plotly]{layout}()}.} + +\item{plot_bgcolor}{color value; passed to \code{\link[plotly]{layout}()}.} + +\item{paper_bgcolor}{color value; passed to \code{\link[plotly]{layout}()}.} + +\item{z}{optional numeric vector specifying the intensity values for the +heatmap. If not provided, the function will use the intensity values from the +\code{OpenSpecy} object.} + +\item{sn}{optional numeric value specifying the signal-to-noise ratio +threshold. If provided along with \code{min_sn}, regions with SNR below the +threshold will be excluded from the heatmap.} + +\item{cor}{optional numeric value specifying the correlation threshold. If +provided along with \code{min_cor}, regions with correlation below the +threshold will be excluded from the heatmap.} + +\item{min_sn}{optional numeric value specifying the minimum signal-to-noise +ratio for inclusion in the heatmap. Regions with SNR below this threshold +will be excluded.} + +\item{min_cor}{optional numeric value specifying the minimum correlation for +inclusion in the heatmap. Regions with correlation below this threshold +will be excluded.} + +\item{select}{optional index of the selected spectrum to highlight on the +heatmap.} + +\item{colorscale}{colorscale passed to \code{\link[plotly]{add_trace}()}.} + +\item{\ldots}{further arguments passed to \code{\link[plotly]{plot_ly}()}.} +} +\value{ +A plotly heatmap object displaying the OpenSpecy data. A subplot +containing the heatmap and spectra plot. A plotly object displaying the +spectra from the \code{OpenSpecy} object(s). +} +\description{ +These functions generate heatmaps, spectral plots, and interactive plots for +OpenSpecy data. +} +\examples{ +data("raman_hdpe") +tiny_map <- read_extdata("CA_tiny_map.zip") |> read_zip() +plotly_spec(raman_hdpe) + +correlation <- cor_spec( + conform_spec(raman_hdpe, range = raman_hdpe$wavenumber, res = 5), + conform_spec(tiny_map, tiny_map$wavenumbers, res = 5) +) +heatmap_spec(tiny_map, z = tiny_map$metadata$y) + +sample_spec(tiny_map, size = 12) |> + interactive_plot(select = 2, x2 = raman_hdpe) + +} +\author{ +Win Cowger, Zacharias Steinmetz +} diff --git a/man/io_spec.Rd b/man/io_spec.Rd new file mode 100644 index 00000000..1802fac8 --- /dev/null +++ b/man/io_spec.Rd @@ -0,0 +1,85 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/io_spec.R +\name{write_spec} +\alias{write_spec} +\alias{write_spec.default} +\alias{write_spec.OpenSpecy} +\alias{read_spec} +\alias{as_hyperSpec} +\title{Read and write spectral data} +\usage{ +write_spec(x, ...) + +\method{write_spec}{default}(x, ...) + +\method{write_spec}{OpenSpecy}(x, file, method = NULL, digits = getOption("digits"), ...) + +read_spec(file, share = NULL, method = NULL, ...) + +as_hyperSpec(x) +} +\arguments{ +\item{x}{an object of class \code{\link{OpenSpecy}}.} + +\item{file}{file path to be read from or written to.} + +\item{method}{optional; function to be used as a custom reader or writer. +Defaults to the appropriate function based on the file extension.} + +\item{digits}{number of significant digits to use when formatting numeric +values; defaults to \code{\link[base]{getOption}("digits")}.} + +\item{share}{defaults to \code{NULL}; needed to share spectra with the +Open Specy community; see \code{\link{share_spec}()} for details.} + +\item{\ldots}{further arguments passed to the submethods.} +} +\value{ +\code{read_spec()} reads data formatted as an \code{OpenSpecy} object and +returns a list object of class \code{\link{OpenSpecy}} containing spectral +data. +\code{write_spec()} writes a file for an object of class +\code{\link{OpenSpecy}} containing spectral data. +\code{as_hyperspec()} converts an \code{OpenSpecy} object to a +\code{\link[hyperSpec]{hyperSpec-class}} object. +} +\description{ +Functions for reading and writing spectral data to and from OpenSpecy format. +\code{OpenSpecy} objects are lists with components \code{wavenumber}, \code{spectra}, +and \code{metadata}. Currently supported formats are .y(a)ml, .json, or .rds. +} +\details{ +Due to floating point number errors there may be some differences in the +precision of the numbers returned if using multiple devices for .json and +.yaml files but the numbers should be nearly identical. +\code{\link[base]{readRDS}()} should return the exact same object every time. +} +\examples{ +read_extdata("raman_hdpe.yml") |> read_spec() +read_extdata("raman_hdpe.json") |> read_spec() +read_extdata("raman_hdpe.rds") |> read_spec() + +\dontrun{ +data(raman_hdpe) +write_spec(raman_hdpe, "raman_hdpe.yml") +write_spec(raman_hdpe, "raman_hdpe.json") +write_spec(raman_hdpe, "raman_hdpe.rds") + +# Convert an OpenSpecy object to a hyperSpec object +hyper <- as_hyperSpec(raman_hdpe) +} + +} +\seealso{ +\code{\link{OpenSpecy}()}; +\code{\link{read_text}()}, \code{\link{read_asp}()}, \code{\link{read_spa}()}, +\code{\link{read_spc}()}, and \code{\link{read_jdx}()} for text files, .asp, +.spa, .spa, .spc, and .jdx formats, respectively; +\code{\link{read_zip}()} and \code{\link{read_any}()} for wrapper functions; +\code{\link[base]{saveRDS}()}; \code{\link[base]{readRDS}()}; +\code{\link[yaml]{write_yaml}()}; \code{\link[yaml]{read_yaml}()}; +\code{\link[jsonlite]{write_json}()}; \code{\link[jsonlite]{read_json}()}; +} +\author{ +Zacharias Steinmetz, Win Cowger +} diff --git a/man/manage_lib.Rd b/man/manage_lib.Rd index c920d968..1246548a 100644 --- a/man/manage_lib.Rd +++ b/man/manage_lib.Rd @@ -4,36 +4,34 @@ \alias{check_lib} \alias{get_lib} \alias{load_lib} +\alias{rm_lib} \title{Manage spectral libraries} \usage{ check_lib( - which = c("ftir", "raman"), - types = c("metadata", "library", "peaks"), + type = c("derivative", "nobaseline", "raw", "mediod", "model"), path = "system", condition = "warning" ) get_lib( - which = c("ftir", "raman"), - types = c("metadata", "library", "peaks"), + type = c("derivative", "nobaseline", "raw", "mediod", "model"), path = "system", node = "x7dpz", conflicts = "overwrite", ... ) -load_lib( - which = c("ftir", "raman"), - types = c("metadata", "library", "peaks"), +load_lib(type, path = "system") + +rm_lib( + type = c("derivative", "nobaseline", "raw", "mediod", "model"), path = "system" ) } \arguments{ -\item{which}{a character string specifying which library to use, -\code{"raman"} or \code{"ftir"}.} - -\item{types}{library types to check/retrieve; defaults to -\code{c("metadata", "library", "peaks")}.} +\item{type}{library type to check/retrieve; defaults to +\code{c("derivative", "nobaseline", "raw", "mediod", "model")} which reads +everything.} \item{path}{where to save or look for local library files; defaults to \code{"system"} pointing to @@ -42,25 +40,26 @@ load_lib( \item{condition}{determines if \code{check_lib()} should warn (\code{"warning"}, the default) or throw and error (\code{"error"}).} -\item{node}{the OSF node to be retrieved; should be \code{"x7dpz"}.} +\item{node}{the OSF node to be retrieved; should be \code{"x7dpz"} unless you +maintain your own OSF node with spectral libraries.} \item{conflicts}{determines what happens when a file with the same name exists at the specified destination. Can be one of the following (see \code{\link[osfr]{osf_download}()} for details): \itemize{ - \item{"error"}{ throw an error and abort the file transfer operation.} - \item{"skip"}{ skip the conflicting file(s) and continue transferring the - remaining files.} - \item{"overwrite" (default)}{ replace the existing file with the - transferred copy.} +\item{"error"}{ throw an error and abort the file transfer operation.} +\item{"skip"}{ skip the conflicting file(s) and continue transferring the +remaining files.} +\item{"overwrite" (default)}{ replace the existing file with the +transferred copy.} }} \item{\ldots}{further arguments passed to \code{\link[osfr]{osf_download}()}.} } \value{ \code{check_lib()} and \code{get_lib()} return messages only; -\code{load_lib()} returns a list object containing the respective spectral -reference library. +\code{load_lib()} returns an \code{OpenSpecy} object containing the +respective spectral reference library. } \description{ These functions will import the spectral libraries from Open Specy if they @@ -76,13 +75,14 @@ already exists on the users computer. (\doi{10.17605/OSF.IO/X7DPZ}). \code{load_lib()} will load the library into the global environment for use with the Open Specy functions. +\code{rm_lib()} removes the libraries from your computer. } \examples{ \dontrun{ -check_lib(which = c("ftir", "raman")) -get_lib(which = c("ftir", "raman")) +check_lib("derivative") +get_lib("derivative") -spec_lib <- load_lib(which = c("ftir", "raman")) +spec_lib <- load_lib("derivative") } } @@ -95,9 +95,6 @@ Spectra in Microplastic Research.” \emph{Applied Spectroscopy}, Cowger, W (2021). “Library data.” \emph{OSF}. \doi{10.17605/OSF.IO/X7DPZ}. } -\seealso{ -\code{\link{match_spec}()} -} \author{ -Zacharias Steinmetz +Zacharias Steinmetz, Win Cowger } diff --git a/man/manage_spec.Rd b/man/manage_spec.Rd new file mode 100644 index 00000000..50bd2612 --- /dev/null +++ b/man/manage_spec.Rd @@ -0,0 +1,64 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/manage_spec.R +\name{c_spec} +\alias{c_spec} +\alias{c_spec.default} +\alias{c_spec.OpenSpecy} +\alias{c_spec.list} +\alias{sample_spec} +\alias{sample_spec.default} +\alias{sample_spec.OpenSpecy} +\title{Manage spectral objects} +\usage{ +c_spec(x, ...) + +\method{c_spec}{default}(x, ...) + +\method{c_spec}{OpenSpecy}(x, ...) + +\method{c_spec}{list}(x, range = NULL, res = 5, ...) + +sample_spec(x, ...) + +\method{sample_spec}{default}(x, ...) + +\method{sample_spec}{OpenSpecy}(x, ...) +} +\arguments{ +\item{x}{a list of \code{OpenSpecy} objects.} + +\item{range}{a numeric providing your own wavenumber ranges or character +argument called \code{"common"} to let \code{c_spec()} find the common +wavenumber range of the supplied spectra. \code{NULL} will interpret the +spectra having all the same wavenumber range.} + +\item{res}{defaults to \code{NULL}, the resolution you want the output +wavenumbers to be.} + +\item{\ldots}{further arguments passed to submethods.} +} +\value{ +\code{c_spec()} and \code{sample_spec()} return \code{OpenSpecy} objects. +} +\description{ +\code{c_spec()} concatenates \code{OpenSpecy} objects. +\code{sample_spec()} samples spectra from an \code{OpenSpecy} object. +} +\examples{ +# Concatenating spectra +spectra <- lapply(c(read_extdata("raman_hdpe.csv"), + read_extdata("ftir_ldpe_soil.asp")), read_any) +common <- c_spec(spectra, range = "common", res = 5) +range <- c_spec(spectra, range = c(1000, 2000), res = 5) + +# Sampling spectra +tiny_map <- read_any(read_extdata("CA_tiny_map.zip")) +sampled <- sample_spec(tiny_map, size = 3) + +} +\seealso{ +\code{\link[OpenSpecy]{conform_spec}()} for conforming wavenumbers +} +\author{ +Zacharias Steinmetz, Win Cowger +} diff --git a/man/match_spec.Rd b/man/match_spec.Rd index a81cf7bc..f90eb855 100644 --- a/man/match_spec.Rd +++ b/man/match_spec.Rd @@ -1,114 +1,152 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/match_spec.R -\name{match_spec} +\name{cor_spec} +\alias{cor_spec} +\alias{cor_spec.default} +\alias{cor_spec.OpenSpecy} \alias{match_spec} -\alias{match_spec.formula} -\alias{match_spec.data.frame} \alias{match_spec.default} -\alias{find_spec} -\title{Match spectra with reference library} +\alias{match_spec.OpenSpecy} +\alias{ident_spec} +\alias{get_metadata} +\alias{get_metadata.default} +\alias{get_metadata.OpenSpecy} +\alias{max_cor_named} +\alias{filter_spec} +\alias{filter_spec.default} +\alias{filter_spec.OpenSpecy} +\alias{ai_classify} +\alias{ai_classify.default} +\alias{ai_classify.OpenSpecy} +\title{Identify and filter spectra} \usage{ -match_spec(x, ...) +cor_spec(x, ...) + +\method{cor_spec}{default}(x, ...) + +\method{cor_spec}{OpenSpecy}(x, library, na.rm = T, ...) -\method{match_spec}{formula}(formula, data = NULL, ...) +match_spec(x, ...) -\method{match_spec}{data.frame}(x, ...) +\method{match_spec}{default}(x, ...) -\method{match_spec}{default}( +\method{match_spec}{OpenSpecy}( x, - y, library, - which = NULL, - type = "full", - range = seq(0, 6000, 0.1), - top_n = 100, + na.rm = T, + top_n = NULL, + add_library_metadata = NULL, + add_object_metadata = NULL, + fill = NULL, ... ) -find_spec( - subset, +ident_spec( + cor_matrix, + x, library, - which = NULL, - type = "metadata", - cols = c("spectrum_identity", "organization", "contact_info", "spectrum_type", - "instrument_used", "instrument_accessories", "instrument_mode", "laser_light_used", - "total_acquisition_time_s", "number_of_accumulations", - "level_of_confidence_in_identification", "cas_number", "material_producer", - "material_purity", "material_form", "material_quality", "spectral_resolution", - "data_processing_procedure", "other_information", "sample_name", "wavenumber", - "intensity", "group"), + top_n = NULL, + add_library_metadata = NULL, + add_object_metadata = NULL, ... ) + +get_metadata(x, ...) + +\method{get_metadata}{default}(x, ...) + +\method{get_metadata}{OpenSpecy}(x, logic, rm_empty = TRUE, ...) + +max_cor_named(cor_matrix, na.rm = T) + +filter_spec(x, ...) + +\method{filter_spec}{default}(x, ...) + +\method{filter_spec}{OpenSpecy}(x, logic, ...) + +ai_classify(x, ...) + +\method{ai_classify}{default}(x, ...) + +\method{ai_classify}{OpenSpecy}(x, library, fill = NULL, ...) } \arguments{ -\item{x}{a numeric vector containing the spectral wavenumbers; alternatively -a data frame containing spectral data as \code{"wavenumber"} and -\code{"intensity"} can be supplied.} - -\item{formula}{an object of class '\code{\link[stats]{formula}}' of the form -\code{intensity ~ wavenumber}.} +\item{x}{an \code{OpenSpecy} object, typically with unknowns.} -\item{data}{a data frame containing the variables in \code{formula}.} +\item{library}{an \code{OpenSpecy} or \code{glmnet} object representing the +reference library of spectra or model to use in identification.} -\item{y}{a numeric vector containing the spectral intensities.} +\item{na.rm}{logical; indicating whether missing values should be removed +when calculating correlations. Default is \code{TRUE}.} -\item{library}{reference library you want to compare against.} +\item{top_n}{integer; specifying the number of top matches to return. +If \code{NULL} (default), all matches will be returned.} -\item{which}{a character string specifying which library to match, -\code{"raman"} or \code{"ftir"}.} +\item{add_library_metadata}{name of a column in the library metadata to be +joined; \code{NULL} if you don't want to join.} -\item{type}{a character string specifying whether the \code{"full"} spectrum -should be matched or spectrum \code{"peaks"} only. \code{"metadata"} is -needed to browser spectra with \code{find_spec()}.} +\item{add_object_metadata}{name of a column in the object metadata to be +joined; \code{NULL} if you don't want to join.} -\item{range}{this should be all possible wavenumber values from your spectral -library.} +\item{fill}{an \code{OpenSpecy} object with a single spectrum to be used to +fill missing values for alignment with the AI classification.} -\item{top_n}{number of top matches that you want to be returned.} +\item{cor_matrix}{a correlation matrix for object and library, +can be returned by \code{cor_spec()}} -\item{subset}{logical expression indicating elements or rows to search for; -see \code{\link[base]{subset}()} for details.} +\item{logic}{a logical or numeric vector describing which spectra to keep.} -\item{cols}{columns to retrieve from the Open Specy reference library; -columns containing no or missing values are automatically removed.} +\item{rm_empty}{logical; whether to remove empty columns in the metadata.} -\item{\ldots}{further arguments passed to the submethods.} +\item{\ldots}{additional arguments passed \code{\link[stats]{cor}()}.} } \value{ -\code{match_spec()} returns a data frame with the \code{top_n} material -matches, their Pearson's r value, and the organization they were provided by. -\code{find_spec()} returns a data frame with the spectral raw data or -metadata of a specific reference spectrum. +\code{match_spec()} and \code{ident_spec()} will return +a \code{\link[data.table]{data.table-class}()} containing correlations +between spectra and the library. +The table has three columns: \code{object_id}, \code{library_id}, and +\code{match_val}. +Each row represents a unique pairwise correlation between a spectrum in the +object and a spectrum in the library. +If \code{top_n} is specified, only the top \code{top_n} matches for each +object spectrum will be returned. +If \code{add_library_metadata} is \code{is.character}, the library metadata +will be added to the output. +If \code{add_object_metadata} is \code{is.character}, the object metadata +will be added to the output. +\code{filter_spec()} returns an \code{OpenSpecy} object. +\code{cor_spec()} returns a correlation matrix. +\code{get_metadata()} returns a \code{\link[data.table]{data.table-class}()} +with the metadata for columns which have information. } \description{ -\code{match_spec()} will compare a spectrum to a spectral library formatted -with the Open Specy standard and report the best match using the Pearson -correlation coefficient. -\code{find_spec()} makes it easy to retrieve single spectra and metadata -from the Open Specy reference library. -} -\details{ -This routine will match the spectrum you want to identify to the wavenumbers -present in the spectral library. Once the spectra are aligned, it computes -the Pearson correlation coefficient between the spectrum you want to -identify and all spectra in the library (see \code{\link[stats]{cor}}). -The function returns a table with the Pearson correlation coefficient values -and all metadata for the top spectral matches. -If using the Open Specy library, all intensity values are in absorbance, so -your spectra should also be in absorbance units. If you need to convert your -spectrum, use \code{\link{adj_intens}()}. +\code{match_spec()} joins two \code{OpenSpecy} objects and their metadata +based on similarity. +\code{cor_spec()} correlates two \code{OpenSpecy} objects, typically one with +knowns and one with unknowns. +\code{ident_spec()} retrieves the top match values from a correlation matrix +and formats them with metadata. +\code{get_metadata()} retrieves metadata from OpenSpecy objects. +\code{max_cor_named()} formats the top correlation values from a correlation +matrix as a named vector. +\code{filter_spec()} filters an Open Specy object. } \examples{ -\dontrun{ -data("raman_hdpe") - -get_lib("raman") -spec_lib <- load_lib("raman") - -match_spec(raman_proc, library = spec_lib, which = "raman") +data("test_lib") +unknown <- read_extdata("ftir_ldpe_soil.asp") |> + read_any() |> + conform_spec(range = test_lib$wavenumber, + res = spec_res(test_lib)) |> + process_spec() +matches <- cor_spec(unknown, test_lib) + +test_lib_extract <- filter_spec(test_lib, + logic = grepl("polycarbonate", test_lib$metadata$polymer_class, + ignore.case = TRUE) +) -find_spec(sample_name == 5381, library = spec_lib, which = "raman") -} +matches2 <- cor_spec(unknown, library = test_lib_extract) } \seealso{ diff --git a/man/process_spec.Rd b/man/process_spec.Rd new file mode 100644 index 00000000..383f8c0d --- /dev/null +++ b/man/process_spec.Rd @@ -0,0 +1,112 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/process_spec.R +\name{process_spec} +\alias{process_spec} +\alias{process_spec.default} +\alias{process_spec.OpenSpecy} +\title{Preprocess Spectra} +\usage{ +process_spec(x, ...) + +\method{process_spec}{default}(x, ...) + +\method{process_spec}{OpenSpecy}( + x, + active = TRUE, + adj_intens = FALSE, + adj_intens_args = list(type = "none"), + conform_spec = TRUE, + conform_spec_args = list(range = NULL, res = 5, type = "interp"), + restrict_range = FALSE, + restrict_range_args = list(min = 0, max = 6000), + flatten_range = FALSE, + flatten_range_args = list(min = 2200, max = 2420), + subtr_baseline = FALSE, + subtr_baseline_args = list(type = "polynomial", degree = 8, raw = FALSE, baseline = + NULL), + smooth_intens = TRUE, + smooth_intens_args = list(polynomial = 3, window = 11, derivative = 1, abs = TRUE), + make_rel = TRUE, + ... +) +} +\arguments{ +\item{x}{an \code{OpenSpecy} object.} + +\item{active}{logical; indicating whether to perform preprocessing. +If \code{TRUE}, the preprocessing steps will be applied. +If \code{FALSE}, the original data will be returned.} + +\item{adj_intens}{logical; describing whether to adjust the intensity units.} + +\item{adj_intens_args}{named list of arguments passed to +\code{\link{smooth_intens}()}.} + +\item{conform_spec}{logical; whether to conform the spectra to a new +wavenumber range and resolution.} + +\item{conform_spec_args}{named list of arguments passed to +\code{\link{conform_spec}()}.} + +\item{restrict_range}{logical; indicating whether to restrict the wavenumber +range of the spectra.} + +\item{restrict_range_args}{named list of arguments passed to +\code{\link{restrict_range}()}.} + +\item{flatten_range}{logical; indicating whether to flatten the range around +the carbon dioxide region.} + +\item{flatten_range_args}{named list of arguments passed to +\code{\link{flatten_range}()}.} + +\item{subtr_baseline}{logical; indicating whether to subtract the baseline +from the spectra.} + +\item{subtr_baseline_args}{named list of arguments passed to +\code{\link{subtr_baseline}()}.} + +\item{smooth_intens}{logical; indicating whether to apply a smoothing filter +to the spectra.} + +\item{smooth_intens_args}{named list of arguments passed to +\code{\link{smooth_intens}()}.} + +\item{make_rel}{logical; if \code{TRUE} spectra are automatically normalized +with \code{\link{make_rel}()}.} + +\item{\ldots}{further arguments passed to subfunctions.} +} +\value{ +\code{process_spec()} returns an \code{OpenSpecy} object with preprocessed +spectra based on the specified parameters. +} +\description{ +\code{process_spec()} is a monolithic wrapper function for all spectral +preprocessing steps. +} +\examples{ +data("raman_hdpe") +plot(raman_hdpe) + +# Process spectra with range restriction and baseline subtraction +process_spec(raman_hdpe, + restrict_range = TRUE, + restrict_range_args = list(min = 500, max = 3000), + subtr_baseline = TRUE, + subtr_baseline_args = list(type = "polynomial", + polynomial = 8)) |> + lines(col = "darkred") + +# Process spectra with smoothing and derivative +process_spec(raman_hdpe, + smooth_intens = TRUE, + smooth_intens_args = list( + polynomial = 3, + window = 11, + derivative = 1 + ) + ) |> + lines(col = "darkgreen") + +} diff --git a/man/raman_hdpe.Rd b/man/raman_hdpe.Rd index e2e3b319..f323515e 100644 --- a/man/raman_hdpe.Rd +++ b/man/raman_hdpe.Rd @@ -5,27 +5,31 @@ \alias{raman_hdpe} \title{Sample Raman spectrum} \format{ -A data table containing 964 rows and 2 columns: +An threepart list of class \code{\link{OpenSpecy}} containing: \tabular{ll}{ -\code{wavenumber}: \tab spectral wavenumber [1/cm] \cr -\code{intensity}: \tab absorbance values [-] \cr +\code{wavenumber}: \tab spectral wavenumbers [1/cm] (vector of 964 rows) \cr +\code{spectra}: \tab absorbance values \link{-} +(a \code{\link[data.table]{data.table}} with 964 rows and 1 column) \cr +\code{metadata}: \tab spectral metadata \cr } } \description{ -Raman spectrum of high-density polyethylene (HDPE). +Raman spectrum of high-density polyethylene (HDPE) provided by +Horiba Scientific. } \examples{ -data("raman_hdpe") +data(raman_hdpe) +print(raman_hdpe) } \references{ -Cowger W, Gray A, Christiansen SH, Christiansen SH, Christiansen SH, +Cowger W, Steinmetz Z, Gray A, Christiansen SH, Christiansen SH, Christiansen SH, De Frond H, Deshpande AD, Hemabessiere L, Lee E, Mill L, et al. (2020). “Critical Review of Processing and Classification Techniques for Images and Spectra in Microplastic Research.” \emph{Applied Spectroscopy}, \strong{74}(9), 989–1010. \doi{10.1177/0003702820929064}. } \author{ -Win Cowger +Zacharias Steinmetz, Win Cowger } \keyword{data} diff --git a/man/read_envi.Rd b/man/read_envi.Rd new file mode 100644 index 00000000..c0c07254 --- /dev/null +++ b/man/read_envi.Rd @@ -0,0 +1,63 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/read_envi.R +\name{read_envi} +\alias{read_envi} +\title{Read ENVI data} +\usage{ +read_envi( + file, + header = NULL, + share = NULL, + metadata = list(file_name = basename(file), user_name = NULL, contact_info = NULL, + organization = NULL, citation = NULL, spectrum_type = NULL, spectrum_identity = NULL, + material_form = NULL, material_phase = NULL, material_producer = NULL, + material_purity = NULL, material_quality = NULL, material_color = NULL, + material_other = NULL, cas_number = NULL, instrument_used = NULL, + instrument_accessories = NULL, instrument_mode = NULL, spectral_resolution = NULL, + laser_light_used = NULL, number_of_accumulations = NULL, + + total_acquisition_time_s = NULL, data_processing_procedure = NULL, + level_of_confidence_in_identification = NULL, other_info = NULL, license = + "CC BY-NC"), + ... +) +} +\arguments{ +\item{file}{name of the binary file.} + +\item{header}{name of the ASCII header file. If \code{NULL}, the name of the +header file is guessed by looking for a second file with the same basename as +\code{file} but with .hdr extension.} + +\item{share}{defaults to \code{NULL}; needed to share spectra with the +Open Specy community; see \code{\link{share_spec}()} for details.} + +\item{metadata}{a named list of the metadata; see +\code{\link{as_OpenSpecy}()} for details.} + +\item{\ldots}{further arguments passed to the submethods.} +} +\value{ +An \code{OpenSpecy} object. +} +\description{ +This function allows ENVI data import. +} +\details{ +ENVI data usually consists of two files, an ASCII header and a binary data +file. The header contains all information necessary for correctly reading +the binary file via \code{\link[caTools]{read.ENVI}()}. +} +\seealso{ +\code{\link{read_spec}()} for reading .y(a)ml, .json, or .rds (OpenSpecy) +files; +\code{\link{read_text}()}, \code{\link{read_asp}()}, \code{\link{read_spa}()}, +\code{\link{read_spc}()}, and \code{\link{read_jdx}()} for text files, .asp, +.spa, .spa, .spc, and .jdx formats, respectively; +\code{\link{read_opus}()} for reading .0 (OPUS) files; +\code{\link{read_zip}()} and \code{\link{read_any}()} for wrapper functions; +\code{\link[caTools]{read.ENVI}()} +} +\author{ +Zacharias Steinmetz, Claudia Beleites +} diff --git a/man/read_ext.Rd b/man/read_ext.Rd new file mode 100644 index 00000000..60601518 --- /dev/null +++ b/man/read_ext.Rd @@ -0,0 +1,158 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/read_ext.R +\name{read_text} +\alias{read_text} +\alias{read_asp} +\alias{read_spa} +\alias{read_spc} +\alias{read_jdx} +\alias{read_extdata} +\title{Read spectral data} +\usage{ +read_text( + file, + colnames = NULL, + method = "fread", + share = NULL, + metadata = list(file_name = basename(file), user_name = NULL, contact_info = NULL, + organization = NULL, citation = NULL, spectrum_type = NULL, spectrum_identity = NULL, + material_form = NULL, material_phase = NULL, material_producer = NULL, + material_purity = NULL, material_quality = NULL, material_color = NULL, + material_other = NULL, cas_number = NULL, instrument_used = NULL, + instrument_accessories = NULL, instrument_mode = NULL, spectral_resolution = NULL, + laser_light_used = NULL, number_of_accumulations = NULL, + + total_acquisition_time_s = NULL, data_processing_procedure = NULL, + level_of_confidence_in_identification = NULL, other_info = NULL, license = + "CC BY-NC"), + ... +) + +read_asp( + file, + share = NULL, + metadata = list(file_name = basename(file), user_name = NULL, contact_info = NULL, + organization = NULL, citation = NULL, spectrum_type = NULL, spectrum_identity = NULL, + material_form = NULL, material_phase = NULL, material_producer = NULL, + material_purity = NULL, material_quality = NULL, material_color = NULL, + material_other = NULL, cas_number = NULL, instrument_used = NULL, + instrument_accessories = NULL, instrument_mode = NULL, spectral_resolution = NULL, + laser_light_used = NULL, number_of_accumulations = NULL, + + total_acquisition_time_s = NULL, data_processing_procedure = NULL, + level_of_confidence_in_identification = NULL, other_info = NULL, license = + "CC BY-NC"), + ... +) + +read_spa( + file, + share = NULL, + metadata = list(file_name = basename(file), user_name = NULL, contact_info = NULL, + organization = NULL, citation = NULL, spectrum_type = NULL, spectrum_identity = NULL, + material_form = NULL, material_phase = NULL, material_producer = NULL, + material_purity = NULL, material_quality = NULL, material_color = NULL, + material_other = NULL, cas_number = NULL, instrument_used = NULL, + instrument_accessories = NULL, instrument_mode = NULL, spectral_resolution = NULL, + laser_light_used = NULL, number_of_accumulations = NULL, + + total_acquisition_time_s = NULL, data_processing_procedure = NULL, + level_of_confidence_in_identification = NULL, other_info = NULL, license = + "CC BY-NC"), + ... +) + +read_spc( + file, + share = NULL, + metadata = list(file_name = basename(file), user_name = NULL, contact_info = NULL, + organization = NULL, citation = NULL, spectrum_type = NULL, spectrum_identity = NULL, + material_form = NULL, material_phase = NULL, material_producer = NULL, + material_purity = NULL, material_quality = NULL, material_color = NULL, + material_other = NULL, cas_number = NULL, instrument_used = NULL, + instrument_accessories = NULL, instrument_mode = NULL, spectral_resolution = NULL, + laser_light_used = NULL, number_of_accumulations = NULL, + + total_acquisition_time_s = NULL, data_processing_procedure = NULL, + level_of_confidence_in_identification = NULL, other_info = NULL, license = + "CC BY-NC"), + ... +) + +read_jdx( + file, + share = NULL, + metadata = list(file_name = basename(file), user_name = NULL, contact_info = NULL, + organization = NULL, citation = NULL, spectrum_type = NULL, spectrum_identity = NULL, + material_form = NULL, material_phase = NULL, material_producer = NULL, + material_purity = NULL, material_quality = NULL, material_color = NULL, + material_other = NULL, cas_number = NULL, instrument_used = NULL, + instrument_accessories = NULL, instrument_mode = NULL, spectral_resolution = NULL, + laser_light_used = NULL, number_of_accumulations = NULL, + + total_acquisition_time_s = NULL, data_processing_procedure = NULL, + level_of_confidence_in_identification = NULL, other_info = NULL, license = + "CC BY-NC"), + ... +) + +read_extdata(file = NULL) +} +\arguments{ +\item{file}{file to be read from or written to.} + +\item{colnames}{character vector of \code{length = 2} indicating the column +names for the wavenumber and intensity; if \code{NULL} columns are guessed.} + +\item{method}{submethod to be used for reading text files; defaults to +\code{\link[data.table]{fread}()} but \code{\link[utils]{read.csv}()} works +as well.} + +\item{share}{defaults to \code{NULL}; needed to share spectra with the +Open Specy community; see \code{\link{share_spec}()} for details.} + +\item{metadata}{a named list of the metadata; see +\code{\link{as_OpenSpecy}()} for details.} + +\item{\ldots}{further arguments passed to the submethods.} +} +\value{ +All \code{read_*()} functions return data frames containing two columns +named \code{"wavenumber"} and \code{"intensity"}. +} +\description{ +Functions for reading spectral data from external file types. +Currently supported reading formats are .csv and other text files, .asp, +.spa, .spc, and .jdx. +Additionally, .0 (OPUS) and .dat (ENVI) files are supported via +\code{\link{read_opus}()} and \code{\link{read_envi}()}, respectively. +\code{\link{read_zip}()} takes any of the files listed above. +Note that proprietary file formats like .0, .asp, and .spa are poorly +supported but will likely still work in most cases. +} +\details{ +\code{read_spc()} and \code{read_jdx()} are wrappers around the +functions provided by the \link[hyperSpec:hyperSpec-package]{hyperSpec}. +Other functions have been adapted various online sources. +Metadata is harvested if possible. +There are many unique iterations of spectral file formats so there may be +bugs in the file conversion. Please contact us if you identify any. +} +\examples{ +read_extdata("raman_hdpe.csv") |> read_text() +read_extdata("raman_atacamit.spc") |> read_spc() +read_extdata("ftir_ldpe_soil.asp") |> read_asp() +read_extdata("testdata_zipped.zip") |> read_zip() + +} +\seealso{ +\code{\link{read_spec}()} for reading .y(a)ml, .json, or .rds (OpenSpecy) +files; +\code{\link{read_opus}()} for reading .0 (OPUS) files; +\code{\link{read_envi}()} for reading .dat (ENVI) files; +\code{\link{read_zip}()} and \code{\link{read_any}()} for wrapper functions; +\code{\link[hyperSpec]{read.jdx}()}; \code{\link[hyperSpec]{read.spc}()} +} +\author{ +Zacharias Steinmetz, Win Cowger +} diff --git a/man/read_multi.Rd b/man/read_multi.Rd new file mode 100644 index 00000000..094ce56e --- /dev/null +++ b/man/read_multi.Rd @@ -0,0 +1,44 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/read_multi.R +\name{read_any} +\alias{read_any} +\alias{read_zip} +\title{Read spectral data from multiple files} +\usage{ +read_any(file, ...) + +read_zip(file, ...) +} +\arguments{ +\item{file}{file to be read from or written to.} + +\item{\ldots}{further arguments passed to the submethods.} +} +\value{ +All \code{read_*()} functions return \code{OpenSpecy} objects +} +\description{ +Wrapper functions for reading files in batch. +} +\details{ +\code{read_any()} provides a single function to quickly read in any of the +supported formats, it assumes that the file extension will tell it how to +process the spectra. +\code{read_zip()} provides functionality for reading in spectral map files +with ENVI file format or as individual files in a zip folder. If individual +files, spectra are concatenated. +} +\examples{ +read_extdata("raman_hdpe.csv") |> read_any() +read_extdata("ftir_ldpe_soil.asp") |> read_any() +read_extdata("ftir_ps.0") |> read_any() +read_extdata("testdata_zipped.zip") |> read_zip() +read_extdata("CA_tiny_map.zip") |> read_zip() + +} +\seealso{ +\code{\link{read_spec}()} +} +\author{ +Zacharias Steinmetz, Win Cowger +} diff --git a/man/read_opus.Rd b/man/read_opus.Rd new file mode 100644 index 00000000..f2338354 --- /dev/null +++ b/man/read_opus.Rd @@ -0,0 +1,91 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/read_opus.R +\name{read_opus} +\alias{read_opus} +\title{Read spectral data from Bruker OPUS binary files} +\usage{ +read_opus( + file, + share = NULL, + metadata = list(file_name = basename(file), user_name = NULL, contact_info = NULL, + organization = NULL, citation = NULL, spectrum_type = NULL, spectrum_identity = NULL, + material_form = NULL, material_phase = NULL, material_producer = NULL, + material_purity = NULL, material_quality = NULL, material_color = NULL, + material_other = NULL, cas_number = NULL, instrument_used = NULL, + instrument_accessories = NULL, instrument_mode = NULL, spectral_resolution = NULL, + laser_light_used = NULL, number_of_accumulations = NULL, + + total_acquisition_time_s = NULL, data_processing_procedure = NULL, + level_of_confidence_in_identification = NULL, other_info = NULL, license = + "CC BY-NC"), + type = "spec", + digits = 1L, + atm_comp_minus4offset = FALSE +) +} +\arguments{ +\item{file}{character vector with path to file(s).} + +\item{share}{defaults to \code{NULL}; needed to share spectra with the +Open Specy community; see \code{\link{share_spec}()} for details.} + +\item{metadata}{a named list of the metadata; see +\code{\link{as_OpenSpecy}()} for details.} + +\item{type}{character vector of spectra types to extract from OPUS binary +file. Default is \code{"spec"}, which will extract the final spectra, e.g. +expressed in absorbance (named \code{AB} in Bruker OPUS programs). Possible +additional values for the character vector supplied to \code{type} are +\code{"spec_no_atm_comp"} (spectrum of the sample without compensation for +atmospheric gases, water vapor and/or carbon dioxide), +\code{"sc_sample"} (single channel spectrum of the sample measurement), \code{"sc_ref"} +(single channel spectrum of the reference measurement), +\code{"ig_sample"} (interferogram of the sample measurement) and \code{"ig_ref"} +(interferogram of the reference measurement).} + +\item{digits}{Integer that specifies the number of decimal places used to +round the wavenumbers (values of x-variables).} + +\item{atm_comp_minus4offset}{Logical whether spectra after atmospheric +compensation are read with an offset of -4 bytes from Bruker OPUS files; +default is \code{FALSE}.} +} +\value{ +An \code{OpenSpecy} object. +} +\description{ +Read file(s) acquired with a Bruker Vertex FTIR Instrument. This function +is basically a fork of \code{opus_read()} from +\url{https://github.com/pierreroudier/opusreader}. +} +\details{ +The type of spectra returned by the function when using +\code{type = "spec"} depends on the setting of the Bruker instrument: typically, +it can be either absorbance or reflectance. + +The type of spectra to extract from the file can also use Bruker's OPUS +software naming conventions, as follows: +\itemize{ +\item \code{ScSm} corresponds to \code{sc_sample} +\item \code{ScRf} corresponds to \code{sc_ref} +\item \code{IgSm} corresponds to \code{ig_sample} +\item \code{IgRf} corresponds to \code{ig_ref} +} +} +\examples{ +read_extdata("ftir_ps.0") |> read_opus() + +} +\seealso{ +\code{\link{read_spec}()} for reading .y(a)ml, .json, or .rds (OpenSpecy) +files; +\code{\link{read_text}()}, \code{\link{read_asp}()}, \code{\link{read_spa}()}, +\code{\link{read_spc}()}, and \code{\link{read_jdx}()} for text files, .asp, +.spa, .spa, .spc, and .jdx formats, respectively; +\code{\link{read_text}()} for reading .dat (ENVI) files; +\code{\link{read_zip}()} and \code{\link{read_any}()} for wrapper functions; +\code{\link{read_opus_raw}()}; +} +\author{ +Philipp Baumann, Zacharias Steinmetz, Win Cowger +} diff --git a/man/read_opus_raw.Rd b/man/read_opus_raw.Rd new file mode 100644 index 00000000..d8bdf18f --- /dev/null +++ b/man/read_opus_raw.Rd @@ -0,0 +1,79 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/read_opus_raw.R +\name{read_opus_raw} +\alias{read_opus_raw} +\title{Read a Bruker OPUS spectrum binary raw string} +\usage{ +read_opus_raw(rw, type = "spec", atm_comp_minus4offset = FALSE) +} +\arguments{ +\item{rw}{a raw vector} + +\item{type}{character vector of spectra types to extract from OPUS binary +file. Default is \code{"spec"}, which will extract the final spectra, e.g. +expressed in absorbance (named \code{AB} in Bruker OPUS programs). Possible +additional values for the character vector supplied to \code{type} are +\code{"spec_no_atm_comp"} (spectrum of the sample without compensation for +atmospheric gases, water vapor and/or carbon dioxide), +\code{"sc_sample"} (single channel spectrum of the sample measurement), +\code{"sc_ref"} (single channel spectrum of the reference measurement), +\code{"ig_sample"} (interferogram of the sample measurement) and \code{"ig_ref"} +(interferogram of the reference measurement).} + +\item{atm_comp_minus4offset}{logical; whether spectra after atmospheric +compensation are read with an offset of -4 bytes from Bruker OPUS +files. Default is \code{FALSE}.} +} +\value{ +A list of 10 elements: + +\describe{ +\item{\code{metadata}}{a \code{data.frame} containing metadata from the OPUS file.} +\item{\code{spec}}{if \code{"spec"} was requested in the \code{type} option, a matrix of +the spectrum of the sample (otherwise set to \code{NULL}).} +\item{\code{spec_no_atm_comp}}{if \code{"spec_no_atm_comp"} was requested in the +\code{type} option, a matrix of the spectrum of the sample without atmospheric +compensation (otherwise set to \code{NULL}).} +\item{\code{sc_sample}}{if \code{"sc_sample"} was requested in the \code{type} option, a +matrix of the single channel spectrum of the sample (otherwise set to +\code{NULL}).} +\item{\code{sc_ref}}{if \code{"sc_ref"} was requested in the \code{type} option, a matrix +of the single channel spectrum of the reference (otherwise set to \code{NULL}).} +\item{\code{ig_sample}}{if \code{"ig_sample"} was requested in the \code{type} option, a +matrix of the interferogram of the sample (otherwise set to \code{NULL}).} +\item{\code{ig_ref}}{if \code{"ig_ref"} was requested in the \code{type} option, a matrix +of the interferogram of the reference (otherwise set to \code{NULL}).} +\item{\code{wavenumbers}}{if \code{"spec"} or \code{"spec_no_atm_comp"} was requested in +the \code{type} option, a numeric vector of the wavenumbers of the spectrum of +the sample (otherwise set to \code{NULL}).} +\item{\code{wavenumbers_sc_sample}}{if \code{"sc_sample"} was requested in the \code{type} +option, a numeric vector of the wavenumbers of the single channel spectrum +of the sample (otherwise set to \code{NULL}).} +\item{\code{wavenumbers_sc_ref}}{if \code{"sc_ref"} was requested in the \code{type} +option, a numeric vector of the wavenumbers of the single channel spectrum +of the reference (otherwise set to \code{NULL}).} +} +} +\description{ +Read single binary acquired with an Bruker Vertex FTIR Instrument +} +\details{ +The type of spectra returned by the function when using +\code{type = "spec"} depends on the setting of the Bruker instrument: typically, +it can be either absorbance or reflectance. + +The type of spectra to extract from the file can also use Bruker's OPUS +software naming conventions, as follows: +\itemize{ +\item \code{ScSm} corresponds to \code{sc_sample} +\item \code{ScRf} corresponds to \code{sc_ref} +\item \code{IgSm} corresponds to \code{ig_sample} +\item \code{IgRf} corresponds to \code{ig_ref} +} +} +\seealso{ +\code{\link{read_opus}()} +} +\author{ +Philipp Baumann and Pierre Roudier +} diff --git a/man/read_spec.Rd b/man/read_spec.Rd deleted file mode 100644 index 3d040c4e..00000000 --- a/man/read_spec.Rd +++ /dev/null @@ -1,106 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/read_spec.R -\name{read_text} -\alias{read_text} -\alias{read_asp} -\alias{read_spa} -\alias{read_jdx} -\alias{read_spc} -\alias{read_0} -\alias{read_extdata} -\title{Read spectral data} -\usage{ -read_text( - file = ".", - cols = NULL, - method = "read.csv", - share = NULL, - id = paste(digest(Sys.info()), digest(sessionInfo()), sep = "/"), - ... -) - -read_asp( - file = ".", - share = NULL, - id = paste(digest(Sys.info()), digest(sessionInfo()), sep = "/"), - ... -) - -read_spa( - file = ".", - share = NULL, - id = paste(digest(Sys.info()), digest(sessionInfo()), sep = "/"), - ... -) - -read_jdx( - file = ".", - share = NULL, - id = paste(digest(Sys.info()), digest(sessionInfo()), sep = "/"), - ... -) - -read_spc( - file = ".", - share = NULL, - id = paste(digest(Sys.info()), digest(sessionInfo()), sep = "/"), - ... -) - -read_0( - file = ".", - share = NULL, - id = paste(digest(Sys.info()), digest(sessionInfo()), sep = "/"), - ... -) - -read_extdata(file = NULL) -} -\arguments{ -\item{file}{file to be read from.} - -\item{cols}{character vector of \code{length = 2} indicating the colum names -for the wavenumber and intensity; if \code{NULL} columns are guessed.} - -\item{method}{submethod to be used for reading text files; defaults to -\link[utils]{read.csv} but \link[data.table]{fread} works as well.} - -\item{share}{defaults to \code{NULL}; needed to share spectra with the -Open Specy community; see \code{\link{share_spec}()} for details.} - -\item{id}{a unique user and/or session ID; defaults to -\code{paste(digest(Sys.info()), digest(sessionInfo()), sep = "/")}.} - -\item{\ldots}{further arguments passed to the submethods.} -} -\value{ -All \code{read_*()} functions return data frames containing two columns -named \code{"wavenumber"} and \code{"intensity"}. -} -\description{ -Functions for reading spectral data types including .asp, .jdx, -.spc, .spa, .0, and .csv. -} -\details{ -\code{read_spc()} and \code{read_jdx()} are just a wrapper around the -functions provided by the \link[hyperSpec:hyperSpec-package]{hyperSpec} -package. -Other functions have been adapted various online sources. -All functions convert datasets to a 2 column table with one column labeled -"wavenumber" and the other "intensity". There are many unique iterations of -spectral file formats so there may be bugs in the file conversion. -Please contact us if you identify any. -} -\examples{ -read_text(read_extdata("raman_hdpe.csv")) -read_asp(read_extdata("ftir_ldpe_soil.asp")) -read_0(read_extdata("ftir_ps.0")) - -} -\seealso{ -\code{\link[hyperSpec]{read.jdx}()}; \code{\link[hyperSpec]{read.spc}()}; -\code{\link[hexView]{readRaw}()}; \code{\link{share_spec}()} -} -\author{ -Zacharias Steinmetz, Win Cowger -} diff --git a/man/run_app.Rd b/man/run_app.Rd index 9943ce5a..5d333e1b 100644 --- a/man/run_app.Rd +++ b/man/run_app.Rd @@ -4,23 +4,24 @@ \alias{run_app} \title{Run Open Specy app} \usage{ -run_app(app_dir = "system", path = "system", log = TRUE, ...) +run_app(path = "system", log = TRUE, ref = "main", test_mode = FALSE, ...) } \arguments{ -\item{app_dir}{the app to run; defaults to \code{"system"} pointing to -\code{system.file("shiny", package = "OpenSpecy")}.} - -\item{path}{where to look for the local library files; defaults to -\code{"system"} pointing to -\code{system.file("extdata", package = "OpenSpecy")}.} +\item{path}{to store the downloaded app files; defaults to \code{"system"} +pointing to \code{system.file(package = "OpenSpecy")}.} \item{log}{logical; enables/disables logging to \code{\link[base]{tempdir}()}} +\item{ref}{git reference; could be a commit, tag, or branch name. Defaults to +"main". Only change this in case of errors.} + +\item{test_mode}{logical; for internal testing only.} + \item{\dots}{arguments passed to \code{\link[shiny]{runApp}()}.} } \value{ This function normally does not return any value, see -\code{\link[shiny]{runApp}()}. +\code{\link[shiny]{runGitHub}()}. } \description{ This wrapper function starts the graphical user interface of Open Specy. @@ -36,7 +37,7 @@ run_app() } \seealso{ -\code{\link[shiny]{runApp}()} +\code{\link[shiny]{runGitHub}()} } \author{ Zacharias Steinmetz diff --git a/man/share_spec.Rd b/man/share_spec.Rd index 0938acba..796ac64f 100644 --- a/man/share_spec.Rd +++ b/man/share_spec.Rd @@ -3,47 +3,28 @@ \name{share_spec} \alias{share_spec} \alias{share_spec.default} -\alias{share_spec.data.frame} +\alias{share_spec.OpenSpecy} \title{Share data with the Open Specy community} \usage{ -share_spec(data, ...) +share_spec(x, ...) -\method{share_spec}{default}(data, ...) +\method{share_spec}{default}(x, ...) -\method{share_spec}{data.frame}( - data, - metadata = c(user_name = "", contact_info = "", organization = "", citation = "", - spectrum_type = "", spectrum_identity = "", material_form = "", material_phase = "", - material_producer = "", material_purity = "", material_quality = "", material_color = - "", material_other = "", cas_number = "", instrument_used = "", - instrument_accessories = "", instrument_mode = "", spectral_resolution = "", - laser_light_used = "", number_of_accumulations = "", total_acquisition_time_s = "", - data_processing_procedure = "", level_of_confidence_in_identification = "", - - other_info = "", license = "CC BY-NC"), - file = NULL, - share = "system", - id = paste(digest(Sys.info()), digest(sessionInfo()), sep = "/"), - ... -) +\method{share_spec}{OpenSpecy}(x, file = NULL, share = "system", credentials = NULL, ...) } \arguments{ -\item{data}{a data frame containing the spectral data; columns should be -named \code{"wavenumber"} and \code{"intensity"}.} - -\item{metadata}{a named vector of the metadata to share; see details below.} +\item{x}{a list object of class \code{OpenSpecy}.} \item{file}{file to share (optional).} \item{share}{accepts any local directory to save the spectrum for later -sharing via e-mail to \email{wincowger@gmail.com}; -\code{"system"} (default) uses the Open Specy package directory at -\code{system.file("extdata", package = "OpenSpecy")}; -if a correct API token exists, \code{"dropbox"} shares the spectrum with the -cloud.} +sharing via email to \email{wincowger@gmail.com}; \code{"system"} (default) +uses the Open Specy package directory at \code{system.file("extdata", +package = "OpenSpecy")}; if a correct API token exists, \code{"cloud"} +shares the spectrum with the cloud.} -\item{id}{a unique user and/or session ID; defaults to -\code{paste(digest(Sys.info()), digest(sessionInfo()), sep = "/")}.} +\item{credentials}{a named list of credentials for cloud sharing; required if +\code{share = "cloud"}).} \item{\ldots}{further arguments passed to the submethods.} } @@ -54,76 +35,21 @@ cloud.} This helper function shares spectral data and metadata with the Open Specy community. -\strong{Please note} that \code{share_spec()} only provides basic sharing +\strong{Please note:} that \code{share_spec()} only provides basic sharing functionality if used interactively. This means that files are only formatted -and saved for sharing but are not send automatically. This only works with +and saved for sharing but are not sent automatically. This only works with hosted instances of Open Specy. } -\details{ -The \code{metadata} argument may contain a named vector with the following -details (\code{*} = mandatory): - -\tabular{ll}{ -\code{user_name*}: \tab User name, e.g. "Win Cowger"\cr -\code{contact_info}: \tab Contact information, e.g. "1-513-673-8956, -wincowger@gmail.com"\cr -\code{organization}: \tab Affiliation, e.g. "University of California, -Riverside"\cr -\code{citation}: \tab Data citation, e.g. "Primpke, S., Wirth, M., Lorenz, -C., & Gerdts, G. (2018). Reference database design for the automated analysis -of microplastic samples based on Fourier transform infrared (FTIR) -spectroscopy. \emph{Analytical and Bioanalytical Chemistry}. -\doi{10.1007/s00216-018-1156-x}"\cr -\code{spectrum_type*}: \tab Raman or FTIR\cr -\code{spectrum_identity*}: \tab Material/polymer analyzed, e.g. -"Polystyrene"\cr -\code{material_form}: \tab Form of the material analyzed, e.g. textile fiber, -rubber band, sphere, granule \cr -\code{material_phase}: \tab Phase of the material analyzed (liquid, gas, -solid) \cr -\code{material_producer}: \tab Producer of the material analyzed, -e.g. Dow \cr -\code{material_purity}: \tab Purity of the material analyzed, e.g. 99.98\% -\cr -\code{material_quality}: \tab Quality of the material analyzed, e.g. -consumer product, manufacturer material, analytical standard, -environmental sample \cr -\code{material_color}: \tab Color of the material analyzed, -e.g. blue, #0000ff, (0, 0, 255) \cr -\code{material_other}: \tab Other material description, e.g. 5 µm diameter -fibers, 1 mm spherical particles \cr -\code{cas_number}: \tab CAS number, e.g. 9003-53-6 \cr -\code{instrument_used}: \tab Instrument used, e.g. Horiba LabRam \cr -\code{instrument_accessories}: \tab Instrument accessories, e.g. -Focal Plane Array, CCD\cr -\code{instrument_mode}: \tab Instrument modes/settings, e.g. -transmission, reflectance \cr -\code{spectral_resolution}: \tab Spectral resolution, e.g. 4/cm \cr -\code{laser_light_used}: \tab Wavelength of the laser/light used, e.g. -785 nm \cr -\code{number_of_accumulations}: \tab Number of accumulations, e.g 5 \cr -\code{total_acquisition_time_s}: \tab Total acquisition time (s), e.g. 10 s -\cr -\code{data_processing_procedure}: \tab Data processing procedure, -e.g. spikefilter, baseline correction, none \cr -\code{level_of_confidence_in_identification}: \tab Level of confidence in -identification, e.g. 99\% \cr -\code{other_info}: \tab Other information \cr -\code{license}: \tab The license of the shared spectrum; defaults to -\code{"CC BY-NC"} (see -\url{https://creativecommons.org/licenses/by-nc/4.0/} for details). Any other -creative commons license is allowed, for example, CC0 or CC BY \cr -} -} \examples{ \dontrun{ data("raman_hdpe") share_spec(raman_hdpe, - metadata = c(user_name = "Win Cowger", - spectrum_type = "FTIR", - spectrum_identity = "PE", - license = "CC BY-NC"), - share = tempdir()) + metadata = list( + user_name = "Win Cowger", + spectrum_type = "FTIR", + spectrum_identity = "PE", + license = "CC BY-NC" + )) } } diff --git a/man/sig_noise.Rd b/man/sig_noise.Rd new file mode 100644 index 00000000..3688cba2 --- /dev/null +++ b/man/sig_noise.Rd @@ -0,0 +1,45 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/sig_noise.R +\name{sig_noise} +\alias{sig_noise} +\alias{sig_noise.default} +\alias{sig_noise.OpenSpecy} +\title{Calculate signal and noise metrics for OpenSpecy objects} +\usage{ +sig_noise(x, ...) + +\method{sig_noise}{default}(x, ...) + +\method{sig_noise}{OpenSpecy}(x, metric = "sig_over_noise", na.rm = TRUE, ...) +} +\arguments{ +\item{x}{an \code{OpenSpecy} object.} + +\item{metric}{character; specifying the desired metric to calculate. +Options include \code{"sig"} (mean intensity), \code{"noise"} (standard +deviation of intensity), \code{"sig_times_noise"} (absolute value of +signal times noise), \code{"sig_over_noise"} (absolute value of signal / +noise), or \code{"tot_sig"} (total signal = signal * number of data +points).} + +\item{na.rm}{logical; indicating whether missing values should be removed +when calculating signal and noise. Default is \code{TRUE}.} + +\item{\ldots}{further arguments passed to subfunctions; currently not used.} +} +\value{ +A numeric vector containing the calculated metric for each spectrum in the +\code{OpenSpecy} object. +} +\description{ +This function calculates common signal and noise metrics for \code{OpenSpecy} +objects. +} +\examples{ +data("raman_hdpe") + +sig_noise(raman_hdpe, metric = "sig") +sig_noise(raman_hdpe, metric = "noise") +sig_noise(raman_hdpe, metric = "sig_times_noise") + +} diff --git a/man/smooth_intens.Rd b/man/smooth_intens.Rd index 005eaf25..afed0c95 100644 --- a/man/smooth_intens.Rd +++ b/man/smooth_intens.Rd @@ -2,34 +2,37 @@ % Please edit documentation in R/smooth_intens.R \name{smooth_intens} \alias{smooth_intens} -\alias{smooth_intens.formula} -\alias{smooth_intens.data.frame} \alias{smooth_intens.default} +\alias{smooth_intens.OpenSpecy} \title{Smooth spectral intensities} \usage{ smooth_intens(x, ...) -\method{smooth_intens}{formula}(formula, data = NULL, ...) +\method{smooth_intens}{default}(x, ...) -\method{smooth_intens}{data.frame}(x, ...) - -\method{smooth_intens}{default}(x, y, p = 3, n = 11, make_rel = TRUE, ...) +\method{smooth_intens}{OpenSpecy}( + x, + polynomial = 3, + window = 11, + derivative = 0, + abs = FALSE, + make_rel = TRUE, + ... +) } \arguments{ -\item{x}{a numeric vector containing the spectral wavenumbers; alternatively -a data frame containing spectral data as \code{"wavenumber"} and -\code{"intensity"} can be supplied.} - -\item{formula}{an object of class '\code{\link[stats]{formula}}' of the form -\code{intensity ~ wavenumber}.} +\item{x}{an object of class \code{OpenSpecy}.} -\item{data}{a data frame containing the variables in \code{formula}.} +\item{polynomial}{polynomial order for the filter} -\item{y}{a numeric vector containing the spectral intensities.} +\item{window}{number of data points in the window, filter length (must be +odd).} -\item{p}{polynomial order for the filter} +\item{derivative}{the derivative order if you want to calculate the +derivative. Zero (default) is no derivative.} -\item{n}{number of data points in the window, filter length (must be odd).} +\item{abs}{logical; whether you want to calculate the absolute value of the +resulting output.} \item{make_rel}{logical; if \code{TRUE} spectra are automatically normalized with \code{\link{make_rel}()}.} @@ -37,8 +40,7 @@ with \code{\link{make_rel}()}.} \item{\ldots}{further arguments passed to \code{\link[signal]{sgolay}()}.} } \value{ -\code{smooth_intens()} returns a data frame containing two columns named -\code{"wavenumber"} and \code{"intensity"}. +\code{smooth_intens()} returns an \code{OpenSpecy} object. } \description{ This smoother can enhance the signal to noise ratio of the data and uses a diff --git a/man/spec_res.Rd b/man/spec_res.Rd index fc733ccd..b71a7341 100644 --- a/man/spec_res.Rd +++ b/man/spec_res.Rd @@ -2,13 +2,21 @@ % Please edit documentation in R/spec_res.R \name{spec_res} \alias{spec_res} +\alias{spec_res.default} +\alias{spec_res.OpenSpecy} \title{Spectral resolution} \usage{ -spec_res(x) +spec_res(x, ...) + +\method{spec_res}{default}(x, ...) + +\method{spec_res}{OpenSpecy}(x, ...) } \arguments{ -\item{x}{a numeric vector or an \R object which is coercible to one by -\code{as.vector(x, "numeric")}; \code{x} should be \code{wavenumber} data.} +\item{x}{a numeric vector with \code{wavenumber} data or an \code{OpenSpecy} +object.} + +\item{\ldots}{further arguments passed to subfunctions; currently not used.} } \value{ \code{spec_res()} returns a single numeric value. @@ -24,7 +32,8 @@ distinguished. } \examples{ data("raman_hdpe") -spec_res(raman_hdpe$wavenumber) + +spec_res(raman_hdpe) } \author{ diff --git a/man/subtr_bg.Rd b/man/subtr_baseline.Rd similarity index 61% rename from man/subtr_bg.Rd rename to man/subtr_baseline.Rd index 3359acdf..9387d5f7 100644 --- a/man/subtr_bg.Rd +++ b/man/subtr_baseline.Rd @@ -1,31 +1,31 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/subtr_bg.R -\name{subtr_bg} -\alias{subtr_bg} -\alias{subtr_bg.formula} -\alias{subtr_bg.data.frame} -\alias{subtr_bg.default} +% Please edit documentation in R/subtr_baseline.R +\name{subtr_baseline} +\alias{subtr_baseline} +\alias{subtr_baseline.default} +\alias{subtr_baseline.OpenSpecy} \title{Automated background subtraction for spectral data} \usage{ -subtr_bg(x, ...) +subtr_baseline(x, ...) -\method{subtr_bg}{formula}(formula, data = NULL, ...) +\method{subtr_baseline}{default}(x, ...) -\method{subtr_bg}{data.frame}(x, ...) - -\method{subtr_bg}{default}(x, y, degree = 8, raw = FALSE, make_rel = TRUE, ...) +\method{subtr_baseline}{OpenSpecy}( + x, + type = "polynomial", + degree = 8, + raw = FALSE, + baseline, + make_rel = TRUE, + ... +) } \arguments{ -\item{x}{a numeric vector containing the spectral wavenumbers; alternatively -a data frame containing spectral data as \code{"wavenumber"} and -\code{"intensity"} can be supplied.} - -\item{formula}{an object of class '\code{\link[stats]{formula}}' of the form -\code{intensity ~ wavenumber}.} +\item{x}{a list object of class \code{OpenSpecy}.} -\item{data}{a data frame containing the variables in \code{formula}.} - -\item{y}{a numeric vector containing the spectral intensities.} +\item{type}{one of \code{"polynomial"} or \code{"manual"} depending on +whether you want spectra to be corrected with a manual baseline or with +polynomial baseline fitting.} \item{degree}{the degree of the polynomial. Must be less than the number of unique points when raw is \code{FALSE}. Typically a good fit can be @@ -33,18 +33,21 @@ found with a 8th order polynomial.} \item{raw}{if \code{TRUE}, use raw and not orthogonal polynomials.} +\item{baseline}{an \code{OpenSpecy} object containing the baseline data to be +subtracted.} + \item{make_rel}{logical; if \code{TRUE} spectra are automatically normalized with \code{\link{make_rel}()}.} \item{\ldots}{further arguments passed to \code{\link[stats]{poly}()}.} } \value{ -\code{subtr_bg()} returns a data frame containing two columns named +\code{subtr_baseline()} returns a data frame containing two columns named \code{"wavenumber"} and \code{"intensity"}. } \description{ This baseline correction routine iteratively finds the baseline of a spectrum -using a polynomial fitting. +using a polynomial fitting or accepts a manual baseline. } \details{ This is a translation of Michael Stephen Chen's MATLAB code written for the @@ -53,7 +56,7 @@ This is a translation of Michael Stephen Chen's MATLAB code written for the \examples{ data("raman_hdpe") -subtr_bg(raman_hdpe) +subtr_baseline(raman_hdpe) } \references{ diff --git a/man/test_lib.Rd b/man/test_lib.Rd index 2f75bd8b..58027c47 100644 --- a/man/test_lib.Rd +++ b/man/test_lib.Rd @@ -5,22 +5,17 @@ \alias{test_lib} \title{Test reference library} \format{ -A list named \code{"test"} with two elements: - -\tabular{ll}{ -\code{metadata}: \tab metadata of 34 Raman spectra \cr -\code{library}: \tab all reference spectra, \code{sample_name} serves as -identifier \cr -} +An \code{OpenSpecy} object; \code{sample_name} is the class of the spectra. } \description{ -Reference library of 34 Raman spectra used for internal testing. +Reference library with 29 FTIR and 28 Raman spectra used for examples and +internal testing. } \examples{ data("test_lib") } \author{ -Jennifer Lynch +Win Cowger } \keyword{data} diff --git a/tests/testthat/test-adj_intens.R b/tests/testthat/test-adj_intens.R index 25301868..39ed63e4 100644 --- a/tests/testthat/test-adj_intens.R +++ b/tests/testthat/test-adj_intens.R @@ -1,16 +1,20 @@ data("raman_hdpe") +raman_hdpe$spectra$intensity2 <- raman_hdpe$spectra$intensity * 2 test_that("adj_intens() works as expected", { expect_silent(adj <- adj_intens(raman_hdpe)) - expect_identical(adj, adj_intens(raman_hdpe$wavenumber, - raman_hdpe$intensity)) - expect_identical(adj, adj_intens(intensity ~ wavenumber, raman_hdpe)) - expect_equal(as.numeric( - cor(raman_hdpe[2], adj_intens(raman_hdpe)[2]) - ), 1, ignore_attr = F) - expect_s3_class(adj, "data.frame") - expect_equal(names(adj), c("wavenumber", "intensity")) - expect_equal(nrow(adj), nrow(raman_hdpe)) - expect_equal(adj[1], raman_hdpe[1]) - expect_equal(range(adj[2]), c(0, 1)) + expect_silent(adj_intens(raman_hdpe, type = "reflectance")) + expect_silent(adj_intens(raman_hdpe, type = "transmittance")) + expect_equal( + cor(raman_hdpe$spectra$intensity, adj$spectra$intensity), 1, + ignore_attr = F) + expect_equal( + cor(raman_hdpe$spectra$intensity, adj$spectra$intensity2), 1, + ignore_attr = F) + + expect_s3_class(adj, "OpenSpecy") + + expect_equal(nrow(adj$spectra), nrow(raman_hdpe$spectra)) + expect_equal(adj$wavenumber, raman_hdpe$wavenumber) + expect_equal(adj$spectra |> range(), c(0, 1)) }) diff --git a/tests/testthat/test-adj_range.R b/tests/testthat/test-adj_range.R new file mode 100644 index 00000000..6e09a892 --- /dev/null +++ b/tests/testthat/test-adj_range.R @@ -0,0 +1,58 @@ +library(data.table) + +test_that("restrict_range() provides correct range", { + test_noise <- as_OpenSpecy(x = seq(400,4000, by = 10), + spectra = data.table(intensity = rnorm(361))) + single_range <- restrict_range(test_noise, min = 1000, + max = 2000) |> + expect_silent() + + double_range <- restrict_range(test_noise, min = c(1000, 2000), + max = c(1500, 2500)) |> + expect_silent() + + is_OpenSpecy(single_range) |> expect_true() + expect_identical(single_range$wavenumber, seq(1000,2000, by = 10)) + expect_identical(double_range$wavenumber, c(seq(1000,1500, by = 10), + seq(2000,2500, by = 10))) +}) + +test_that("flatten_range() function test", { + sam <- as_OpenSpecy(x = 1:10, spectra = data.table(V1 = 1:10)) + flat_sam <- flatten_range(sam, min = c(4, 7), max = c(5, 10), + make_rel = F) |> + expect_silent() + + expect_equal(flat_sam$spectra$V1[4:5], c(4.5, 4.5)) + expect_equal(flat_sam$spectra$V1[7:10], c(8.5, 8.5, 8.5, 8.5)) + + data("raman_hdpe") + flat_hdpe <- flatten_range(raman_hdpe, min = c(500, 1000), + max = c(700, 1500)) |> + expect_silent() + expect_equal(flat_hdpe$spectra$intensity[1:50], + make_rel(raman_hdpe$spectra$intensity)[1:50]) + expect_equal(flat_hdpe$spectra$intensity[60:100] |> unique() |> round(6), + 0.036709) + + tiny_map <- read_extdata("CA_tiny_map.zip") |> read_zip() + flat_map <- flatten_range(tiny_map, min = c(1000, 2000), + max = c(1200, 2400), make_rel = F) |> + expect_silent() + + expect_false(all.equal(flat_map$spectra, tiny_map$spectra) |> isTRUE()) + expect_equal(flat_map$spectra[1:20], tiny_map$spectra[1:20]) + + expect_equal(flat_map$spectra[40:60, 1:5] |> unique() |> round(4) + |> as.numeric(), + c(-0.8694, -1.246, -0.8304, -1.1909, -0.7857)) +}) + +test_that("flatten_range() error handling", { + test <- as_OpenSpecy(x = 1:10, spectra = data.table(V1 = 1:10)) + + expect_error(flatten_range(test)) + expect_error(flatten_range(test, min = c(1000), + max = c(2000, 3000))) + expect_error(flatten_range(test, min = c(2000), max = c(1000))) +}) diff --git a/tests/testthat/test-as_OpenSpecy.R b/tests/testthat/test-as_OpenSpecy.R new file mode 100644 index 00000000..61369532 --- /dev/null +++ b/tests/testthat/test-as_OpenSpecy.R @@ -0,0 +1,101 @@ +library(data.table) + +df <- read_extdata("raman_hdpe.csv") |> read.csv() + +test_that("as_OpenSpecy() generates OpenSpecy objects", { + expect_silent(as_OpenSpecy(df)) + + expect_silent(osf <- as_OpenSpecy(df)) + expect_silent(ost <- data.table(df) |> as_OpenSpecy()) + expect_silent(osl <- list(df$wavenumber, df[2]) |> as_OpenSpecy()) + expect_silent(OpenSpecy(osf)) + + expect_s3_class(osf, "OpenSpecy") + expect_s3_class(ost, "OpenSpecy") + expect_s3_class(osl, "OpenSpecy") + + expect_equal(names(osf), c("wavenumber", "spectra", "metadata")) + expect_equal(names(ost), c("wavenumber", "spectra", "metadata")) + expect_equal(names(osl), c("wavenumber", "spectra", "metadata")) + + expect_equal(OpenSpecy(df), OpenSpecy(osf)) + expect_equal(ost$spectra, osf$spectra) + expect_equal(ost$wavenumber, osf$wavenumber) + expect_equal(ost$metadata, osf$metadata) + + expect_true(is_OpenSpecy(osf)) + expect_true(is_OpenSpecy(ost)) + expect_true(is_OpenSpecy(osl)) + expect_false(is_OpenSpecy(df)) +}) + +test_that("as_OpenSpecy() handles errors correctly", { + expect_silent(as_OpenSpecy(df$wavenumber, as.data.frame(df$intensity))) + expect_error(as_OpenSpecy(df$wavenumber, df$intensity)) + expect_error(as_OpenSpecy(df$wavenumber)) + expect_error(as_OpenSpecy(df$wavenumber, as.data.frame(df$intensity[-1]))) + expect_error(as_OpenSpecy(df$wavenumber, + data.table(intensity = df$intensity, + intensity = df$intensity))) + + expect_warning(as_OpenSpecy(data.frame(x = df$wavenumber, + abs = df$intensity))) + expect_warning(as_OpenSpecy(data.frame(wav = df$wavenumber, + y = df$intensity))) + + expect_error(as_OpenSpecy(df$wavenumber, as.data.frame(df$intensity), + coords = "")) + expect_error(as_OpenSpecy(df$wavenumber, as.data.frame(df$intensity), + coords = df)) + expect_error(as_OpenSpecy(df$wavenumber, as.data.frame(df$intensity), + metadata = "")) +}) + +test_that("check_OpenSpecy() work as expected", { + os <- as_OpenSpecy(df) + check_OpenSpecy(os) |> expect_true() + + check_OpenSpecy(df) |> expect_error() + + osv <- osn <- oss <- ost <- osl <- os + + osv$wavenumber <- list(osv$wavenumber) + check_OpenSpecy(osv) |> expect_false() |> expect_warning() + + names(osn) <- 1:3 + check_OpenSpecy(osn) |> expect_error() + + oss$wavenumber <- sample(oss$wavenumber) + check_OpenSpecy(oss) |> expect_false() |> expect_message() + + class(ost$metadata) <- class(ost$spectra) <- "data.frame" + check_OpenSpecy(ost) |> expect_false() |> expect_message() |> expect_message() + + osl$metadata <- rbind(osl$metadata, osl$metadata) + osl$spectra <- osl$spectra[-1] + + check_OpenSpecy(osl) |> expect_false() |> expect_warning() |> expect_warning() +}) + +test_that("'OpenSpecy' objects are read correctly", { + os <- as_OpenSpecy(df) + + expect_equal(range(os$wavenumber) |> round(2), c(301.04, 3198.12)) + expect_equal(range(os$spectra) |> round(2), c(26, 816)) + expect_length(os$wavenumber, 964) + expect_equal(os$metadata$x, 1) + expect_equal(os$metadata$y, 1) + expect_equal(os$metadata$license, "CC BY-NC") +}) + +test_that("'OpenSpecy' objects are transcribed to and from 'hyperSpec' objects", { + os <- as_OpenSpecy(df) + hyper <- as_hyperSpec(os) + expect_s4_class(hyper, "hyperSpec") + + openhyper <- as_OpenSpecy(hyper) + + expect_true(is_OpenSpecy(openhyper)) + expect_equal(openhyper$wavenumber, hyper@wavelength) + expect_equal(unlist(openhyper$spectra$V1),unname(t(hyper$spc)[,1])) +}) diff --git a/tests/testthat/test-conform_spec.R b/tests/testthat/test-conform_spec.R new file mode 100644 index 00000000..0cc8b172 --- /dev/null +++ b/tests/testthat/test-conform_spec.R @@ -0,0 +1,49 @@ +library(data.table) + +data("raman_hdpe") + +test_that("conform_spec() throws an error for non-OpenSpecy objects", { + # Create a non-OpenSpecy object + non_OpenSpecy <- data.frame(wavenumber = c(1000, 1100, 1200), + intensity = c(0.1, 0.2, 0.3)) + + conform_spec(non_OpenSpecy, c(1000, 2000)) |> + expect_error() +}) + +test_that("conform_spec() handles errors correctly", { + conform_spec(raman_hdpe, range = c(min(raman_hdpe$wavenumber + 10), + max(raman_hdpe$wavenumber))) |> + expect_silent() + conform_spec(raman_hdpe, range = c(0,5000)) |> + expect_silent() + expect_equal(conform_spec(raman_hdpe, c(1000, 2000))$wavenumber, + seq(1000, 2000, by = 5)) + conform_spec(raman_hdpe, c(min(raman_hdpe$wavenumber + 10))) |> + expect_error() + conform_spec(raman_hdpe, c(min(raman_hdpe$wavenumber + 10), + max(raman_hdpe$wavenumber)), res = 0) |> + expect_error() +}) + +test_that("conform_spec() conforms wavenumbers correctly", { + sam <- as_OpenSpecy(wn <- seq(1000, 2000, 5), + data.table(intensity = rnorm(length(wn)))) + new_wavenumbers <- c(1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900) + conf_new <- conform_spec(sam, new_wavenumbers) |> + expect_silent() + conf_roll <- conform_spec(sam, new_wavenumbers,res = NULL, type = "roll") |> + expect_silent() + + expect_equal(length(conf_new$wavenumber), length(conf_new$spectra[[1]])) + expect_equal(range(conf_new$wavenumber), range(new_wavenumbers)) + + expect_identical(conf_roll$wavenumber, new_wavenumbers) + + expect_s3_class(conf_new, "OpenSpecy") + expect_s3_class(conf_roll, "OpenSpecy") + + conform_spec(raman_hdpe)$spectra$intensity[c(63, 143, 283, 325, 402)] |> + round(2) |> + expect_equal(c(78.84, 65.00, 105.73, 109.41, 116.00)) +}) diff --git a/tests/testthat/test-data_norm.R b/tests/testthat/test-data_norm.R index 73226aac..fd88d10b 100644 --- a/tests/testthat/test-data_norm.R +++ b/tests/testthat/test-data_norm.R @@ -10,3 +10,22 @@ test_that("make_rel() give correct output", { expect_equal(round(make_rel(c(965.86, 82.75, -458.28, -0.98, -62.94)), 4), c(1.0000, 0.3799, 0.0000, 0.3211, 0.2776)) }) + +test_that("adj_res() function test", { + expect_equal(adj_res(seq(500, 4000, 4), 5), round(seq(500, 4000, 4)/5)*5) +}) + +test_that("conform_res() function test", { + x <- seq(500, 4000, 4) + expect_equal(conform_res(x, 5), seq(floor(min(x)/5)*5, ceiling(max(x)/5)*5, by=5)) +}) + +test_that("adj_neg() function test", { + expect_equal(adj_neg(c(-1000, -1, 0, 1, 10)), c(1, 1000, 1001, 1002, 1011)) + expect_equal(adj_neg(c(1, 2, 3)), c(1, 2, 3)) +}) + +test_that("make_rel() function test", { + expect_equal(make_rel(c(1, 2, 3)), c(0, 0.5, 1)) + expect_equal(make_rel(c(10, 20, 30)), c(0, 0.5, 1)) +}) diff --git a/tests/testthat/test-def_features.R b/tests/testthat/test-def_features.R new file mode 100644 index 00000000..646c43b8 --- /dev/null +++ b/tests/testthat/test-def_features.R @@ -0,0 +1,70 @@ +map <- read_extdata("CA_tiny_map.zip") |> read_any() + +test_that("features are identified when given logical", { + map$metadata$particles <- map$metadata$y == 0 + id_map <- def_features(map, map$metadata$particles) + expect_length(unique(id_map$metadata$feature_id), 2) + expect_equal(max(id_map$metadata$area, na.rm = T), 13) + expect_equal(max(id_map$metadata$feret_max, na.rm = T), 13) + expect_equal(max(id_map$metadata$feret_min, na.rm = T), 1) + expect_equal(max(id_map$metadata$perimeter, na.rm = T), 24) +}) + +test_that("particles are identified when given character", { + map$metadata$particles <- ifelse(map$metadata$y == 1, "particle", "not_particle") + id_map <- def_features(map, map$metadata$particles) + expect_length(unique(id_map$metadata$feature_id), 3) + expect_equal(max(id_map$metadata$area, na.rm = T), 182) + expect_equal(round(max(id_map$metadata$feret_max, na.rm = T)), 19) +}) + +test_that("an error is thrown for invalid feature input", { + def_features(map, map$metadata) |> expect_error() +}) + +test_that("check that particles are identified with all TRUE or FALSE logical vectors", { + # All TRUE case + map$metadata$particles <- rep(TRUE, nrow(map$metadata)) + def_features(map, map$metadata$particles) |> expect_error() + + # All FALSE case + map$metadata$particles <- rep("test_FALSE", nrow(map$metadata)) + def_features(map, map$metadata$particles) |> expect_error() +}) + +test_that("the original spectrum remains unmodified and metadata is amended", { + map <- read_extdata("CA_tiny_map.zip") |> read_any() + + id_map <- def_features(map,ifelse(map$metadata$x == 1, + "particle", "not_particle")) + + expect_equal(id_map$wavenumber, map$wavenumber) + expect_equal(id_map$spectra, map$spectra) + expect_contains(id_map$metadata, map$metadata) + + expect_contains(names(id_map$metadata), + c("feature_id", "area", "feret_max", "centroid_y", + "centroid_x")) +}) + +test_that("collapse particles returns expected values", { + particles <- ifelse(map$metadata$y == 1, "particleA", "particleB") + id_map <- def_features(map, particles) + test_collapsed <- collapse_spec(id_map) + + expect_equal(test_collapsed$metadata |> nrow(), 3) + expect_equal(test_collapsed$metadata$feret_max |> round(2), c(13, 13, 18.69)) + expect_equal(test_collapsed$metadata$centroid_x |> unique(), 6) + + particles <- map$metadata$y == 1 + id_map <- def_features(map, particles) + test_collapsed <- collapse_spec(id_map) + + expect_equal(test_collapsed$metadata |> nrow(), 2) + expect_equal(test_collapsed$metadata$feret_max |> round(2), c(NA, 13)) + expect_equal(test_collapsed$metadata$centroid_x |> unique(), 6) + + expect_contains(names(test_collapsed$metadata), + c("feature_id", "area", "feret_max", "centroid_y", + "centroid_x")) +}) diff --git a/tests/testthat/test-gen_OpenSpecy.R b/tests/testthat/test-gen_OpenSpecy.R new file mode 100644 index 00000000..766dceaa --- /dev/null +++ b/tests/testthat/test-gen_OpenSpecy.R @@ -0,0 +1,22 @@ +library(data.table) +data("raman_hdpe") + +test_that("print() and summary() work as expected", { + print(raman_hdpe) |> expect_output( + paste0("wavenumber.*intensity.*308.221.*", + "48.*metadata.*spectrum_identity.*HDPE")) + summary(raman_hdpe) |> expect_output("Length.*964.*spectra.*26") +}) + +test_that("head returns the first few lines of the OpenSpecy object", { + head <- head(raman_hdpe) + + nrow(head) |> expect_equal(6) + head |> expect_equal(head(data.table(wavenumber = raman_hdpe$wavenumber, + raman_hdpe$spectra))) +}) + +test_that("ploting works without errors", { + plot(raman_hdpe) |> expect_silent() +}) + diff --git a/tests/testthat/test-interactive_plots.R b/tests/testthat/test-interactive_plots.R new file mode 100644 index 00000000..d1c1df78 --- /dev/null +++ b/tests/testthat/test-interactive_plots.R @@ -0,0 +1,20 @@ +map <- read_zip(read_extdata("CA_tiny_map.zip")) +data("raman_hdpe") + +test_that("heatmap_spec() generates 'plotly' object", { + heatmap_spec(map, z = map$metadata$y) |> + expect_silent() |> + expect_s3_class("plotly") +}) + +test_that("plotly_spec() generates 'plotly' object", { + plotly_spec(raman_hdpe, select = 1) |> + expect_silent() |> + expect_s3_class("plotly") +}) + +test_that("interactive_plot() generates 'plotly' object", { + interactive_plot(map, x2 = raman_hdpe, select = 2) |> + suppressWarnings() |> + expect_s3_class("plotly") +}) diff --git a/tests/testthat/test-io_spec.R b/tests/testthat/test-io_spec.R new file mode 100644 index 00000000..dbc972fb --- /dev/null +++ b/tests/testthat/test-io_spec.R @@ -0,0 +1,77 @@ +# Loading test data +data(raman_hdpe) + +# Create temp dir for testthat +tmp <- file.path(tempdir(), "OpenSpecy-testthat") +dir.create(tmp, showWarnings = F) + +library(data.table) + +test_that("extdata files are present", { + ed <- read_extdata() + any(grepl("\\.yml$", ed)) |> expect_true() + any(grepl("\\.json$", ed)) |> expect_true() + any(grepl("\\.rds$", ed)) |> expect_true() +}) + +test_that("write_spec() works without errors", { + write_spec(raman_hdpe, file.path(tmp, "test.yml")) |> expect_silent() + write_spec(raman_hdpe, file.path(tmp, "test.json")) |> expect_silent() + write_spec(raman_hdpe, file.path(tmp, "test.rds")) |> expect_silent() + + write_spec(as.data.frame(raman_hdpe), file.path(tmp, "test.yml")) |> + expect_error() + write_spec(raman_hdpe, file.path(tmp, "test.csv")) |> expect_error() +}) + +test_that("read_spec() gives expected output", { + yml <- read_extdata("raman_hdpe.yml") |> read_spec() |> expect_silent() + jsn <- read_extdata("raman_hdpe.json") |> read_spec() |> expect_silent() + rds <- read_extdata("raman_hdpe.rds") |> read_spec() |> expect_silent() + + read_spec(read_extdata("raman_hdpe.csv")) |> expect_error() + + read_extdata("raman_hdpe.yml") |> read_spec(share = tmp) |> expect_message() + read_extdata("raman_hdpe.json") |> read_spec(share = tmp) |> expect_message() + read_extdata("raman_hdpe.rds") |> read_spec(share = tmp) |> expect_message() + + expect_s3_class(yml, "OpenSpecy") + expect_s3_class(jsn, "OpenSpecy") + expect_s3_class(rds, "OpenSpecy") + + jsn$metadata$file_name <- yml$metadata$file_name <- + rds$metadata$file_name <- NULL + expect_equal(jsn, yml) + expect_equal(rds, raman_hdpe) + expect_equal(jsn[1:2], raman_hdpe[1:2]) + expect_equal(yml[1:2], raman_hdpe[1:2]) + + read_spec(read_extdata("raman_hdpe.yml"), share = tmp) |> expect_message() +}) + +test_that("read_spec() and write_spec() work nicely together", { + yml <- read_spec(file.path(tmp, "test.yml")) |> expect_silent() + jsn <- read_spec(file.path(tmp, "test.json")) |> expect_silent() + rds <- read_spec(file.path(tmp, "test.rds")) |> expect_silent() + + jsn$metadata$file_name <- yml$metadata$file_name <- + rds$metadata$file_name <- NULL + expect_equal(jsn, yml) + expect_equal(rds, raman_hdpe) + expect_equal(jsn[1:2], raman_hdpe[1:2]) + expect_equal(yml[1:2], raman_hdpe[1:2]) +}) + +test_that("as_hyperspec function", { + hyperspec_object <- as_hyperSpec(raman_hdpe) + + # Verify the class of the output + expect_s4_class(hyperspec_object, "hyperSpec") + + # Verify the equality of the content + expect_equal(hyperspec_object@wavelength, raman_hdpe$wavenumber) + expect_equal(c(hyperspec_object$spc), raman_hdpe$spectra$intensity) +}) + +# Tidy up +unlink(tmp, recursive = T) diff --git a/tests/testthat/test-manage_lib.R b/tests/testthat/test-manage_lib.R index 2a6898af..fc57f016 100644 --- a/tests/testthat/test-manage_lib.R +++ b/tests/testthat/test-manage_lib.R @@ -1,5 +1,3 @@ -data("test_lib") - # Create temp dir for testthat tmp <- file.path(tempdir(), "OpenSpecy-testthat") dir.create(tmp, showWarnings = F) @@ -9,37 +7,57 @@ test_that("stop if OSF not reachable", { skip_if_not(is.null(curl::nslookup("api.osf.io", error = F)), message = "OSF is online") - expect_error(get_lib(which = "test", type = c("metadata", "library"), - path = tmp)) + get_lib(type = "test", path = tmp) |> + expect_error() }) test_that("get_lib() downloads test library", { skip_on_cran() skip_if_offline(host = "api.osf.io") - expect_message( - expect_output(get_lib(which = "test", type = c("metadata", "library"), - path = tmp)) - ) + get_lib(type = "test", path = tmp) |> + expect_output() |> expect_message() + get_lib(type = "model", path = tmp) |> + expect_output() |> expect_message() }) test_that("check_lib() finds test library", { skip_on_cran() skip_if_offline(host = "api.osf.io") - expect_silent(check_lib(which = "test", type = c("metadata", "library"), - path = tmp)) - expect_warning(check_lib(which = "test", type = c("peaks"), - path = tmp)) + check_lib(type = "test", path = tmp) |> expect_silent() + check_lib(type = c("raw", "derivative", "nobaseline"), path = tmp) |> + expect_warning() +}) + +test_that("get_lib() downloads complete library", { + skip_on_cran() + skip_if_offline(host = "api.osf.io") + skip_if_not(testthat:::on_ci(), "Not on CI") + + get_lib(type = c("derivative", "nobaseline"), path = tmp) |> + expect_output() |> expect_message() +}) + +test_that("check_lib() finds complete library", { + skip_on_cran() + skip_if_offline(host = "api.osf.io") + skip_if_not(testthat:::on_ci(), "Not on CI") + + check_lib(type = c("derivative", "nobaseline"), path = tmp) |> + expect_silent() + check_lib(type = "raw", path = tmp) |> + expect_warning() }) test_that("load_lib() works as expected", { skip_on_cran() skip_if_offline(host = "api.osf.io") - expect_silent(tl <- load_lib(which = "test", type = c("metadata", "library"), - path = tmp)) + tl <- load_lib(type = "test", path = tmp) |> + expect_silent() expect_type(tl, "list") + expect_s3_class(tl, "OpenSpecy") expect_identical(tl, test_lib, ignore_attr = T) }) diff --git a/tests/testthat/test-manage_spec.R b/tests/testthat/test-manage_spec.R new file mode 100644 index 00000000..ade9e807 --- /dev/null +++ b/tests/testthat/test-manage_spec.R @@ -0,0 +1,42 @@ +library(data.table) +data("raman_hdpe") + +test_that("merging identical files without range specification", { + specs <- lapply(c(read_extdata("raman_hdpe.yml"), + read_extdata("raman_hdpe.yml")), read_spec) + same <- c_spec(specs) |> expect_silent() + + expect_equal(same$wavenumber, raman_hdpe$wavenumber) + expect_equal(same$spectra$intensity, raman_hdpe$spectra$intensity) +}) + +specs <- lapply(c(read_extdata("raman_hdpe.yml"), + read_extdata("ftir_ldpe_soil.asp")), read_any) + +test_that("merging different files with common range", { + diff <- c_spec(specs, range = "common", res = 5) |> + expect_silent() + + diff$wavenumber[1:2] |> expect_equal(c(655, 660)) + diff$spectra$intensity[1:2] |> round(2) |> expect_equal(c(53.87, 59.00)) + diff$spectra$intensity.1[1:2] |> round(2) |> expect_equal(c(0.03, 0.03)) +}) + +test_that("merging different files with specified range", { + spec <- c_spec(specs, range = c(1000, 2000), res = 5) |> + expect_silent() + + spec$wavenumber |> expect_equal(seq(1000, 2000, 5)) + spec$spectra$intensity |> + expect_equal( + conform_spec(raman_hdpe, c(1000, 2000), res = 5)$spectra$intensity + ) +}) + +test_that("sample_spec() returns a subset of the spectra", { + tiny_map <- read_any(read_extdata("CA_tiny_map.zip")) + sampled <- sample_spec(tiny_map, size = 5) + expect_s3_class(sampled, "OpenSpecy") + expect_equal(ncol(sampled$spectra), 5) +}) + diff --git a/tests/testthat/test-match_spec.R b/tests/testthat/test-match_spec.R index 8859a465..c90c3a9f 100644 --- a/tests/testthat/test-match_spec.R +++ b/tests/testthat/test-match_spec.R @@ -1,25 +1,115 @@ -data("raman_hdpe") +# Create temp dir for testthat +tmp <- file.path(tempdir(), "OpenSpecy-testthat") +dir.create(tmp, showWarnings = F) + +# Create test data for cor_spec function data("test_lib") -test_that("match_spec() gives expected results", { - expect_message( - ms <- match_spec(raman_hdpe, test_lib, which = "test") +unknown <- read_extdata("ftir_ldpe_soil.asp") |> read_any() |> + conform_spec(range = test_lib$wavenumber, res = spec_res(test_lib)) |> + process_spec(smooth_intens = T, make_rel = T) + +# Create a subset of test_lib for filtering +test_lib_extract <- filter_spec( + test_lib, logic = test_lib$metadata$polymer_class == "polycarbonates" + ) + +# Match_spec function with AI +test_that("match_spec returns correct structure with AI", { + skip_on_cran() + skip_if_offline(host = "api.osf.io") + + get_lib("model", path = tmp) + lib <- load_lib(type = "model", path = tmp) + + set.seed(47) + rn <- runif(n = length(unique(lib$variables_in))) + fill <- as_OpenSpecy(as.numeric(unique(lib$variables_in)), + spectra = data.frame(rn)) + matches <- match_spec(x = unknown, library = lib, na.rm = T, fill = fill) |> + expect_silent() + nrow(matches) |> expect_equal(1) + names(matches) |> expect_contains(c("x", "y", "z", "value", "name")) + round(matches$value, 2) |> expect_equal(0.52) + grepl("polyamide", matches$name) |> expect_true() +}) + +# Match_spec function +test_that("match_spec returns correct structure", { + matches <- match_spec(x = unknown, library = test_lib, na.rm = T, top_n = 5, + add_library_metadata = "sample_name", + add_object_metadata = "col_id") |> + expect_silent() + + nrow(matches) |> expect_equal(5) + names(matches) |> expect_contains(c("object_id", "library_id", "match_val")) + round(matches$match_val, 2) |> expect_equal(c(0.67, 0.57, 0.55, 0.50, 0.43)) + tolower(matches$polymer) |> expect_equal( + c("poly(ethylene)", "polystyrene", "poly(vinyl chloride)", + "poly(dimethylsiloxane) (pdms)", NA) ) - expect_error(match_spec(raman_hdpe, test_lib)) - expect_length(ms, 4) - h1 <- c(ms[1,1:3]) - expect_equal(h1$sample_name, 5373) - expect_equal(h1$spectrum_identity, "HDPE") - expect_equal(h1$rsq, 0.91) }) -test_that("find_spec() works as expected", { - expect_silent( - fs <- find_spec(sample_name == 5373, test_lib, which = "test") +# Write the tests for cor_spec function +test_that("cor_spec returns a data.table with correct columns", { + matches <- cor_spec(unknown, library = test_lib) |> + expect_silent() + + unknown2 <- unknown + unknown2$wavenumber[1:3] <- unknown2$wavenumber[1:3] + 1 + + matches2 <- cor_spec(unknown2, library = test_lib) |> + expect_warning() + inherits(matches, "matrix") |> expect_true() + expect_identical(dim(matches), c(ncol(test_lib$spectra), + ncol(unknown$spectra))) + + top_matches <- max_cor_named(cor_matrix = matches, na.rm = T) |> + expect_silent() + + expect_length(top_matches, 1) + + ncol(filter_spec(test_lib, logic = names(top_matches))$spectra) |> + expect_equal(1) + + test_lib$metadata$test <- NA + + test_metadata <- get_metadata(test_lib, logic = names(top_matches), + rm_empty = T) |> + expect_silent() + + expect_equal(nrow(test_metadata), 1) + full_test <- ident_spec(matches, unknown, library = test_lib, top_n = 5, + add_library_metadata = "sample_name") |> + expect_silent() + + nrow(full_test) |> expect_equal(5) + names(full_test) |> expect_contains(c("object_id", "library_id", "match_val")) + round(full_test$match_val, 2) |> expect_equal(c(0.67, 0.57, 0.55, 0.50, 0.43)) + tolower(full_test$polymer) |> expect_equal( + c("poly(ethylene)", "polystyrene", "poly(vinyl chloride)", + "poly(dimethylsiloxane) (pdms)", NA) ) - expect_error(find_spec(sample_name == 5373, test_lib)) - expect_length(fs, 4) - h2 <- c(fs[1,c(1,3,4)]) - expect_equal(h2$sample_name, 5373) - expect_equal(h2$spectrum_identity, "HDPE") }) + +# Write the tests for filter_spec function +test_that("filter_spec returns OpenSpecy object with filtered spectra", { + os_filtered <- filter_spec(test_lib, logic = rep(F, ncol(test_lib$spectra))) |> + expect_silent() + expect_equal(ncol(os_filtered$spectra), 0) + expect_equal(nrow(os_filtered$metadata), 0) +}) + +# Write the tests for filter_spec function +test_that("filter_spec returns OpenSpecy object with filtered spectra", { + logic <- rep(F,ncol(test_lib$spectra)) + logic[1] <- TRUE + + os_filtered <- filter_spec(test_lib, logic = logic) |> + expect_silent() + expect_equal(ncol(os_filtered$spectra), 1) + expect_equal(nrow(os_filtered$metadata), 1) +}) + +# Tidy up +unlink(tmp, recursive = T) diff --git a/tests/testthat/test-process_spec.R b/tests/testthat/test-process_spec.R new file mode 100644 index 00000000..d2dc6c39 --- /dev/null +++ b/tests/testthat/test-process_spec.R @@ -0,0 +1,24 @@ +test_that("process_spec() returns expected values", { + tiny_map <- read_extdata("CA_tiny_map.zip") |> read_any() + + conf <- process_spec(tiny_map) |> expect_silent() + expect_equal(conf, conform_spec(tiny_map, range = NULL, res = 5) |> + smooth_intens(polynomial = 3, + window = 11, + derivative = 1, + abs = T, + make_rel = T)) + + + proc <- process_spec(raman_hdpe, + smooth_intens = TRUE, + smooth_intens_args = list( + polynomial = 3, + window = 11, + derivative = 1 + ) + ) |> expect_silent() + + expect_equal(proc, conform_spec(raman_hdpe) |> + smooth_intens(derivative = 1, make_rel = T)) +}) diff --git a/tests/testthat/test-read_envi.R b/tests/testthat/test-read_envi.R new file mode 100644 index 00000000..9f54bf92 --- /dev/null +++ b/tests/testthat/test-read_envi.R @@ -0,0 +1,29 @@ +# Create temp dir for testthat +tmp <- file.path(tempdir(), "OpenSpecy-testthat") +dir.create(tmp, showWarnings = F) + +test_that("ENVI files are read", { + tiny_map <- read_extdata("CA_tiny_map.zip") |> read_zip() |> + expect_silent() + read_extdata("CA_tiny_map.zip") |> read_zip(share = tmp) |> + expect_message() |> expect_warning() + + expect_s3_class(tiny_map, "OpenSpecy") + + expect_equal(ncol(tiny_map$spectra), 208) + expect_length(tiny_map$wavenumber, 427) + + range(tiny_map$wavenumber) |> round(1) |> + expect_equal(c(717.4, 4003.7)) + range(tiny_map$spectra) |> round(2) |> + expect_equal(c(-1.32, 1.17)) + tiny_map$spectra[c(1,427), c(1,45)] |> round(2) |> unlist() |> as.numeric() |> + expect_equal(c(-0.86, -0.88, -0.62, -0.64)) + + names(tiny_map$metadata) |> + expect_contains(c("x", "y", "file_name", "file_id", "description", + "pixel size")) +}) + +# Tidy up +unlink(tmp, recursive = T) diff --git a/tests/testthat/test-read_ext.R b/tests/testthat/test-read_ext.R new file mode 100644 index 00000000..6547a3f6 --- /dev/null +++ b/tests/testthat/test-read_ext.R @@ -0,0 +1,111 @@ +# Create temp dir for testthat +tmp <- file.path(tempdir(), "OpenSpecy-testthat") +dir.create(tmp, showWarnings = F) + +library(data.table) + +data("raman_hdpe") + +test_that("extdata files are present", { + ed <- read_extdata() + expect_true(any(grepl("\\.asp$", ed))) + expect_true(any(grepl("\\.csv$", ed))) + expect_true(any(grepl("\\.0$", ed))) + expect_true(any(grepl("\\.jdx$", ed))) + expect_true(any(grepl("\\.spa$", ed))) + expect_true(any(grepl("\\.spc$", ed))) +}) + +test_that("read_text() gives expected output", { + csv <- read_extdata("raman_hdpe.csv") |> read_text() |> + expect_silent() + read_extdata("raman_hdpe.csv") |> read_text(share = tmp) |> + expect_message() |> expect_warning() + + dtf <- read_extdata("raman_hdpe.csv") |> read_text(method = "fread") |> + expect_silent() + read_extdata("ftir_pva_without_header.csv") |> read_text() |> + expect_warning() |> expect_warning() + read_extdata("ftir_pva_without_header.csv") |> read_text(header = F) |> + expect_warning() |> expect_warning() + read_extdata("ftir_pva_without_header.csv") |> read_text(method = "fread") |> + expect_warning() |> expect_warning() + + expect_s3_class(csv, "OpenSpecy") + expect_equal(names(csv), c("wavenumber", "spectra", "metadata")) + expect_equal(csv$wavenumber, raman_hdpe$wavenumber) + expect_equal(csv$spectra, raman_hdpe$spectra) + expect_equal(csv, dtf) +}) + +test_that("read_asp() gives expected output", { + asp <- read_extdata("ftir_ldpe_soil.asp") |> read_asp() |> + expect_silent() + read_extdata("raman_hdpe.csv") |> read_asp() |> + expect_error() + read_extdata("ftir_ldpe_soil.asp") |> read_asp(share = tmp) |> + expect_message() |> expect_warning() + + expect_s3_class(asp, "OpenSpecy") + expect_equal(names(asp), c("wavenumber", "spectra", "metadata")) + expect_length(asp$wavenumber, 1798) + range(asp$wavenumber) |> round(1) |> + expect_equal(c(650.4, 3999.4)) + range(asp$spectra) |> round(4) |> + expect_equal(c(0.0010, 0.5182)) +}) + +test_that("read_spa() gives expected output", { + spa <- read_extdata("ftir_polyethylene_reflectance_adjustment_not_working.spa") |> + read_spa() |> expect_silent() + read_extdata("raman_hdpe.csv") |> read_spa() |> + expect_error() + read_extdata("ftir_polyethylene_reflectance_adjustment_not_working.spa") |> + read_spa(share = tmp) |> + expect_message() |> expect_warning() + + expect_s3_class(spa, "OpenSpecy") + expect_equal(names(spa), c("wavenumber", "spectra", "metadata")) + expect_length(spa$wavenumber, 1738) + range(spa$wavenumber) |> round(1) |> + expect_equal(c(649.9, 3999.8)) + range(spa$spectra) |> round(2) |> + expect_equal(c(61.51, 102.88)) +}) + +test_that("read_jdx() gives expected output", { + suppressWarnings(jdx <- read_jdx(read_extdata("fitr_nitrocellulose.jdx"), + encoding = "latin1")) |> + capture_messages() |> + expect_match("JDX file inconsistency.*") + read_extdata("raman_hdpe.csv") |> read_jdx() |> + expect_error() + + expect_s3_class(jdx, "OpenSpecy") + expect_equal(names(jdx), c("wavenumber", "spectra", "metadata")) + expect_length(jdx$wavenumber, 7154) + range(jdx$wavenumber) |> round(1) |> + expect_equal(c(599.9, 7499.0)) + range(jdx$spectra) |> round(4) |> + expect_equal(c(0.0106, 0.6989)) +}) + +test_that("read_spc() gives expected output", { + spc <- read_extdata("raman_atacamit.spc") |> read_spc() |> + expect_silent() + read_extdata("raman_hdpe.csv") |> read_spc() |> + expect_error() + read_extdata("raman_atacamit.spc") |> read_spc(share = tmp) |> + expect_message() |> expect_warning() + + expect_s3_class(spc, "OpenSpecy") + expect_equal(names(spc), c("wavenumber", "spectra", "metadata")) + expect_length(spc$wavenumber, 559) + range(spc$wavenumber) |> round(1) |> + expect_equal(c(117.8, 1050.0)) + range(spc$spectra) |> round(2) |> + expect_equal(c(0.08, 585.51)) +}) + +# Tidy up +unlink(tmp, recursive = T) diff --git a/tests/testthat/test-read_multi.R b/tests/testthat/test-read_multi.R new file mode 100644 index 00000000..f080b632 --- /dev/null +++ b/tests/testthat/test-read_multi.R @@ -0,0 +1,22 @@ +# Create temp dir for testthat +tmp <- file.path(tempdir(), "OpenSpecy-testthat") +dir.create(tmp, showWarnings = F) + +data("raman_hdpe") + +test_that("reading in multi files doesn't throw error", { + expect_silent(multi <- read_extdata("testdata_zipped.zip") |> read_any()) + expect_s3_class(multi, "OpenSpecy") + + read_extdata("testdata_zipped.zip") |> read_any(share = tmp) |> + expect_message() |> expect_message() |> expect_message() |> + expect_warning() |> expect_warning() |> expect_warning() + + expect_equal(multi$wavenumber, raman_hdpe$wavenumber) + expect_equal(multi$spectra$intensity, raman_hdpe$spectra$intensity) + expect_equal(multi$spectra$intensity.1, raman_hdpe$spectra$intensity) + expect_equal(multi$spectra$intensity.2, raman_hdpe$spectra$intensity) +}) + +# Tidy up +unlink(tmp, recursive = T) diff --git a/tests/testthat/test-read_opus.R b/tests/testthat/test-read_opus.R new file mode 100644 index 00000000..23dba994 --- /dev/null +++ b/tests/testthat/test-read_opus.R @@ -0,0 +1,40 @@ +# Create temp dir for testthat +tmp <- file.path(tempdir(), "OpenSpecy-testthat") +dir.create(tmp, showWarnings = F) + +test_that("opus files are read correctly", { + single <- read_extdata("ftir_ps.0") |> read_opus() |> + expect_silent() + multi <- c(read_extdata("ftir_ps.0"), read_extdata("ftir_ps.0")) |> + read_opus() |> expect_silent() + read_extdata("raman_hdpe.csv") |> read_opus() |> + expect_error() + read_extdata("ftir_ps.0") |> read_opus(share = tmp) |> expect_message() |> + expect_warning() + + expect_s3_class(single, "OpenSpecy") + expect_s3_class(multi, "OpenSpecy") + + expect_equal(names(single), c("wavenumber", "spectra", "metadata")) + expect_equal(names(multi), c("wavenumber", "spectra", "metadata")) + expect_length(single$wavenumber, 2126) + range(single$wavenumber) |> round(1) |> + expect_equal(c(399.2, 4497.5)) + range(single$spectra) |> round(4) |> + expect_equal(c(0.0130, 0.6112)) + expect_equal(multi$wavenumber, single$wavenumber |> round(1)) + range(multi$spectra, na.rm = T) |> round(4) |> + expect_equal(c(0.0130, 0.6103)) + + names(single$metadata) |> + expect_contains(c("x", "y", "unique_id", "sample_id", "date_time_sm", + "date_time_rf", "sample_name", "instr_name_range", + "resolution_wn", "result_spc", "beamspl", "laser_wn", + "spc_in_file", "zero_filling", "temp_scanner_sm", + "temp_scanner_rf", "hum_rel_sm", "hum_rel_rf", + "hum_abs_sm", "hum_abs_rf", "file_id")) + expect_identical(names(multi$metadata), names(single$metadata)) +}) + +# Tidy up +unlink(tmp, recursive = T) diff --git a/tests/testthat/test-read_spec.R b/tests/testthat/test-read_spec.R deleted file mode 100644 index 0aa2b175..00000000 --- a/tests/testthat/test-read_spec.R +++ /dev/null @@ -1,95 +0,0 @@ -# Create temp dir for testthat -tmp <- file.path(tempdir(), "OpenSpecy-testthat") -dir.create(tmp, showWarnings = F) - -library(data.table) - -test_that("extdata files are present", { - ed <- read_extdata() - expect_true(any(grepl("\\.asp$", ed))) - expect_true(any(grepl("\\.csv$", ed))) - expect_true(any(grepl("\\.0$", ed))) - expect_true(any(grepl("\\.jdx$", ed))) - expect_true(any(grepl("\\.spa$", ed))) - expect_true(any(grepl("\\.spc$", ed))) -}) - -test_that("read_text() gives expected output", { - expect_silent(csv <- read_text(read_extdata("raman_hdpe.csv"))) - expect_warning( - expect_message(read_text(read_extdata("raman_hdpe.csv"), share = tmp)) - ) - expect_silent( - dtf <- read_text(read_extdata("raman_hdpe.csv"), method = "fread") - ) - expect_error(read_text(read_extdata("ftir_pva_without_header.csv"))) - expect_warning(read_text(read_extdata("ftir_pva_without_header.csv"), - header = F)) - expect_warning( - read_text(read_extdata("ftir_pva_without_header.csv"), method = "fread") - ) - expect_s3_class(csv, "data.frame") - expect_equal(names(csv), c("wavenumber", "intensity")) - expect_equal(nrow(csv), 1095) - expect_equal(round(range(csv[1]), 1), c(150.9, 2998.5)) - expect_equal(round(range(csv[2]), 1), c(3264.2, 41238.9)) - expect_equal(csv, dtf) -}) - -test_that("read_asp() gives expected output", { - expect_silent(asp <- read_asp(read_extdata("ftir_ldpe_soil.asp"))) - expect_error(read_asp(read_extdata("raman_hdpe.csv"))) - expect_s3_class(asp, "data.frame") - expect_equal(names(asp), c("wavenumber", "intensity")) - expect_equal(nrow(asp), 1798) - expect_equal(round(range(asp[1]), 1), c(650.4, 3999.4)) - expect_equal(round(range(asp[2]), 4), c(0.0010, 0.5182)) -}) - -test_that("read_spa() gives expected output", { - expect_silent(spa <- read_spa(read_extdata("ftir_polyethylene_reflectance_adjustment_not_working.spa"))) - expect_error(read_spa(read_extdata("raman_hdpe.csv"))) - expect_s3_class(spa, "data.frame") - expect_equal(names(spa), c("wavenumber", "intensity")) - expect_equal(nrow(spa), 1738) - expect_equal(round(range(spa[1]), 1), c(649.9, 3999.8)) - expect_equal(round(range(spa[2]), 2), c(61.51, 102.88)) -}) - -test_that("read_jdx() gives expected output", { - expect_match(capture_messages( - suppressWarnings(jdx <- read_jdx(read_extdata("fitr_nitrocellulose.jdx"), - encoding = "latin1")) - ), "JDX file inconsistency.*" - ) - expect_error(read_jdx(read_extdata("throws_error_raman_1000002.jdx"))) - expect_error(read_jdx(read_extdata("raman_hdpe.csv"))) - expect_s3_class(jdx, "data.frame") - expect_equal(names(jdx), c("wavenumber", "intensity")) - expect_equal(nrow(jdx), 7154) - expect_equal(round(range(jdx[1]), 1), c(599.9, 7499.0)) - expect_equal(round(range(jdx[2]), 4), c(0.0106, 0.6989)) -}) - -test_that("read_spc() gives expected output", { - expect_silent(spc <- read_spc(read_extdata("raman_atacamit.spc"))) - expect_error(read_spc(read_extdata("raman_hdpe.csv"))) - expect_s3_class(spc, "data.frame") - expect_equal(names(spc), c("wavenumber", "intensity")) - expect_equal(nrow(spc), 559) - expect_equal(round(range(spc[1]), 1), c(117.8, 1050.0)) - expect_equal(round(range(spc[2]), 2), c(0.08, 585.51)) -}) - -test_that("read_0() gives expected output", { - expect_silent(f0 <- read_0(read_extdata("ftir_ps.0"))) - expect_error(read_0(read_extdata("raman_hdpe.csv"))) - expect_s3_class(f0, "data.frame") - expect_equal(names(f0), c("wavenumber", "intensity")) - expect_equal(nrow(f0), 2126) - expect_equal(round(range(f0[1]), 1), c(399.2, 4497.5)) - expect_equal(round(range(f0[2]), 4), c(0.0130, 0.6112)) -}) - -# Tidy up -unlink(tmp, recursive = T) diff --git a/tests/testthat/test-run_app.R b/tests/testthat/test-run_app.R new file mode 100644 index 00000000..3abe4935 --- /dev/null +++ b/tests/testthat/test-run_app.R @@ -0,0 +1,11 @@ +# Create temp dir for testthat +tmp <- file.path(tempdir(), "OpenSpecy-testthat") +dir.create(tmp, showWarnings = F) + +test_that("run_app() wrapper doesn't produce errors", { + run_app(path = tmp, test_mode = T) |> + expect_silent() +}) + +# Tidy up +unlink(tmp, recursive = T) diff --git a/tests/testthat/test-share_spec.R b/tests/testthat/test-share_spec.R index a83c32a6..a0f560ad 100644 --- a/tests/testthat/test-share_spec.R +++ b/tests/testthat/test-share_spec.R @@ -1,22 +1,41 @@ -data("raman_hdpe") - # Create temp dir for testthat tmp <- file.path(tempdir(), "OpenSpecy-testthat") dir.create(tmp, showWarnings = F) -test_that("share_text() gives expected output", { - expect_message(share_spec(raman_hdpe, - metadata = c(user_name = "Win Cowger", - spectrum_type = "FTIR", - spectrum_identity = "PE", - license = "CC BY-NC"), - share = tmp - )) - expect_warning( - expect_message(share_spec(raman_hdpe, share = tmp)) - ) - expect_error(share_spec(raman_hdpe, metadata = c("a", "b", "c"), - share = tmp)) +data("raman_hdpe") + +wo_meta <- raman_hdpe +wo_meta$metadata$spectrum_identity <- NULL + +test_that("share_text() works locally", { + share_spec(raman_hdpe, file = read_extdata("raman_hdpe.csv"), share = tmp) |> + expect_message() + read_extdata("testdata_zipped.zip") |> read_zip() |> + share_spec(share = tmp) |> + expect_message() |> expect_warning() + + file.path(tmp, raman_hdpe$metadata$session_id, + paste0(raman_hdpe$metadata$file_id, ".yml")) |> + file.exists() |> + expect_true() + + share_spec(wo_meta, share = tmp) |> + expect_message() |> + expect_warning() +}) + +test_that("share_text() uploads to the cloud", { + skip_on_cran() + skip_if_not(testthat:::on_ci(), "Not on CI") + skip_if(Sys.getenv("AWS_ACCESS_KEY_ID") == "" || + Sys.getenv("AWS_SECRET_ACCESS_KEY") == "", "No credentials") + + share_spec(raman_hdpe, file = read_extdata("raman_hdpe.csv"), share = "cloud", + credentials = list( + s3_key = Sys.getenv("AWS_ACCESS_KEY_ID"), + s3_secret = Sys.getenv("AWS_SECRET_ACCESS_KEY"), + s3_region = "us-east-2", s3_bucket = "openspecy")) |> + expect_message() }) # Tidy up diff --git a/tests/testthat/test-sig_noise.R b/tests/testthat/test-sig_noise.R new file mode 100644 index 00000000..f8074a96 --- /dev/null +++ b/tests/testthat/test-sig_noise.R @@ -0,0 +1,19 @@ +# Load your OpenSpecy object +data("raman_hdpe") + +test_that("sig_noise returns correct values", { + sig_noise(raman_hdpe, metric = "sig") |> round(2) |> unname() |> + expect_equal(101.17) + + sig_noise(raman_hdpe, metric = "noise") |> round(2) |> unname() |> + expect_equal(61.01) + + sig_noise(raman_hdpe, metric = "sig_times_noise") |> round(2) |> unname() |> + expect_equal(6172.1) + + sig_noise(raman_hdpe, metric = "sig_over_noise") |> round(2) |> unname() |> + expect_equal(1.66) + + sig_noise(raman_hdpe, metric = "tot_sig") |> round(2) |> unname() |> + expect_equal(97527) +}) diff --git a/tests/testthat/test-smooth_intens.R b/tests/testthat/test-smooth_intens.R index 8f23eee6..954f2167 100644 --- a/tests/testthat/test-smooth_intens.R +++ b/tests/testthat/test-smooth_intens.R @@ -1,16 +1,13 @@ data("raman_hdpe") test_that("smooth_intens() works as expected", { - expect_silent(smt <- smooth_intens(raman_hdpe, p = 3)) - expect_identical(smt, smooth_intens(raman_hdpe$wavenumber, - raman_hdpe$intensity)) - expect_identical(smt, smooth_intens(intensity ~ wavenumber, raman_hdpe)) - expect_equal(as.numeric( - round(cor(smt[2], smooth_intens(raman_hdpe, p = 1)[2]), 4) - ), 0.9756, ignore_attr = F) - expect_s3_class(smt, "data.frame") - expect_equal(names(smt), c("wavenumber", "intensity")) - expect_equal(nrow(smt), nrow(raman_hdpe)) - expect_equal(smt[1], raman_hdpe[1]) - expect_equal(range(smt[2]), c(0, 1)) + smt <- smooth_intens(raman_hdpe, polynomial = 3) |> expect_silent() + + cor(smt$spectra$intensity, + smooth_intens(raman_hdpe, polynomial = 1)$spectra$intensity) |> round(4) |> + expect_equal(0.9756, ignore_attr = F) + expect_s3_class(smt, "OpenSpecy") + expect_equal(nrow(smt$spectra), nrow(raman_hdpe$spectra)) + expect_equal(smt$wavenumber, raman_hdpe$wavenumber) + expect_equal(range(smt$spectra), c(0, 1)) }) diff --git a/tests/testthat/test-spec_res.R b/tests/testthat/test-spec_res.R index 20fc9b3c..9e8f62e6 100644 --- a/tests/testthat/test-spec_res.R +++ b/tests/testthat/test-spec_res.R @@ -1,5 +1,5 @@ data("raman_hdpe") test_that("spec_res() gives correct output", { - expect_equal(round(spec_res(raman_hdpe$wavenumber), 3), 3.005) + spec_res(raman_hdpe$wavenumber) |> round(2) |> expect_equal(2.54) }) diff --git a/tests/testthat/test-subtr_baseline.R b/tests/testthat/test-subtr_baseline.R new file mode 100644 index 00000000..52c6b542 --- /dev/null +++ b/tests/testthat/test-subtr_baseline.R @@ -0,0 +1,29 @@ +data("raman_hdpe") + +test_that("polynomial subtr_baseline() works as expected", { + poly <- subtr_baseline(raman_hdpe, degree = 8) |> expect_silent() + + cor(poly$spectra$intensity, + subtr_baseline(raman_hdpe, degree = 1)$spectra$intensity) |> round(4) |> + expect_equal(0.9763, ignore_attr = F) + expect_s3_class(poly, "OpenSpecy") + expect_equal(nrow(poly$spectra), nrow(raman_hdpe$spectra)) + expect_equal(poly$wavenumber, raman_hdpe$wavenumber) + expect_equal(range(poly$spectra), c(0, 1)) +}) + +test_that("manual subtr_baseline() works as expected", { + subtr_baseline(raman_hdpe, type = "manual") |> expect_error() + + bl <- raman_hdpe + bl$spectra$intensity <- bl$spectra$intensity / 2 + + man <- subtr_baseline(raman_hdpe, type = "manual", baseline = bl) |> + expect_silent() + + cor(raman_hdpe$spectra$intensity, man$spectra$intensity) |> + expect_equal(1, ignore_attr = F) + expect_equal(nrow(man$spectra), nrow(raman_hdpe$spectra)) + expect_equal(man$wavenumber, raman_hdpe$wavenumber) + expect_equal(range(man$spectra), c(0, 1)) +}) diff --git a/tests/testthat/test-subtr_bg.R b/tests/testthat/test-subtr_bg.R deleted file mode 100644 index 972804dc..00000000 --- a/tests/testthat/test-subtr_bg.R +++ /dev/null @@ -1,16 +0,0 @@ -data("raman_hdpe") - -test_that("subtr_bg() works as expected", { - expect_silent(sb <- subtr_bg(raman_hdpe, degree = 8)) - expect_identical(sb, subtr_bg(raman_hdpe$wavenumber, - raman_hdpe$intensity)) - expect_identical(sb, subtr_bg(intensity ~ wavenumber, raman_hdpe)) - expect_equal(as.numeric( - round(cor(sb[2], subtr_bg(raman_hdpe, degree = 1)[2]), 4) - ), 0.9763, ignore_attr = F) - expect_s3_class(sb, "data.frame") - expect_equal(names(sb), c("wavenumber", "intensity")) - expect_equal(nrow(sb), nrow(raman_hdpe)) - expect_equal(sb[1], raman_hdpe[1]) - expect_equal(range(sb[2]), c(0, 1)) -}) diff --git a/tests/testthat/test-workflows.R b/tests/testthat/test-workflows.R new file mode 100644 index 00000000..d4456292 --- /dev/null +++ b/tests/testthat/test-workflows.R @@ -0,0 +1,74 @@ +# Create temp dir for testthat +tmp <- file.path(tempdir(), "OpenSpecy-testthat") +dir.create(tmp, showWarnings = F) + +test_that("Raman batch analysis with test library", { + skip_on_cran() + skip_if_offline(host = "api.osf.io") + + batch <- read_extdata("testdata_zipped.zip") |> read_any() |> + expect_silent() + is_OpenSpecy(batch) |> expect_true() + plot(batch) |> expect_silent() + plotly_spec(batch) |> expect_silent() + + get_lib(type = "test", path = tmp) |> expect_no_error() + check_lib(type = "test", path = tmp) |> expect_silent() + lib <- load_lib(type = "test", path = tmp) |> expect_silent() + + filter_spec(lib, lib$metadata$SpectrumType == "Raman") |> expect_silent() + batch2 <- conform_spec(batch, lib$wavenumber, res = spec_res(lib$wavenumber)) |> + expect_silent() + + plotly_spec(batch2) |> expect_silent() + + sig_noise(batch2, metric = "run_sig_over_noise") |> + expect_silent() + batch3 <- process_spec(batch2, subtr_baseline = T) |> expect_silent() + plotly_spec(x = batch3, x2 = batch) |> expect_silent() + + matches <- cor_spec(batch3, library = lib) |> expect_silent() + test_max_cor <- max_cor_named(matches) |> expect_silent() + sig_noise(batch3, metric = "run_sig_over_noise") |> + expect_silent() +}) + +test_that("Raman batch analysis with complete library", { + skip_on_cran() + skip_if_offline(host = "api.osf.io") + skip_if_not(testthat:::on_ci(), "Not on CI") + + batch <- read_extdata(file = "testdata_zipped.zip") |> read_any() |> + expect_silent() + is_OpenSpecy(batch) |> expect_true() + plot(batch) |> expect_silent() + plotly_spec(batch) |> expect_silent() + + get_lib(type = "nobaseline", path = tmp) |> expect_no_error() + check_lib(type = "nobaseline", path = tmp) |> expect_silent() + lib <- load_lib(type = "nobaseline", path = tmp) |> expect_silent() + + filter_spec(lib, lib$metadata$SpectrumType == "Raman") |> expect_silent() + batch2 <- conform_spec(batch, range = lib$wavenumber, + res = spec_res(lib$wavenumber)) |> + expect_silent() + + plotly_spec(batch2) |> expect_silent() + + test_sn2 <- sig_noise(batch2, metric = "run_sig_over_noise") |> + expect_silent() + batch3 <- process_spec(batch2, subtr_baseline = T) |> expect_silent() + plotly_spec(x = batch3, x2 = batch) |> expect_silent() + + matches <- cor_spec(batch3, library = lib) |> expect_silent() + test_max_cor <- max_cor_named(matches) |> expect_silent() + test_sn <- sig_noise(batch3, metric = "run_sig_over_noise") |> + expect_silent() + + heatmap_spec(batch3, sn = test_sn, cor = test_max_cor, min_sn = 4, + min_cor = 0.7, select = 2, source = "heatplot") |> + expect_silent() +}) + +# Tidy up +unlink(tmp, recursive = T) diff --git a/tests/validation/test-augmented.R b/tests/validation/test-augmented.R index c4a7f0ad..476ac74a 100644 --- a/tests/validation/test-augmented.R +++ b/tests/validation/test-augmented.R @@ -53,13 +53,13 @@ augmented_ftir <- augment_full(spec_lib$ftir$library %>% raman_proc <- augmented_raman %>% group_by(sample_name) %>% smooth_intens(p = 3) #%>% - subtr_bg(degree = 8) %>% + subtr_baseline(degree = 8) %>% mutate() ftir_proc <- augmented_ftir %>% group_by(sample_name) %>% smooth_intens() %>% - subtr_bg() + subtr_baseline() spectrum = raman_subset[100] @@ -72,7 +72,7 @@ ggplot() + geom_line(data = augmented_raman %>% filter(sample_name == spectrum) %>% smooth_intens(p = 3) %>% - subtr_bg(degree = 8), + subtr_baseline(degree = 8), aes(x = wavenumber, y = intensity)) @@ -80,7 +80,7 @@ ggplot() + augmented_raman %>% filter(sample_name == spectrum) %>% smooth_intens(p = 3) %>% - subtr_bg(degree = 8) %>% + subtr_baseline(degree = 8) %>% match_spec(library = spec_lib, which = "raman") spectrum @@ -95,7 +95,7 @@ for(spectrum in raman_subset){ topmatch <- augmented_raman %>% filter(sample_name == spectrum) %>% smooth_intens(p = 3) %>% - subtr_bg(degree = 8) %>% + subtr_baseline(degree = 8) %>% match_spec(library = spec_lib, which = "raman", top_n = 1) %>% select(spectrum_identity) %>% unlist() @@ -117,7 +117,7 @@ for(spectrum in ftir_subset){ topmatch <- augmented_ftir %>% filter(sample_name == spectrum) %>% smooth_intens(p = 3) %>% - subtr_bg(degree = 8) %>% + subtr_baseline(degree = 8) %>% match_spec(library = spec_lib, which = "ftir", top_n = 1) %>% select(spectrum_identity) %>% unlist() diff --git a/vignettes/advanced.Rmd b/vignettes/advanced.Rmd new file mode 100644 index 00000000..6509573f --- /dev/null +++ b/vignettes/advanced.Rmd @@ -0,0 +1,193 @@ +--- +title: "Spectroscopy Advanced" +author: > + Jessica Meyers, Jeremy Conkle, Win Cowger, Zacharias Steinmetz, + Andrew Gray, Chelsea Rochman, Sebastian Primpke, Jennifer Lynch, + Hannah Hapich, Hannah De Frond, Keenan Munno, Bridget O’Donnell +date: "`r Sys.Date()`" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{Spectroscopy Advanced} + %\VignetteEncoding{UTF-8} + %\VignetteEngine{knitr::rmarkdown} +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + warning = FALSE +) +``` + +# How to Interpret the Reported Matches + +There are several important things to consider when interpreting a spectral +match including the library source, the Pearson's r, and other metrics. + +# The Library Source + +When you click on a spectrum, all of the metadata that we have in Open Specy +about that source will be displayed in a metadata window below to the matches +table. Each library has different methodologies used to develop it. It is useful +to read up on the library sources from the literature that they came from. E.g. +Chabuka et al. 2020 focuses on weathered plastics, so matching to it may suggest +that your spectrum is of a weathered polymer. Primpke et al. 2018 only has a +spectral range up to 2000, so some polymers may be difficult to differentiate +with it. Make sure to cite the libraries that you use during your search when +you publish your results. The authors were kind enough to make their data open +access so that it could be used in Open Specy and we should return the favor by +citing them. + +# Pearson's R + +Correlation values are used to identify the closest matches available in the +current Open Specy spectral libraries to improve material identification and +reduce sample processing times. Pearson's r values range from 0 - 1 with 0 being +a completely different spectrum and 1 being an exact match. Some general +guidelines that we have observed from using Open Specy. If no matches are \> +\~0.3 the material may require additional processing or may not exist in the +Open Specy library. Correlation values are not the only metric you should use to +assess your spectra's match to a material in the library, matches need to make +sense. + +# Things to Consider beyond Correlation + +Peak position and height similarities are more important than correlation and +need to be assessed manually. Peak position correlates with specific bond types. +Peak height correlates to the concentration of a compound. Therefore, peak +height and peak position should match as closely as possible to the matched +spectrum. When there are peaks that exist in the spectra you are trying to +interpret that do not exist in the match, there may be additional materials to +identify. In this case, restrict the preprocessing range to just the +unidentified peak and try to identify it as an additional component (see also +[https://www.compoundchem.com/2015/02/05/irspectroscopy/](https://www.compoundchem.com/2015/02/05/irspectroscopy/)). + +Also, check the match metadata to see if the match makes sense. Example: A +single fiber cannot be a "cotton blend" since there would be no other fibers to +make up the rest of the blend. Example: Cellophane does not degrade into fibers, +so a match for a fiber to cellophane wouldn't make sense. Example: You are +analyzing a particle at room temperature, but the matched material is liquid at +room temperature. The material may be a component of the particle but it cannot +be the whole particle. + +# How Specific Do You Need to be in the Material Type of the Match? + +You can choose to be specific about how you classify a substance (e.g. +polyester, cellophane) or more general (e.g. synthetic, semi-synthetic, natural, +etc.). The choice depends on your research question. Using more general groups +can speed up analysis time but will decrease the information you have for +interpretation. To identify materials more generally, you can often clump the +identities provided by Open Specy to suit your needs. For example, matches to +"polyester" and "polypropylene" could be clumped to the category "plastic". + +# How to Differentiate Between Similar Spectra? + +One common challenge is differentiating between LDPE and HDPE. But, even with a +low resolution instrument (MacroRAM, 2 cm^-1^ pixel^-1^), you can still see some +differences. From a wide view, these low, medium, and high density PE samples +all look relatively similar (figures courtesy of Bridget O\'Donnell, Horiba +Scientific): + +```{r, fig.align="center", echo=FALSE} +knitr::include_graphics("advanced/horiba-1.png") +``` + +But, a closer look at the 1450 cm^-1^ band reveals clear differences: + +```{r, fig.align="center", echo=FALSE} +knitr::include_graphics("advanced/horiba-2.png") +``` + +When you overlay them, you start to see differences in other spectral regions +too: + +```{r, fig.align="center", echo=FALSE} +knitr::include_graphics("advanced/horiba-3.png") +``` + +So, the question is, how do we deal with samples that are very similar with only +subtle differences? Usually, researchers will use MVA techniques after they've +collected multiple reference spectra of known samples (LDPE and HDPE in this +case). They can then develop models and apply them to distinguish between +different types of PE. With a reference database like Open Specy, this is +complicated by the fact that researchers are measuring samples on different +instruments with correspondingly different spectral responses and spectral +resolutions. That makes it even more difficult to accurately match definitively +to LDPE and HDPE as opposed to generic 'PE'. + +One possibility is to place more emphasis (from a computational perspective) on +the bands that show the most difference (the triplet at 1450 cm^-1^) by +restricting the range used to match in Open Specy. + +The other, much simpler option is to just match any PE hit to generic 'PE' and +not specifically HDPE or LDPE. + +Another challenge is in differentiating between types of nylons. But, Raman has +a pretty easy time distinguishing nylons. These spectra were recorded of a +series of nylons and the differences are much more distinguishable compared to +the PE results above (nylon 6, 6-6, 6-9, 6-10, and 6-12 top to bottom): + +```{r, fig.align="center", echo=FALSE} +knitr::include_graphics("advanced/horiba-4.png") +``` + +The differences are even more pronounced when you overlay the +spectra: + +```{r, fig.align="center", echo=FALSE} +knitr::include_graphics("advanced/horiba-5.png") +``` + +# What to Do When Matches Aren't Making Sense + +1. Double check that the baseline correction and smoothing parameters + result in the best preprocessing of the data. +2. Try reprocessing your spectrum, but limit it to specific peak + regions with a higher signal to noise ratio. +3. Restrict the spectral range to include or exclude questionable + peaks or peaks that were not present in the previous matches. +4. Restrict the spectral range to exclude things like CO~2~ (2200 cm^-1^) + or H~2~O (\~1600 cm^-1^) in spikes in the IR spectrum. +5. If nothing above works to determine a quality match, you may need + to measure the spectrum of your material again or use another + spectral analysis tool. + +# References + +Chabuka BK, Kalivas JH (2020). “Application of a Hybrid Fusion Classification +Process for Identification of Microplastics Based on Fourier Transform Infrared +Spectroscopy.” *Applied Spectroscopy*, **74**(9), 1167–1183. doi: +[10.1177/0003702820923993](https://doi.org/10.1177/0003702820923993). + +Cowger W, Gray A, Christiansen SH, Christiansen SH, Christiansen SH, De Frond H, +Deshpande AD, Hemabessiere L, Lee E, Mill L, et al. (2020). “Critical Review of +Processing and Classification Techniques for Images and Spectra in Microplastic +Research.” *Applied Spectroscopy*, **74**(9), 989–1010. doi: +[10.1177/0003702820929064](https://doi.org/10.1177/0003702820929064). + +Cowger W, Steinmetz Z, Gray A, Munno K, Lynch J, Hapich H, Primpke S, +De Frond H, Rochman C, Herodotou O (2021). “Microplastic Spectral Classification +Needs an Open Source Community: Open Specy to the Rescue!” +*Analytical Chemistry*, **93**(21), 7543–7548. doi: +[10.1021/acs.analchem.1c00123](https://doi.org/10.1021/acs.analchem.1c00123). + +Primpke S, Wirth M, Lorenz C, Gerdts G (2018). “Reference Database Design for +the Automated Analysis of Microplastic Samples Based on Fourier Transform +Infrared (FTIR) Spectroscopy.” *Analytical and Bioanalytical Chemistry*, +**410**(21), 5131–5141. doi: +[10.1007/s00216-018-1156-x](https://doi.org/10.1007/s00216-018-1156-x). + +Renner G, Schmidt TC, Schram J (2017). “A New Chemometric Approach for Automatic +Identification of Microplastics from Environmental Compartments Based on FT-IR +Spectroscopy.” *Analytical Chemistry*, **89**(22), 12045–12053. doi: +[10.1021/acs.analchem.7b02472](https://doi.org/10.1021/acs.analchem.7b02472). + +Savitzky A, Golay MJ (1964). “Smoothing and Differentiation of Data by +Simplified Least Squares Procedures.” *Analytical Chemistry*, **36**(8), +1627–1639. + +Zhao J, Lui H, McLean DI, Zeng H (2007). “Automated Autofluorescence Background +Subtraction Algorithm for Biomedical Raman Spectroscopy.” +*Applied Spectroscopy*, **61**(11), 1225–1232. doi: +[10.1366/000370207782597003](https://doi.org/10.1366/000370207782597003). diff --git a/vignettes/images/horiba-1.png b/vignettes/advanced/horiba-1.png similarity index 100% rename from vignettes/images/horiba-1.png rename to vignettes/advanced/horiba-1.png diff --git a/vignettes/images/horiba-2.png b/vignettes/advanced/horiba-2.png similarity index 100% rename from vignettes/images/horiba-2.png rename to vignettes/advanced/horiba-2.png diff --git a/vignettes/images/horiba-3.png b/vignettes/advanced/horiba-3.png similarity index 100% rename from vignettes/images/horiba-3.png rename to vignettes/advanced/horiba-3.png diff --git a/vignettes/images/horiba-4.png b/vignettes/advanced/horiba-4.png similarity index 100% rename from vignettes/images/horiba-4.png rename to vignettes/advanced/horiba-4.png diff --git a/vignettes/images/horiba-5.png b/vignettes/advanced/horiba-5.png similarity index 100% rename from vignettes/images/horiba-5.png rename to vignettes/advanced/horiba-5.png diff --git a/vignettes/images/analyzeplot.png b/vignettes/images/analyzeplot.png deleted file mode 100644 index 59189fdc..00000000 Binary files a/vignettes/images/analyzeplot.png and /dev/null differ diff --git a/vignettes/images/baselinecorrectionpoly.jpg b/vignettes/images/baselinecorrectionpoly.jpg deleted file mode 100644 index 949f2fbc..00000000 Binary files a/vignettes/images/baselinecorrectionpoly.jpg and /dev/null differ diff --git a/vignettes/images/googletranslate.jpg b/vignettes/images/googletranslate.jpg deleted file mode 100644 index 4d5dc97c..00000000 Binary files a/vignettes/images/googletranslate.jpg and /dev/null differ diff --git a/vignettes/images/intensityadjustment.jpg b/vignettes/images/intensityadjustment.jpg deleted file mode 100644 index 00749098..00000000 Binary files a/vignettes/images/intensityadjustment.jpg and /dev/null differ diff --git a/vignettes/images/mainpage.jpg b/vignettes/images/mainpage.jpg deleted file mode 100644 index 28fbc1ce..00000000 Binary files a/vignettes/images/mainpage.jpg and /dev/null differ diff --git a/vignettes/images/matches.jpg b/vignettes/images/matches.jpg deleted file mode 100644 index 628184f8..00000000 Binary files a/vignettes/images/matches.jpg and /dev/null differ diff --git a/vignettes/images/metadatainput.jpg b/vignettes/images/metadatainput.jpg deleted file mode 100644 index 18e0d9f4..00000000 Binary files a/vignettes/images/metadatainput.jpg and /dev/null differ diff --git a/vignettes/images/preprocessplot.png b/vignettes/images/preprocessplot.png deleted file mode 100644 index f95c510f..00000000 Binary files a/vignettes/images/preprocessplot.png and /dev/null differ diff --git a/vignettes/images/rangeselection.jpg b/vignettes/images/rangeselection.jpg deleted file mode 100644 index f51be499..00000000 Binary files a/vignettes/images/rangeselection.jpg and /dev/null differ diff --git a/vignettes/images/regiontomatch.jpg b/vignettes/images/regiontomatch.jpg deleted file mode 100644 index 6cdb4ba1..00000000 Binary files a/vignettes/images/regiontomatch.jpg and /dev/null differ diff --git a/vignettes/images/removetoolexample.jpg b/vignettes/images/removetoolexample.jpg deleted file mode 100644 index 3d421f60..00000000 Binary files a/vignettes/images/removetoolexample.jpg and /dev/null differ diff --git a/vignettes/images/samplefile.jpg b/vignettes/images/samplefile.jpg deleted file mode 100644 index 426441d9..00000000 Binary files a/vignettes/images/samplefile.jpg and /dev/null differ diff --git a/vignettes/images/sharemetadata.jpg b/vignettes/images/sharemetadata.jpg deleted file mode 100644 index fd59ac3c..00000000 Binary files a/vignettes/images/sharemetadata.jpg and /dev/null differ diff --git a/vignettes/images/smoothing.jpg b/vignettes/images/smoothing.jpg deleted file mode 100644 index 61e2563e..00000000 Binary files a/vignettes/images/smoothing.jpg and /dev/null differ diff --git a/vignettes/images/smoothingpoly.jpg b/vignettes/images/smoothingpoly.jpg deleted file mode 100644 index 6dc12501..00000000 Binary files a/vignettes/images/smoothingpoly.jpg and /dev/null differ diff --git a/vignettes/images/spectrumtoanalyze.jpg b/vignettes/images/spectrumtoanalyze.jpg deleted file mode 100644 index 12d13b23..00000000 Binary files a/vignettes/images/spectrumtoanalyze.jpg and /dev/null differ diff --git a/vignettes/images/spectrumtype.jpg b/vignettes/images/spectrumtype.jpg deleted file mode 100644 index 1327481e..00000000 Binary files a/vignettes/images/spectrumtype.jpg and /dev/null differ diff --git a/vignettes/images/toolexample.jpg b/vignettes/images/toolexample.jpg deleted file mode 100644 index 4554430e..00000000 Binary files a/vignettes/images/toolexample.jpg and /dev/null differ diff --git a/vignettes/images/uploadfile.jpg b/vignettes/images/uploadfile.jpg deleted file mode 100644 index 1435b22d..00000000 Binary files a/vignettes/images/uploadfile.jpg and /dev/null differ diff --git a/vignettes/sop.Rmd b/vignettes/sop.Rmd index d924bfca..04d4c4da 100644 --- a/vignettes/sop.Rmd +++ b/vignettes/sop.Rmd @@ -1,5 +1,5 @@ --- -title: "Standard Operating Procedure" +title: "Open Specy Tutorial" author: > Jessica Meyers, Jeremy Conkle, Win Cowger, Zacharias Steinmetz, Andrew Gray, Chelsea Rochman, Sebastian Primpke, Jennifer Lynch, @@ -7,7 +7,7 @@ author: > date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > - %\VignetteIndexEntry{Standard Operating Procedure} + %\VignetteIndexEntry{Open Specy Tutorial} %\VignetteEncoding{UTF-8} %\VignetteEngine{knitr::rmarkdown} --- @@ -15,697 +15,660 @@ vignette: > ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, - comment = "#>" + comment = "#>", + warning = FALSE ) ``` -Open Specy Raman and (FT)IR spectral analysis tool for plastic particles and -other environmental samples. Supported features include reading spectral data -files (.asp, .csv, .jdx, .spc, .spa, .0), smoothing spectral intensities -with `smooth_intens()`, correcting background with -`subtr_bg()`, and identifying spectra using an onboard reference -library. Analyzed spectra can be shared with the Open Specy community. -A Shiny app is available via `run_app()` or online at -[https://openanalysis.org/openspecy/](https://openanalysis.org/openspecy/). +# Document Overview -This document outlines a common workflow for -using Open Specy and highlights some topics that users are often -requesting a tutorial on. If the document is followed sequentially from -beginning to end, the user will have a better understanding of every -procedure involved in using Open Specy as a tool for interpreting -spectra. It takes approximately 45 minutes to read through and follow -along with this standard operating procedure the first time. Afterward, -knowledgeable users should be able to thoroughly analyze individual -spectra at an average speed of 1 min^-1^. +This document outlines a common workflow for using the Open Specy package and +app and highlights some topics that users are often requesting a tutorial on. If +the document is followed sequentially from beginning to end, the user will have +a better understanding of every procedure involved in using the Open Specy R +package and app as a tool for interpreting spectra. It takes approximately 45 +minutes to read through and follow along with this standard operating procedure +the first time. Afterward, knowledgeable users should be able to thoroughly +analyze spectra at an average speed of 1 min^-1^ or faster with the new batch +and automated procedures. -# Getting started +# R Package and App + +The Open Specy R package is the backbone of the Shiny app. The choice is yours +as to which you start with, we use both on a regular basis. The tutorial will +talk through the R functions and app features step by step together. + +## Installation + +After installing the R package, you just need to read in the library. ```{r setup} library(OpenSpecy) ``` -# Viewing and Sharing Spectra +## Running the App To get started with the Open Specy user interface, access [https://openanalysis.org/openspecy/](https://openanalysis.org/openspecy/) -or start the Shiny GUI directly from R typing +or start the Shiny GUI directly from your own computer in R. ```{r eval=FALSE} run_app() ``` -Then click the **Upload File** tab at the top of the page. +## Reading Data -```{r, fig.align="center", out.width="98%", echo=FALSE} -knitr::include_graphics("images/mainpage.jpg") -``` +If you have your own data, click **Browse** at the top left hand corner of the +Analyze Spectra tab. -Accessibility is extremely important to us and we are making strives to improve -the accessibility of Open Specy for all spectroscopists. Please reach out if you -have ideas for improvement. - -We added a Google translate plugin to all pages in the app so that you can -easily translate the app. We know that not all languages will be fully supported -but we will continue to try and improve the translations. - -```{r, fig.align="center", echo=FALSE} -knitr::include_graphics("images/googletranslate.jpg") +```{r, fig.align="center", out.width="98%", echo=FALSE} +knitr::include_graphics("sop/mainpage.jpg") ``` -## Download a test dataset +Before uploading, indicate if you would like to share the uploaded data or not +using the slider. If selected, any data uploaded to the tool will automatically +be shared under [CC-BY 4.0 +license](https://creativecommons.org/licenses/by/4.0/) and will be available for +researchers and other ventures to use to improve spectral analysis, build +machine learning tools, etc. Some users may choose not to share if they need to +keep their data private. If switched off, none of the uploaded data will be +stored or shared in Open Specy. ```{r, fig.align="center", echo=FALSE} -knitr::include_graphics("images/samplefile.jpg") +knitr::include_graphics("sop/uploadfile.jpg") ``` -If you don't have your own data to use right away, that is ok. You can -download test data to try out the tool by clicking on the test data -button. A .csv file of HDPE Raman spectrum will download on your -computer. This file can also be used as a template for formatting .csv -data into an Open Specy accepted format. The following line of code does the -same: +Open Specy allows for upload of native Open Specy .y(a)ml, .json, or .rds files. +In addition, .csv, .asp, .jdx, .0, .spa, .spc, and .zip files can be imported. +.zip files can either contain multiple files with individual spectra in them of +the non-zip formats or it can contain a .hdr and .dat file that form an ENVI +file for a spectral map. Open Specy and .csv files should always load correctly +but the other file types are still in development, though most of the time these +files work perfectly. If uploading a .csv file, label the column with the +wavenumbers `wavenumber` and name the column with the intensities `intensity`. +Wavenumber units must be cm^-1^. Any other columns are not used by the software. +Always keep a copy of the original file before alteration to preserve metadata +and raw data for your records. -```{r eval=FALSE} -data("raman_hdpe") +```{r sample_data, echo=FALSE, out.width="50%"} +knitr::kable(head(raman_hdpe), caption = "Sample data `raman_hdpe`") ``` -## Choose whether to share your uploaded data or not +It is best practice to cross check files in the proprietary software they came +from and Open Specy before use in Open Specy. Due to the complexity of some +proprietary file types, we haven't been able to make them fully compatible yet. +If your file is not working, please contact the administrator and share the file +so that we can work on integrating it. -```{r, fig.align="center", echo=FALSE} -knitr::include_graphics("images/uploadfile.jpg") -``` - -Before uploading, indicate if you would like to share the -uploaded data or not using the slider. If selected, -any data uploaded to the tool will automatically be shared under [CC-BY 4.0 license](https://creativecommons.org/licenses/by/4.0/) -and will be available for -researchers and other ventures to use to improve spectral analysis, -build machine learning tools, etc. Some users may choose not to share if -they need to keep their data private. If switched off, none of -the uploaded data will be stored or shared in Open Specy. - -## Upload/Read Data - -Open Specy allows for upload of .csv, .asp, .jdx, .0, .spc, and -.spa files. .csv files should always load correctly but the other file -types are still in beta development, though most of the time these files -work perfectly. It is best practice to cross check files in the -proprietary software they came from and Open Specy before use in Open -Specy. Due to the complexity of these file types, we haven't been able -to make them fully compatible yet. If your file is not working, please -contact the administrator and share the file so that we can get it to -work. - -For the most consistent results, files should be converted to .csv -format before uploading to Open Specy. The specific steps to converting -your instrument's native files to .csv can be found in its software -manual or you can check out +The specific steps to converting your instrument's native files to .csv can be +found in its software manual or you can check out [Spectragryph](https://www.effemm2.de/spectragryph/), which supports many spectral file conversions (see Mini Tutorial section: File conversion in Spectragryph to Open Specy accepted format). -If uploading a .csv file, label the column with the wavenumbers -`wavenumber` and name the column with the intensities `intensity`. +The following line of code will read in your data when using the package and +interprets which reading function to use based on the file extension. -```{r sample_data, echo=FALSE, out.width="50%"} -knitr::kable(head(raman_hdpe), caption = "Sample data `raman_hdpe`") +```{r, eval=FALSE} +read_any("path/to/your/data") ``` -Wavenumber units must be cm^-1^. Any other columns are not used by the -software. Always keep a copy of the original file before alteration to preserve -metadata and raw data for your records. - -To upload data, click **Browse** and choose one of your -files to upload, or drag and drop your file into the gray box. At this -time you can only upload one file at a time. - -Upon upload and throughout the analysis, intensity values are min-max normalized -(Equation 1). - -$$\frac{x - \mathrm{min}(x)}{\mathrm{min}(x) - \mathrm{max}(x)}$$ -

Equation 1: Max-Min Normalization

- -The following R functions from the Open Specy package will also read in spectral -data accordingly: +These file type specific functions will also read in spectral +data accordingly if you have a particular format in mind. ```{r, eval=FALSE} read_text(".csv") read_asp(".asp") -read_0(".0") +read_opus(".0") ``` -## Viewing Spectra Plot +If you don't have your own data, you can use a test dataset. -After spectral data are uploaded, it will appear in the main window. This plot -is selectable, zoomable, and provides information on hover. You can also -save a .png file of the plot view using the camera icon at the top right when -you hover over the plot. This plot will change the view based on updates -from the **Intensity Adjustment** selection. +```{r, fig.align="center", echo=FALSE} +knitr::include_graphics("sop/samplefile.jpg") +``` -## Intensity Adjustment +A .csv file of an HDPE Raman spectrum will download on your computer. This file +can also be used as a template for formatting .csv data into an Open Specy +accepted format. -```{r, fig.align='center', echo=FALSE} -knitr::include_graphics("images/intensityadjustment.jpg") +The following line of code does the same to load the example file into R: + +```{r} +data("raman_hdpe") ``` -Open Specy assumes that intensity units are in absorbance units but -Open Specy can adjust reflectance or transmittance spectra to absorbance -units using this selection in the upload file tab. The transmittance -adjustment uses the $\log_{10} 1/T$ calculation which does not correct for -system or particle characteristics. The reflectance adjustment -use the Kubelka-Munk equation $\frac{(1-R)^2}{2R}$. If none is selected, Open -Specy assumes that the uploaded data is an absorbance spectrum and does not -apply an adjustment. +We also have many onboard files that you can call to test different formats: -This is the respective R code: +```{r} +spectral_map <- read_extdata("CA_tiny_map.zip") |> + read_any() # preserves some metadata +asp_example <- read_extdata("ftir_ldpe_soil.asp") |> + read_any() +ps_example <- read_extdata("ftir_ps.0") |> + read_any() # preserves some metadata +spc_example <- read_extdata("raman_atacamit.spc") |> + read_any() +csv_example <- read_extdata("raman_hdpe.csv") |> + read_any() +json_example <- read_extdata("raman_hdpe.json") |> + read_any() # read in exactly as an OpenSpecy object +rds_example <- read_extdata("raman_hdpe.rds") |> + read_any() # read in exactly as an OpenSpecy object +yml_example <- read_extdata("raman_hdpe.yml") |> + read_any() # read in exactly as an OpenSpecy object +batch_example <- read_extdata("testdata_zipped.zip") |> + read_any() +``` + +You will notice now that the R package reads in files into an object with class +`OpenSpecy`. This is a class we created for high throughput spectral analysis +which now also preserves spectral metadata. You can even create these from +scratch if you'd like: ```{r} -library(dplyr) +scratch_OpenSpecy <- as_OpenSpecy(x = seq(1000,2000, by = 5), + spectra = data.frame(runif(n = 201)), + metadata = list(file_name = "fake_noise")) +``` -raman_adj <- raman_hdpe %>% - adj_intens() +We have some generic functions built for inspecting the spectra: -head(raman_adj) +```{r} +print(scratch_OpenSpecy) # shows the raw object ``` -## Share metadata on known spectra +```{r} +summary(scratch_OpenSpecy) # summarizes the contents of the spectra +``` -```{r, fig.align="center", echo=FALSE} -knitr::include_graphics("images/metadatainput.jpg") +```{r} +head(scratch_OpenSpecy) # shows the top wavenumbers and intensities ``` -To share metadata about your spectrum, click the metadata input button. -When sharing data, please provide as much metadata as you can. Metadata -helps make shared data as useful as possible. Metadata inputs each have -examples provided in the input. The examples disappear when the box is -clicked and will not be saved if nothing is input by the user. -Mandatory inputs are marked with a red asterisk. If these inputs are not -filled, the data will be considered uninterpretable and will be -discarded. Inputs left blank will be left blank in the metadata sheet -and interpreted as "unknown" or "not applicable". To share metadata, -click the share data button at the bottom of the metadata inputs. +## Visualization + +### Spectra -When the user clicks the **Share Data** button their current uploaded data -and metadata is sent to an -[open-access online repository](https://osf.io/rjg3c/). +In the app after spectral data are uploaded, it will appear in the main window. +This plot is selectable, zoomable, and provides information on hover. You can +also save a .png file of the plot view using the camera icon at the top right +when you hover over the plot. ```{r, fig.align="center", echo=FALSE} -knitr::include_graphics("images/sharemetadata.jpg") +knitr::include_graphics("sop/spectra_vis.jpg") ``` -All inputs from the metadata (described below) are input to a -metadata sheet. The metadata sheet is -given the same unique name as the data, but it ends with "\_form". The -exact same data is saved as would be downloaded using the download data -button (described below). All high quality uploaded data with metadata -will eventually be reviewed by spectroscopy experts and added to the -internal library if it passes review. If multiple files are going to be -uploaded with metadata, the cells will also stay filled with the last -input after the share data button is clicked. Just upload the next -dataset and change the metadata inputs that are different. If more than -50 files will be shared at once, you can contact the website -administrator to get a bulk upload sheet for more rapid upload. +In R, we have two ways to visualize your spectra, one is quick and efficient and +the other is interactive. Here is an example of quick and efficient plotting. -Type +```{r, fig.align="center", fig.width=6} +plot(scratch_OpenSpecy) # quick and efficient +``` -```{r eval=FALSE} -share_spec(raman_hdpe, - metadata = c(user_name = "Win Cowger", - contact_info = "wincowger@gmail.com", - spectrum_type = "Raman", - spectrum_identity = "HDPE") - ) +This is an example of an interactive plot. + +```{r, fig.align="center", out.width="98%"} +plotly_spec(scratch_OpenSpecy) # this will min-max normalize your data even + # if it isn't already but are not changing your + # underlying data ``` -to share your spectral data from the R console. +With interactive plots you can also plot two different datasets simultaneously +to compare. -# Preprocessing +```{r, fig.align="center", out.width="98%"} +plotly_spec(scratch_OpenSpecy, rds_example) # interactive comparison +``` -After uploading data, you can preprocess the data using baseline -correction, smoothing, and range selection and save your preprocessed -data. Go to the **Preprocess Spectrum** tab to select -your parameters for processing the spectrum. +### Maps -## Preprocess Spectra Plot +Spectral maps can also be visualized as overlaid spectra but in addition the +spatial information can be plotted as a heatmap. A similar plot should appear in +the app if you upload multiple spectra or a spectral map. It is important to +note that when multiple spectra are uploaded in batch they are prescribed `x` +and `y` coordinates, this can be helpful for visualizing summary statistics and +navigating vast amounts of data but shouldn't be confused with data which +actually has spatial coordinates. Hovering over the map will reveal information +about the signal and noise and correlation values. Clicking the map will provide +the selected spectrum underneath. In the app, the colors of the heatmap are +either the signal and noise or the spectral identifications depending on whether +the identification is turned on or not. Pixels are black if the spectra does not +pass the signal-noise threshold and/or the correlation threshold. ```{r, fig.align="center", echo=FALSE} -knitr::include_graphics("images/preprocessplot.png") +knitr::include_graphics("sop/map_vis.jpg") +``` + +The same plot can be created in R but the user can control what values form the +colors of the heatmap by specifying `z`. + +```{r, fig.align="center", out.width="98%"} +heatmap_spec(spectral_map, + z = spectral_map$metadata$x) # will show more advanced example + # later in tutorial +``` + +An interactive plot of the heatmap and spectra overlayed can be generated with +the `interactive_plot()` function. A user can hover over the heatmap to identify +the next row id they are interested in and update the value of select to see +that spectrum. + +```{r, fig.align="center", out.width="98%"} +interactive_plot(spectral_map, select = 100, z = spectral_map$metadata$x) ``` -The preprocess spectra plot shows the uploaded spectra in comparison to -the processed spectra that has been processed using the processing -inputs on the page. It will automatically update with any new slider -inputs. This allows the user to tune the inputs to optimize the signal -to noise ratio. The goal with preprocessing is to make peak regions have -high intensities and non-peak regions should have low intensities. +## Processing -## Preprocessing Tools +After uploading data, you can preprocess the data using intensity adjustment, +baseline subtraction, smoothing, flattening, and range selection and save your +preprocessed data. Once the process button is selected the default processing +will initiate. This is an absolute derivative transformation, it is kind of +magic, it does something similar to smoothing, baseline subtraction, and +intensity correction simultaneously and really quickly. By clicking +preprocessing you should see the spectrum update with the processed spectra. ```{r, fig.align="center", echo=FALSE} -knitr::include_graphics("images/smoothing.jpg") +knitr::include_graphics("sop/processing.jpg") ``` -When the slider is green for the tool type, that means that that tool is being -used to preprocess the spectrum. If the slider is clicked blank, the cog button -to the right will disappear to indicate that the tool is no longer being used. +To view the raw data again, just deselect preprocessing. Toggling preprocessing +on and off can help you to make sure that the spectra are processed correctly. -```{r, fig.align="center", echo=FALSE} -knitr::include_graphics("images/deselection.jpg") +The `process_spec()` function is a monolithic function for all +processing procedures which is optimized by default to result in high signal to +noise in most cases, same as the app. + +```{r} +processed <- process_spec(raman_hdpe) +summary(processed) +summary(raman_hdpe) ``` -If the cog button is clicked, any functions associated with that tool will be -displayed and can be manipulated to process the spectrum. +You can compare the processed and unprocessed data in an overlay plot: -## Smoothing Polynomial +```{r eval=FALSE} +plotly_spec(raman_hdpe, processed) +``` + +We want people to use the `process_spec()` function for most processing +operations, all processing functions can be tuned using its parameters. However, +we recognize that nesting of functions and order of operations can be useful for +users to control so you can also use individual functions for each operation if +you'd like. + +#### Threshold Signal-Noise + +Considering whether you have enough signal to analyze spectra is important. +Classical spectroscopy would recommend your highest peak to be at least 10 times +the baseline of your processed spectra before you begin analysis. If your +spectra is below that threshold, you may want to consider recollecting it. In +practice, we are rarely able to collect spectra of that good quality and more +often use 5. The Signal Over Noise technique searches your spectra for high and +low regions and conducts division on them to derive the signal to noise ratio. +Signal Times Noise multiplies the mean signal by the standard deviation of the +signal and Total Signal sums the intensities. The latter can be really useful +for thresholding spectral maps to identify particles which we will discuss +later. ```{r, fig.align="center", echo=FALSE} -knitr::include_graphics("images/smoothingpoly.jpg") -``` - -The first step of the Open Specy preprocessing routing is spectral -smoothing. The goal of this function is to increase the signal to noise -ratio (S/N) without distorting the shape or relative size of the peaks. -The value on the slider is the polynomial order of the -[Savitzky-Golay (SG) filter](https://en.wikipedia.org/wiki/Savitzky%E2%80%93Golay_filter). -The SG filter is fit to a moving window of 11 data points where the center -point in the window is replaced with the polynomial estimate. The -number of data points in the window is not user adjustable. Higher -numbers lead to more wiggly fits and thus less smooth, lower numbers -lead to more smooth fits, a 7th order polynomial will make the spectrum -have almost no smoothing. If smoothing is set to 0 then no smoothing is -conducted on the spectrum. When smoothing is done well, peak shapes and -relative heights should not change. Typically a 3rd order -polynomial (3 on the slider) works to increase the signal to -noise without distortion, but if the spectrum is noisy, decrease -polynomial order and if it is already smooth, increase the polynomial -order to the maximum (7). Examples of smoothing below: - -```{r smoothing, fig.cap = "Sample `raman_hdpe` spectrum with different smoothing polynomials (p) from Cowger et al. (2020).", fig.width=6, echo=FALSE} -library(ggplot2) -data("raman_hdpe") +knitr::include_graphics("sop/snr_threshold.jpg") +``` -compare_smoothing <- rbind( - cbind(smoothing = "none", adj_intens(raman_hdpe)), - cbind(smoothing = "p = 1", smooth_intens(raman_hdpe, p = 1)), - cbind(smoothing = "p = 4", smooth_intens(raman_hdpe, p = 4)) -) +If analyzing spectra in batch, we recommend looking at the heatmap and +optimizing the percent of spectra that are above your signal to noise threshold +to determine the correct settings instead of looking through spectra +individually. Good Signal tells you what percent of your data are above your +signal threshold. -ggplot(compare_smoothing, aes(wavenumber, intensity)) + - geom_line(aes(color = smoothing)) + - theme_minimal() +```{r, fig.align="center", echo=FALSE} +knitr::include_graphics("sop/signal_settings.jpg") ``` -The different degrees of smoothing were achieved with the following R commands: +#### Intensity Adjustment -```{r, eval=FALSE} -smooth_intens(raman_hdpe, p = 1) -smooth_intens(raman_hdpe, p = 4) +```{r, fig.align='center', echo=FALSE} +knitr::include_graphics("sop/intensityadjustment.jpg") ``` -The intensity-adjusted sample spectrum `raman_adj` is smoothed accordingly: +Open Specy assumes that intensity units are in absorbance units but Open Specy +can adjust reflectance or transmittance spectra to absorbance units using this +selection. The transmittance adjustment uses the $\log_{10} 1/T$ calculation +which does not correct for system or particle characteristics. The reflectance +adjustment use the Kubelka-Munk equation $\frac{(1-R)^2}{2R}$. If none is +selected, Open Specy assumes that the uploaded data is an absorbance spectrum +and does not apply an adjustment. + +This is the respective R code for a scenario where the spectra doesn't need +intensity adjustment: + +```{r, fig.align="center", fig.width=6} +raman_adj <- raman_hdpe |> + adj_intens(type = "none") + +plot(raman_adj) +``` + +#### Conforming + +Conforming spectra is essential before comparing to a reference library and can +be useful for summarizing data when you don't need it to be highly resolved +spectrally. In the app, conforming happens behind the scenes without any user +input to make sure that the spectra the user uploads and the library spectra +will be compatible. In code, we set the default spectral resolution to 5 because +this tends to be pretty good for a lot of applications and is in between 4 and 8 +which are commonly used wavenumber resolutions. There are several ways that this +is function can be specified. ```{r} -raman_smooth <- raman_adj %>% - smooth_intens() +conform_spec(raman_hdpe) |> # default convert res to 5 wavenumbers. + summary() + +# Force one spectrum to have the exact same wavenumbers as another +conform_spec(asp_example, range = ps_example$wavenumber, res = NULL) |> + summary() -head(raman_smooth) +# Specify the wavenumber resolution and use a rolling join instead of linear +# approximation (faster for large datasets). +conform_spec(spectral_map, range = ps_example$wavenumber, res = 10, + type = "roll") |> + summary() ``` -## Baseline Correction Polynomial +#### Smoothing ```{r, fig.align="center", echo=FALSE} -knitr::include_graphics("images/baselinecorrectionpoly.jpg") -``` - -The second step of Open Specy's preprocessing routine is baseline -correction. The goal of baseline correction is to get all non-peak -regions of the spectra to zero absorbance. The higher the polynomial order, -the more wiggly the fit to the baseline. If the baseline -is not very wiggly, a more wiggly fit could remove peaks which is not -desired. The baseline correction algorithm used in Open Specy is called -"iModPolyfit" (Zhao et al. 2007). This algorithm iteratively fits polynomial -equations of the -specified order to the whole spectrum. During the first fit iteration, -peak regions will often be above the baseline fit. The data in the peak -region is removed from the fit to make sure that the baseline is less -likely to fit to the peaks. The iterative fitting terminates once the -difference between the new and previous fit is small. An example of a -good baseline fit below. - -```{r subtraction, fig.cap = "Sample `raman_hdpe` spectrum with different degrees of background subtraction (Cowger et al., 2020).", fig.width=6, echo=FALSE} +knitr::include_graphics("sop/smoothingpoly.jpg") +``` + +The first step of the Open Specy preprocessing routing is spectral smoothing. +The goal of this function is to increase the signal to noise ratio (S/N) without +distorting the shape or relative size of the peaks. The value on the slider is +the polynomial order of the [Savitzky-Golay (SG) +filter](https://en.wikipedia.org/wiki/Savitzky%E2%80%93Golay_filter). Higher +numbers lead to more wiggly fits and thus less smooth, lower numbers lead to +more smooth fits. The SG filter is fit to a moving window of 11 data points by +default where the center point in the window is replaced with the polynomial +estimate. Larger windows will produce smoother fits. The derivative order is set +to 1 by default which transforms the spectra to their first derivative. A zero +order derivative will have no derivative transformation. When smoothing is done +well, peak shapes and relative heights should not change. The absolute value is +primarily useful for first derivative spectra where the absolute value results +in an absorbance-like spectrum which is why we set it as the default. + +Examples of smoothing with the R package: + +```{r smooth_intens, fig.cap = "Sample `raman_hdpe` spectrum with different smoothing polynomials from Cowger et al. (2020).", fig.width=6, fig.align="center"} library(ggplot2) data("raman_hdpe") -compare_subtraction <- rbind( - cbind(fitting = "none", adj_intens(raman_hdpe)), - cbind(fitting = "degree = 2", subtr_bg(raman_hdpe, degree = 2)), - cbind(fitting = "degree = 8", subtr_bg(raman_hdpe, degree = 8)) -) +none <- adj_intens(raman_hdpe, make_rel = T) +p1 <- smooth_intens(raman_hdpe, polynomial = 1) +p4 <- smooth_intens(raman_hdpe, polynomial = 4) -ggplot(compare_subtraction, aes(wavenumber, intensity)) + - geom_line(aes(color = fitting)) + - theme_minimal() +compare_derivative <- c_spec(list(none, p1, p4)) + +plot(compare_derivative) ``` -The smoothed sample spectrum `raman_smooth` is background-corrected as follows: +Derivative transformation can happen with the same function. You'll notice a new +function we are using `c_spec()` which is used to combine spectral objects into +one OpenSpecy object. -```{r} -raman_bgc <- raman_smooth %>% - subtr_bg() +```{r compare_derivative, fig.cap = "Sample `raman_hdpe` spectrum with different derivatives from Cowger et al. (2020).", fig.width=6, fig.align="center"} + +none <- adj_intens(raman_hdpe, make_rel = T) +d1 <- smooth_intens(raman_hdpe, derivative = 1, abs = T) +d2 <- smooth_intens(raman_hdpe, derivative = 2) -head(raman_bgc) +compare_derivative <- c_spec(list(none, d1, d2)) + +plot(compare_derivative) ``` -## Spectral Range +#### Baseline Correction ```{r, fig.align="center", echo=FALSE} -knitr::include_graphics("images/rangeselection.jpg") -``` +knitr::include_graphics("sop/baselinecorrectionpoly.jpg") +``` + +The second step of Open Specy's preprocessing routine is baseline correction. +The goal of baseline correction is to get all non-peak regions of the spectra to +zero absorbance. The higher the polynomial order, the more wiggly the fit to the +baseline. If the baseline is not very wiggly, a more wiggly fit could remove +peaks which is not desired. The baseline correction algorithm used in Open Specy +is called "iModPolyfit" (Zhao et al. 2007). This algorithm iteratively fits +polynomial equations of the specified order to the whole spectrum. During the +first fit iteration, peak regions will often be above the baseline fit. The data +in the peak region is removed from the fit to make sure that the baseline is +less likely to fit to the peaks. The iterative fitting terminates once the +difference between the new and previous fit is small. An example of a good +baseline fit below. For those who have been with OpenSpecy a while, you will +notice the app no longer supports manual baseline correction, it was a hard +choice but it just didn't make sense to keep it now that we are moving toward +high throughput automated methods. It does still exist in the R function though. + +```{r subtr_baseline, fig.cap = "Sample `raman_hdpe` spectrum with different degrees of background subtraction (Cowger et al., 2020).", fig.width=6, fig.align="center"} +data("raman_hdpe") + +none <- adj_intens(raman_hdpe) +d2 <- subtr_baseline(raman_hdpe, degree = 2) +d8 <- subtr_baseline(raman_hdpe, degree = 8) + +compare_subtraction <- c_spec(list(none, d2, d8)) -The final step of preprocessing is restricting the spectral range. -Sometimes the instrument operates with high noise at the ends of the -spectrum and sometimes the baseline fit can produce distortions at the -ends of the spectrum, both can be removed using this routine. You should -look into the signal to noise ratio of your specific -instrument by wavelength to determine what wavelength ranges to use. -Distortions due to baseline fit can be assessed from looking at the -preprocess spectra plot. Additionally, you can restrict the range -to examine a single peak or a subset of peaks of interests. This -function allows users to isolate peaks of interest for matching, while -removing noise and influence from less relevant spectral data. +plot(compare_subtraction) +``` -## Download Data +#### Range Selection ```{r, fig.align="center", echo=FALSE} -knitr::include_graphics("images/download.jpg") +knitr::include_graphics("sop/rangeselection.jpg") ``` -After you have the preprocessing parameters set, we recommend that you -download the preprocessed data for your records. The download data -button will append the uploaded data to three columns created by the -preprocessing parameters. "Wavelength" and "Absorbance" are columns from -the data uploaded by the user. "NormalizedIntensity" is the max-min -normalized value (Equation 1) of the "Absorbance". "Smoothed" is the -Savitzky-Golay filter specified by the slider explained above. -"BaselineRemoved" is the smoothed and baseline corrected value that is -visible on the center plot. +Sometimes an instrument operates with high noise at the ends of the spectrum +and, a baseline fit produces distortions, or there are regions of interest for +analysis. Range selection accomplishes those goals. You should look into the +signal to noise ratio of your specific instrument by wavelength to determine +what wavelength ranges to use. Distortions due to baseline fit can be assessed +from looking at the preprocess spectra. Additionally, you can restrict the range +to examine a single peak or a subset of peaks of interests. The maximum and +minimum wavenumbers will specify the range to keep in the app. In the R +function, multiple ranges can be specified simultaneously. + +```{r restrict_range, fig.cap = "Sample `raman_hdpe` spectrum with different degrees of background subtraction (Cowger et al., 2020).", fig.width=6, fig.align="center"} +data("raman_hdpe") -# Matching +none <- adj_intens(raman_hdpe) +r1 <- restrict_range(raman_hdpe, min = 1000, max = 2000) +r2 <- restrict_range(raman_hdpe, min = c(1000, 1800), max = c(1200, 2000)) -After uploading data and preprocessing it (if desired) you can now -identify the spectrum. To identify the spectrum go to the **Match -Spectrum** tab. +compare_ranges <- c_spec(list(none, r1, r2), range = "common") +# Common argument crops the ranges to the most common range between the spectra +# when joining. -You will see your spectrum and the top matches, but before looking at -matches, you need to check the three selectable -parameters below. +plot(compare_ranges) +``` -## Spectrum Type +#### Flattening Ranges ```{r, fig.align="center", echo=FALSE} -knitr::include_graphics("images/spectrumtype.jpg") +knitr::include_graphics("sop/flattten_example.jpg") ``` -The spectra type input on the "Match spectra" tab specifies the type of -spectra (Raman or FTIR) that the user has uploaded and wants to match -to. This input will tell the website whether to use the FTIR library or -the Raman library to make the match. +Sometimes there are peaks that really shouldn't be in your spectra and can +distort your interpretation of the spectra but you don't necessarily want to +remove the regions from the analysis because you believe those regions should be +flat instead of having a peak. One way to deal with this is to replace the peak +values with the mean of the values around the peak. This is the purpose of the +`flatten_range` function. By default it is set to flatten the CO2 region for +FTIR spectra because that region often needs to be flattened when atmouspheric +artefacts occur in spectra. Like `restrict_range`, the R function can accept +multiple ranges. -## Spectrum To Analyze +```{r flatten_range, fig.cap = "Sample `raman_hdpe` spectrum with different degrees of background subtraction (Cowger et al., 2020).", fig.width=6, fig.align="center"} +single <- filter_spec(spectral_map, 120) # Function to filter spectra by index + # number or name or a logical vector. +none <- adj_intens(single) +f1 <- flatten_range(single) +f2 <- flatten_range(single, min = c(1000, 2500), max = c(1200, 3000)) -```{r, fig.align="center", echo=FALSE} -knitr::include_graphics("images/spectrumtoanalyze.jpg") +compare_flats <- c_spec(list(none, f1, f2)) + +plot(compare_flats) ``` -The spectra to analyze input specifies if the tool will match the -**Uploaded** spectra (unaltered by the inputs on the **Preprocess Spectra** -tab) or the **Processed Spectra** (manipulated by the inputs in the -Preprocess Spectra Tab). +#### Min-Max Normalization -## Region To Match +Min-Max normalization is used throughout the app but is not one of the options +the user can specify. Often we regard spectral intensities as arbitrary and +min-max normalization allows us to view spectra on the same scale without +drastically distorting their shapes or relative peak intensities. In the app, it +is primarily used for plotting and as a processing step before correlation +analysis. In the package, most of the processing functions will min-max +transform your spectra relative by default if you do not specify otherwise. This +plot shows the two spectra on such different scales that one looks like a +straight line. -```{r, fig.align="center", echo=FALSE} -knitr::include_graphics("images/regiontomatch.jpg") -``` +```{r adj_intens, fig.cap = "Sample `raman_hdpe` spectrum with different degrees of background subtraction (Cowger et al., 2020).", fig.width=6, fig.align="center"} +none <- adj_intens(raman_hdpe, make_rel = F) +relative <- adj_intens(raman_hdpe, make_rel = T) -The region to match input specifies if the "Full Spectrum" will match -the entire range of the spectra (including non peak regions) in the -reference database. This is the most intuitive match. Or should the -**Peaks Only** match just the peak regions in the reference database. This -is an advanced feature proposed in Renner et al. (2017). -This can be a less intuitive approach but in cases where -there are few peaks and high baseline interference, it could be the best -option. In cases where non-peak regions are important for the -interpretation of the match, this is not the best approach. +compare_rel <- c_spec(list(none, relative)) -## Match Table +plot(compare_rel) +``` + +## Identifying Spectra ```{r, fig.align="center", echo=FALSE} -knitr::include_graphics("images/matches.jpg") +knitr::include_graphics("sop/identification.jpg") ``` -The selectable table shows the top material matches returned by the tool, -their Pearson's r value, and the organization they were provided by. -When rows are selected their spectra are added to the match plot. -The spectrum being matched and reference library are determined by -the previously mentioned parameters. During the matching process, one -final cleaning step happens using a simple minimum subtraction -algorithm (Equation 2) which in many cases will allow unprocessed -spectra to remove subtle baseline, but will not harm the spectra which -has no baseline. Then, these aligned data are tested for correlation -using the Pearson's r. The Pearson's r is used as a match quality -indicator and the spectra from the top 1000 best matches are returned -from the library. You can restrict the libraries which are displayed -in the table by clicking the box that says **All** under the Organization column. +After uploading data and preprocessing it (if desired) you can now identify the +spectrum. To identify the spectrum go to the **Identification** box. Pro tip: if +you select *Identification** without uploading data to the app, you'll be able +to explore the library by itself. -Similarly you can restrict the range of Pearson\'s r values or search -for specific material types. +### Reading Libraries -$$\mathrm{for~each}~peak~group^{1,n}: x - \mathrm{min}(x)$$ -

Equation 2: Minimum Subtraction

+These options define the strategy for identification. The ID Library will inform +which library is used. Both (default) will search both FTIR and Raman libraries. +Deriv will search against a derivative transformed library. No Baseline will +search against a baseline corrected library. This should be in line with how you +choose to process your spectra. Cor options use a simple Pearson correlation +search algorithm. AI is currently experimental and uses either a multinomial +model or correlation on mediod spectra from the library. Correlation +thresholding will set the minimum value from matching to use as a 'positive +identification' -The same table can be returned using the Open Specy library commands in -the R console. +In the R package, you'll download and load the libraries into your working +environment. -```{r eval=FALSE} -# Fetch current spectral library from https://osf.io/x7dpz/ -get_lib() -# Load library into global environment -spec_lib <- load_lib() -# Match spectrum with library and retrieve meta data -match_spec(raman_bgc, library = spec_lib, which = "raman") +```{r eval=F} +get_lib() #will load all libraries unless specified otherwise. +lib <- load_lib(type = "derivative") ``` -## Selection Metadata +### Matches -```{r, fig.align="center", out.width="98%", echo=FALSE} -knitr::include_graphics("images/selectionmetadata.jpg") +```{r, fig.align="center", echo=FALSE} +knitr::include_graphics("sop/matches.jpg") ``` -Whatever match is selected from the match table may have additional -metadata about it. That metadata will be displayed below the plot. -Some of this metadata may assist you in interpreting the spectra. For -example, if the spectra has metadata which says it is a liquid and you -are analyzing a solid particle, that spectrum may not be the best match. - -The R command for manual metadata selection using `sample_name == 5381` as -example is: +Top matches in the app can be assessed by clicking the cog in the right hand +corner of the Spectra box. This will open a side window with the matches sorted +from most to least similar. Clicking on rows in the table will add the selected +match to the spectra viewer. Using the table's filter options, you can restrict +the range of Pearson\'s r values or search for specific material types. -```{r, eval=FALSE} -find_spec(sample_name == 5381, library = spec_lib, which = "raman") -``` +The same table can be returned using the OpenSpecy package commands in the R +console. -## Match Plot +```{r} +data("test_lib") -```{r, fig.align="center", echo=FALSE} -knitr::include_graphics("images/matchplot.png") -``` - -This plot is dynamically updated by selecting matches from the match -table. The red spectrum is the spectrum that you selected from the -reference library and the white spectrum is the spectrum that you are -trying to identify. Whenever a new dataset is uploaded, the plot and -data table in this tab will be updated. These plots can be saved as a -.png by clicking the camera button at the top of the plot. - -## How to interpret the reported matches - -There are several important things to consider when interpreting a -spectral match including the library source, the Pearson's r, and other -metrics. - -### The library source - -When you click on a spectrum, all of the metadata that we have in Open -Specy about that source will be displayed in a metadata window below to -the matches table. Each library has different methodologies used to -develop it. It is useful to read up on the library sources from the -literature that they came from. E.g. Chabuka et -al. 2020 focuses on weathered plastics, so matching to it -may suggest that your spectrum is of a weathered polymer. Primpke -et al. 2018 only has a spectral range up to 2000, so some polymers may -be difficult to differentiate with it. Make sure to cite the libraries -that you use during your search when you publish your results. -The authors were kind enough to make their data open access so that it -could be used in Open Specy and we should return the favor by citing -them. - -### Pearson's r - -Correlation values are used to identify the closest matches available -in the current Open Specy spectral libraries to improve material -identification and reduce sample processing times. Pearson's r values -range from 0 - 1 with 0 being a completely different spectrum and 1 -being an exact match. Some general guidelines that we have observed from -using Open Specy. If no matches are \> \~0.3 the material -may require additional processing or may not exist in the Open Specy -library. Correlation values are not the only metric you should -use to assess your spectra's match to a material in the library, matches -need to make sense. - -### Things to consider beyond correlation - -Peak position and height similarities are more important than -correlation and need to be assessed manually. Peak position -correlates with specific bond types. Peak height correlates to -the concentration of a compound. Therefore, peak height and peak -position should match as closely as possible to the matched spectrum. -When there are peaks that exist in the spectra you are trying to -interpret that do not exist in the match, there may be additional -materials to identify. In this case, restrict the preprocessing range to -just the unidentified peak and try to identify it as an additional -component (see also -[https://www.compoundchem.com/2015/02/05/irspectroscopy/](https://www.compoundchem.com/2015/02/05/irspectroscopy/)). - -Also, check the match metadata to see if the match makes sense. -Example: A single fiber cannot be a "cotton blend" -since there would be no other fibers to make up the rest of the blend. -Example: Cellophane does not degrade into fibers, so -a match for a fiber to cellophane wouldn't make -sense. Example: You are analyzing a particle -at room temperature, but the matched material is liquid at room -temperature. The material may be a component of the particle but it -cannot be the whole particle. - -### How specific do you need to be in the material type of the match? - -You can choose to be specific about how you classify a substance (e.g. -polyester, cellophane) or more general (e.g. synthetic, semi-synthetic, -natural, etc.). The choice depends on your research question. Using more -general groups can speed up analysis time but will decrease the -information you have for interpretation. To identify materials more -generally, you can often clump the identities provided by Open Specy to -suit your needs. For example, matches to "polyester" and "polypropylene" -could be clumped to the category "plastic". - -### How to differentiate between similar spectra? - -One common challenge is differentiating between LDPE and HDPE. But, -even with a low resolution instrument (MacroRAM, 2 cm^-1^ pixel^-1^), -you can still see some differences. From a wide view, -these low, medium, and high density PE samples all look relatively -similar (figures courtesy of Bridget O\'Donnell, Horiba Scientific): +processed <- process_spec(ps_example, + conform_spec_args = list(range = test_lib$wavenumber, + res = NULL)) -```{r, fig.align="center", echo=FALSE} -knitr::include_graphics("images/horiba-1.png") +test <- match_spec(processed, test_lib, add_object_metadata = "col_id", + add_library_metadata = "sample_name") ``` -But, a closer look at the 1450 cm^-1^ band reveals -clear differences: +### Selection Metadata -```{r, fig.align="center", echo=FALSE} -knitr::include_graphics("images/horiba-2.png") +```{r, fig.align="center", out.width="98%", echo=FALSE} +knitr::include_graphics("sop/selectionmetadata.jpg") ``` -When you overlay them, you start to see differences in other spectral -regions too: +Whatever match is selected from the match table may have additional metadata +about it. That metadata will be displayed below the plot. Some of this metadata +may assist you in interpreting the spectra. For example, if the spectra has +metadata which says it is a liquid and you are analyzing a solid particle, that +spectrum may not be the best match. -```{r, fig.align="center", echo=FALSE} -knitr::include_graphics("images/horiba-3.png") -``` - -So, the question is, how do we deal with samples that are very similar -with only subtle differences? Usually, researchers will use MVA -techniques after they've collected multiple reference spectra of known -samples (LDPE and HDPE in this case). They can then develop models and -apply them to distinguish between different types of PE. With a -reference database like Open Specy, this is complicated by the fact that -researchers are measuring samples on different instruments with -correspondingly different spectral responses and spectral resolutions. -That makes it even more difficult to accurately match definitively to -LDPE and HDPE as opposed to generic 'PE'. - -One possibility is to place more emphasis (from a computational -perspective) on the bands that show the most difference (the triplet at -1450 cm^-1^) by restricting the range used to match in -Open Specy. - -The other, much simpler option is to just match any PE hit to generic -'PE' and not specifically HDPE or LDPE. - -Another challenge is in differentiating between types of nylons. But, -Raman has a pretty easy time distinguishing nylons. These spectra were -recorded of a series of nylons and the differences are much more -distinguishable compared to the PE results above (nylon 6, 6-6, 6-9, -6-10, and 6-12 top to -bottom): +The R command for manual metadata selection using `sample_name == 5381` as +example is: -```{r, fig.align="center", echo=FALSE} -knitr::include_graphics("images/horiba-4.png") +```{r, eval=FALSE} +find_spec(sample_name == 5381, library = spec_lib, which = "raman") ``` -The differences are even more pronounced when you overlay the -spectra: +### Match Plot ```{r, fig.align="center", echo=FALSE} -knitr::include_graphics("images/horiba-5.png") +knitr::include_graphics("sop/matchplot.png") ``` -### What to do when matches aren't making sense +This plot is dynamically updated by selecting matches from the match table. The +red spectrum is the spectrum that you selected from the reference library and +the white spectrum is the spectrum that you are trying to identify. Whenever a +new dataset is uploaded, the plot and data table in this tab will be updated. +These plots can be saved as a .png by clicking the camera button at the top of +the plot. -1. Double check that the baseline correction and smoothing parameters - result in the best preprocessing of the data. -2. Try reprocessing your spectrum, but limit it to specific peak - regions with a higher signal to noise ratio. -3. Restrict the spectral range to include or exclude questionable - peaks or peaks that were not present in the previous matches. -4. Restrict the spectral range to exclude things like CO~2~ (2200 cm^-1^) - or H~2~O (\~1600 cm^-1^) in spikes in the IR spectrum. -5. If nothing above works to determine a quality match, you may need - to measure the spectrum of your material again or use another - spectral analysis tool. +### Sharing Reference Data -# Mini Tutorials +If you have reference data or AI models that you think would be useful for +sharing with the spectroscopy community through OpenSpecy please contact the +website administrator to discuss options for collaborating. -## File Conversion in SpectraGryph to Open Specy Accepted Format +## Characterizing Particles -1. Download Spectragryph from -[https://www.effemm2.de/spectragryph/down.html](https://www.effemm2.de/spectragryph/down.html) +### Thresholding -2. Open Spectragryph and upload your file by dragging and dropping it -into the console. +### Collapse Spectra -```{r, fig.align="center", out.width="98%", echo=FALSE} -knitr::include_graphics("images/spectragryph-1.png") -``` +## Additional App Features -3. Click File, Save/export data, save data as, and save it as an spc -file. ¸ +Accessibility is extremely important to us and we are making strives to improve +the accessibility of Open Specy for all spectroscopists. Please reach out if you +have ideas for improvement. -```{r, fig.align="center", out.width="98%", echo=FALSE} -knitr::include_graphics("images/spectragryph-2.png") -``` +We added a Google translate plugin to all pages in the app so that you can +easily translate the app. We know that not all languages will be fully supported +but we will continue to try and improve the translations. -4. Then upload that .spc file to Open Specy. +### Download Data ```{r, fig.align="center", echo=FALSE} -knitr::include_graphics("images/uploadfile.jpg") +knitr::include_graphics("sop/download.jpg") ``` -## Conceptual diagram of data flow through Open Specy +After you have the preprocessing parameters set, we recommend that you download +the preprocessed data for your records. The download data button will append the +uploaded data to three columns created by the preprocessing parameters. +"Wavelength" and "Absorbance" are columns from the data uploaded by the user. +"NormalizedIntensity" is the max-min normalized value (Equation 1) of the +"Absorbance". "Smoothed" is the Savitzky-Golay filter specified by the slider +explained above. "BaselineRemoved" is the smoothed and baseline corrected value +that is visible on the center plot. + +## Conceptual diagram of Open Specy ```{r, fig.align="center", echo=FALSE} -knitr::include_graphics("images/flowchart.png") +knitr::include_graphics("sop/flowchart.png") ``` # References diff --git a/vignettes/sop.html b/vignettes/sop.html index aeb2af7b..2b89eca7 100644 --- a/vignettes/sop.html +++ b/vignettes/sop.html @@ -11,9 +11,9 @@ - + -Standard Operating Procedure +Open Specy Tutorial + + + + + + + + + + + + + + +
                               # if it isn't already but are not changing your
+                               # underlying data
+

With interactive plots you can also plot two different datasets +simultaneously to compare.

+
plotly_spec(scratch_OpenSpecy, rds_example) # interactive comparison
+
+ + +
+

Maps

+

Spectral maps can also be visualized as overlaid spectra but in +addition the spatial information can be plotted as a heatmap. A similar +plot should appear in the app if you upload multiple spectra or a +spectral map. It is important to note that when multiple spectra are +uploaded in batch they are prescribed x and y +coordinates, this can be helpful for visualizing summary statistics and +navigating vast amounts of data but shouldn’t be confused with data +which actually has spatial coordinates. Hovering over the map will +reveal information about the signal and noise and correlation values. +Clicking the map will provide the selected spectrum underneath. In the +app, the colors of the heatmap are either the signal and noise or the +spectral identifications depending on whether the identification is +turned on or not. Pixels are black if the spectra does not pass the +signal-noise threshold and/or the correlation threshold.

+

+

The same plot can be created in R but the user can control what +values form the colors of the heatmap by specifying z.

+
heatmap_spec(spectral_map,
+             z = spectral_map$metadata$x) # will show more advanced example 
+
+ +
                                          # later in tutorial
+

An interactive plot of the heatmap and spectra overlayed can be +generated with the interactive_plot() function. A user can +hover over the heatmap to identify the next row id they are interested +in and update the value of select to see that spectrum.

+
interactive_plot(spectral_map, select = 100, z = spectral_map$metadata$x)
+
+ +
+ +
+

Processing

+

After uploading data, you can preprocess the data using intensity +adjustment, baseline subtraction, smoothing, flattening, and range +selection and save your preprocessed data. Once the process button is +selected the default processing will initiate. This is an absolute +derivative transformation, it is kind of magic, it does something +similar to smoothing, baseline subtraction, and intensity correction +simultaneously and really quickly. By clicking preprocessing you should +see the spectrum update with the processed spectra.

+

+

To view the raw data again, just deselect preprocessing. Toggling +preprocessing on and off can help you to make sure that the spectra are +processed correctly.

+

The process_spec() function is a monolithic function for +all processing procedures which is optimized by default to result in +high signal to noise in most cases, same as the app.

+
processed <- process_spec(raman_hdpe)
+summary(processed)
+#> $wavenumber
+#>  Length Min. Max. Res.
+#>     579  305 3195    5
+#> 
+#> $spectra
+#>  Number Min. Intensity Max. Intensity
+#>       1              0              1
+#> 
+#> $metadata
+#>   Min. Max.
+#> x    1    1
+#> y    1    1
+#> [1] "x"                 "y"                 "user_name"        
+#> [4] "spectrum_type"     "spectrum_identity" "organization"     
+#> [7] "license"           "session_id"        "file_id"
+summary(raman_hdpe)
+#> $wavenumber
+#>  Length   Min.    Max. Res.
+#>     964 301.04 3198.12 2.54
+#> 
+#> $spectra
+#>  Number Min. Intensity Max. Intensity
+#>       1             26            816
+#> 
+#> $metadata
+#>   Min. Max.
+#> x    1    1
+#> y    1    1
+#> [1] "x"                 "y"                 "user_name"        
+#> [4] "spectrum_type"     "spectrum_identity" "organization"     
+#> [7] "license"           "session_id"        "file_id"
+

You can compare the processed and unprocessed data in an overlay +plot:

+
plotly_spec(raman_hdpe, processed)
+

We want people to use the process_spec() function for +most processing operations, all processing functions can be tuned using +its parameters. However, we recognize that nesting of functions and +order of operations can be useful for users to control so you can also +use individual functions for each operation if you’d like.

+
+

Threshold Signal-Noise

+

Considering whether you have enough signal to analyze spectra is +important. Classical spectroscopy would recommend your highest peak to +be at least 10 times the baseline of your processed spectra before you +begin analysis. If your spectra is below that threshold, you may want to +consider recollecting it. In practice, we are rarely able to collect +spectra of that good quality and more often use 5. The Signal Over Noise +technique searches your spectra for high and low regions and conducts +division on them to derive the signal to noise ratio. Signal Times Noise +multiplies the mean signal by the standard deviation of the signal and +Total Signal sums the intensities. The latter can be really useful for +thresholding spectral maps to identify particles which we will discuss +later.

+

+

If analyzing spectra in batch, we recommend looking at the heatmap +and optimizing the percent of spectra that are above your signal to +noise threshold to determine the correct settings instead of looking +through spectra individually. Good Signal tells you what percent of your +data are above your signal threshold.

+

+
+
+

Intensity Adjustment

+

Open Specy assumes that intensity units are in absorbance units but Open Specy can adjust reflectance or transmittance spectra to absorbance -units using this selection in the upload file tab. The transmittance -adjustment uses the \(\log_{10} 1/T\) -calculation which does not correct for system or particle -characteristics. The reflectance adjustment use the Kubelka-Munk -equation \(\frac{(1-R)^2}{2R}\). If -none is selected, Open Specy assumes that the uploaded data is an -absorbance spectrum and does not apply an adjustment.

-

This is the respective R code:

-
library(dplyr)
-#> 
-#> Attaching package: 'dplyr'
-#> The following object is masked from 'package:OpenSpecy':
-#> 
-#>     filter
-#> The following objects are masked from 'package:stats':
-#> 
-#>     filter, lag
-#> The following objects are masked from 'package:base':
-#> 
-#>     intersect, setdiff, setequal, union
-
-raman_adj <- raman_hdpe %>%
-  adj_intens()
-
-head(raman_adj)
-#>   wavenumber  intensity
-#> 1    301.040 0.00000000
-#> 2    304.632 0.03037975
-#> 3    308.221 0.02784810
-#> 4    311.810 0.02405063
-#> 5    315.398 0.02531646
-#> 6    318.983 0.02025316
-
-
-

Share metadata on known spectra

-

-

To share metadata about your spectrum, click the metadata input -button. When sharing data, please provide as much metadata as you can. -Metadata helps make shared data as useful as possible. Metadata inputs -each have examples provided in the input. The examples disappear when -the box is clicked and will not be saved if nothing is input by the -user. Mandatory inputs are marked with a red asterisk. If these inputs -are not filled, the data will be considered uninterpretable and will be -discarded. Inputs left blank will be left blank in the metadata sheet -and interpreted as “unknown” or “not applicable”. To share metadata, -click the share data button at the bottom of the metadata inputs.

-

When the user clicks the Share Data button their -current uploaded data and metadata is sent to an open-access online repository.

-

-

All inputs from the metadata (described below) are input to a -metadata sheet. The metadata sheet is given the same unique name as the -data, but it ends with “_form”. The exact same data is saved as would be -downloaded using the download data button (described below). All high -quality uploaded data with metadata will eventually be reviewed by -spectroscopy experts and added to the internal library if it passes -review. If multiple files are going to be uploaded with metadata, the -cells will also stay filled with the last input after the share data -button is clicked. Just upload the next dataset and change the metadata -inputs that are different. If more than 50 files will be shared at once, -you can contact the website administrator to get a bulk upload sheet for -more rapid upload.

-

Type

-
share_spec(raman_hdpe,
-           metadata = c(user_name = "Win Cowger",
-                        contact_info = "wincowger@gmail.com",
-                        spectrum_type = "Raman",
-                        spectrum_identity = "HDPE")
-           )
-

to share your spectral data from the R console.

-
-
-
-

Preprocessing

-

After uploading data, you can preprocess the data using baseline -correction, smoothing, and range selection and save your preprocessed -data. Go to the Preprocess Spectrum tab to select your -parameters for processing the spectrum.

-
-

Preprocess Spectra Plot

-

-

The preprocess spectra plot shows the uploaded spectra in comparison -to the processed spectra that has been processed using the processing -inputs on the page. It will automatically update with any new slider -inputs. This allows the user to tune the inputs to optimize the signal -to noise ratio. The goal with preprocessing is to make peak regions have -high intensities and non-peak regions should have low intensities.

-
-
-

Preprocessing Tools

-

-

When the slider is green for the tool type, that means that that tool -is being used to preprocess the spectrum. If the slider is clicked -blank, the cog button to the right will disappear to indicate that the -tool is no longer being used.

-

-

If the cog button is clicked, any functions associated with that tool -will be displayed and can be manipulated to process the spectrum.

-
-
-

Smoothing Polynomial

-

+units using this selection. The transmittance adjustment uses the \(\log_{10} 1/T\) calculation which does not +correct for system or particle characteristics. The reflectance +adjustment use the Kubelka-Munk equation \(\frac{(1-R)^2}{2R}\). If none is selected, +Open Specy assumes that the uploaded data is an absorbance spectrum and +does not apply an adjustment.

+

This is the respective R code for a scenario where the spectra +doesn’t need intensity adjustment:

+
raman_adj <- raman_hdpe |> 
+  adj_intens(type = "none")
+
+plot(raman_adj)
+

+
+
+

Conforming

+

Conforming spectra is essential before comparing to a reference +library and can be useful for summarizing data when you don’t need it to +be highly resolved spectrally. In the app, conforming happens behind the +scenes without any user input to make sure that the spectra the user +uploads and the library spectra will be compatible. In code, we set the +default spectral resolution to 5 because this tends to be pretty good +for a lot of applications and is in between 4 and 8 which are commonly +used wavenumber resolutions. There are several ways that this is +function can be specified.

+
conform_spec(raman_hdpe) |> # default convert res to 5 wavenumbers.
+  summary()
+#> $wavenumber
+#>  Length Min. Max. Res.
+#>     579  305 3195    5
+#> 
+#> $spectra
+#>  Number Min. Intensity Max. Intensity
+#>       1       41.65339       662.0608
+#> 
+#> $metadata
+#>   Min. Max.
+#> x    1    1
+#> y    1    1
+#> [1] "x"                 "y"                 "user_name"        
+#> [4] "spectrum_type"     "spectrum_identity" "organization"     
+#> [7] "license"           "session_id"        "file_id"
+
+# Force one spectrum to have the exact same wavenumbers as another
+conform_spec(asp_example, range = ps_example$wavenumber, res = NULL) |> 
+  summary()
+#> $wavenumber
+#>  Length     Min.     Max.     Res.
+#>    2126 399.2239 4497.537 1.928618
+#> 
+#> $spectra
+#>  Number Min. Intensity Max. Intensity
+#>       1             NA             NA
+#> 
+#> $metadata
+#>   Min. Max.
+#> x    1    1
+#> y    1    1
+#> [1] "x"          "y"          "file_name"  "license"    "session_id"
+#> [6] "file_id"    "col_id"
+
+# Specify the wavenumber resolution and use a rolling join instead of linear
+# approximation (faster for large datasets). 
+conform_spec(spectral_map, range = ps_example$wavenumber, res = 10,
+             type = "roll") |> 
+  summary()
+#> $wavenumber
+#>  Length Min. Max. Res.
+#>     329  720 4000   10
+#> 
+#> $spectra
+#>  Number Min. Intensity Max. Intensity
+#>     208      -1.317072       1.168225
+#> 
+#> $metadata
+#>   Min. Max.
+#> x    0   12
+#> y    0   15
+#>  [1] "x"             "y"             "file_name"     "license"      
+#>  [5] "description"   "samples"       "lines"         "bands"        
+#>  [9] "header offset" "data type"     "interleave"    "z plot titles"
+#> [13] "pixel size"    "session_id"    "file_id"       "col_id"
+
+
+

Smoothing

+

The first step of the Open Specy preprocessing routing is spectral smoothing. The goal of this function is to increase the signal to noise ratio (S/N) without distorting the shape or relative size of the peaks. The value on the slider is the polynomial order of the Savitzky-Golay -(SG) filter. The SG filter is fit to a moving window of 11 data -points where the center point in the window is replaced with the -polynomial estimate. The number of data points in the window is not user -adjustable. Higher numbers lead to more wiggly fits and thus less -smooth, lower numbers lead to more smooth fits, a 7th order polynomial -will make the spectrum have almost no smoothing. If smoothing is set to -0 then no smoothing is conducted on the spectrum. When smoothing is done -well, peak shapes and relative heights should not change. Typically a -3rd order polynomial (3 on the slider) works to increase the signal to -noise without distortion, but if the spectrum is noisy, decrease -polynomial order and if it is already smooth, increase the polynomial -order to the maximum (7). Examples of smoothing below:

-
- -

Sample raman_hdpe spectrum with -different smoothing polynomials (p) from Cowger et al. (2020).

-
-

The different degrees of smoothing were achieved with the following R -commands:

-
smooth_intens(raman_hdpe, p = 1)
-smooth_intens(raman_hdpe, p = 4)
-

The intensity-adjusted sample spectrum raman_adj is -smoothed accordingly:

-
raman_smooth <- raman_adj %>% 
-  smooth_intens()
-
-head(raman_smooth)
-#>   wavenumber  intensity
-#> 1    301.040 0.00000000
-#> 2    304.632 0.01568318
-#> 3    308.221 0.02461353
-#> 4    311.810 0.02828915
-#> 5    315.398 0.02820811
-#> 6    318.983 0.02586852
-
-
-

Baseline Correction Polynomial

-

+(SG) filter. Higher numbers lead to more wiggly fits and thus less +smooth, lower numbers lead to more smooth fits. The SG filter is fit to +a moving window of 11 data points by default where the center point in +the window is replaced with the polynomial estimate. Larger windows will +produce smoother fits. The derivative order is set to 1 by default which +transforms the spectra to their first derivative. A zero order +derivative will have no derivative transformation. When smoothing is +done well, peak shapes and relative heights should not change. The +absolute value is primarily useful for first derivative spectra where +the absolute value results in an absorbance-like spectrum which is why +we set it as the default.

+

Examples of smoothing with the R package:

+
library(ggplot2)
+data("raman_hdpe")
+
+none <- adj_intens(raman_hdpe, make_rel = T)
+p1 <- smooth_intens(raman_hdpe, polynomial = 1)
+p4 <- smooth_intens(raman_hdpe, polynomial = 4)
+
+compare_derivative <- c_spec(list(none, p1, p4))
+    
+plot(compare_derivative)
+
+Sample `raman_hdpe` spectrum with different smoothing polynomials from Cowger et al. (2020). +

+Sample raman_hdpe spectrum with different smoothing +polynomials from Cowger et al. (2020). +

+
+

Derivative transformation can happen with the same function. You’ll +notice a new function we are using c_spec() which is used +to combine spectral objects into one OpenSpecy object.

+

+none <- adj_intens(raman_hdpe, make_rel = T)
+d1 <- smooth_intens(raman_hdpe, derivative = 1, abs = T)
+d2 <- smooth_intens(raman_hdpe, derivative = 2)
+
+compare_derivative <- c_spec(list(none, d1, d2))
+    
+plot(compare_derivative)
+
+Sample `raman_hdpe` spectrum with different derivatives from Cowger et al. (2020). +

+Sample raman_hdpe spectrum with different derivatives from +Cowger et al. (2020). +

+
+
+
+

Baseline Correction

+

The second step of Open Specy’s preprocessing routine is baseline correction. The goal of baseline correction is to get all non-peak regions of the spectra to zero absorbance. The higher the polynomial @@ -642,127 +2756,165 @@

Baseline Correction Polynomial

baseline fit. The data in the peak region is removed from the fit to make sure that the baseline is less likely to fit to the peaks. The iterative fitting terminates once the difference between the new and -previous fit is small. An example of a good baseline fit below.

-
- -

Sample raman_hdpe spectrum with -different degrees of background subtraction (Cowger et al., 2020).

-
-

The smoothed sample spectrum raman_smooth is -background-corrected as follows:

-
raman_bgc <- raman_smooth %>% 
-  subtr_bg()
-
-head(raman_bgc)
-#>   wavenumber   intensity
-#> 1    301.040 0.000000000
-#> 2    304.632 0.000000000
-#> 3    308.221 0.006298355
-#> 4    311.810 0.008146146
-#> 5    315.398 0.007025667
-#> 6    318.983 0.004412447
-
-
-

Spectral Range

-

-

The final step of preprocessing is restricting the spectral range. -Sometimes the instrument operates with high noise at the ends of the -spectrum and sometimes the baseline fit can produce distortions at the -ends of the spectrum, both can be removed using this routine. You should -look into the signal to noise ratio of your specific instrument by -wavelength to determine what wavelength ranges to use. Distortions due -to baseline fit can be assessed from looking at the preprocess spectra -plot. Additionally, you can restrict the range to examine a single peak -or a subset of peaks of interests. This function allows users to isolate -peaks of interest for matching, while removing noise and influence from -less relevant spectral data.

-
-
-

Download Data

-

-

After you have the preprocessing parameters set, we recommend that -you download the preprocessed data for your records. The download data -button will append the uploaded data to three columns created by the -preprocessing parameters. “Wavelength” and “Absorbance” are columns from -the data uploaded by the user. “NormalizedIntensity” is the max-min -normalized value (Equation 1) of the “Absorbance”. “Smoothed” is the -Savitzky-Golay filter specified by the slider explained above. -“BaselineRemoved” is the smoothed and baseline corrected value that is -visible on the center plot.

+previous fit is small. An example of a good baseline fit below. For +those who have been with OpenSpecy a while, you will notice the app no +longer supports manual baseline correction, it was a hard choice but it +just didn’t make sense to keep it now that we are moving toward high +throughput automated methods. It does still exist in the R function +though.

+
data("raman_hdpe")
+
+none <- adj_intens(raman_hdpe)
+d2 <- subtr_baseline(raman_hdpe, degree = 2)
+d8 <- subtr_baseline(raman_hdpe, degree = 8)
+
+compare_subtraction <- c_spec(list(none, d2, d8))
+
+plot(compare_subtraction)
+
+Sample `raman_hdpe` spectrum with different degrees of background subtraction (Cowger et al., 2020). +

+Sample raman_hdpe spectrum with different degrees of +background subtraction (Cowger et al., 2020). +

-
-

Matching

-

After uploading data and preprocessing it (if desired) you can now -identify the spectrum. To identify the spectrum go to the Match -Spectrum tab.

-

You will see your spectrum and the top matches, but before looking at -matches, you need to check the three selectable parameters below.

-
-

Spectrum Type

-

-

The spectra type input on the “Match spectra” tab specifies the type -of spectra (Raman or FTIR) that the user has uploaded and wants to match -to. This input will tell the website whether to use the FTIR library or -the Raman library to make the match.

-
-
-

Spectrum To Analyze

-

-

The spectra to analyze input specifies if the tool will match the -Uploaded spectra (unaltered by the inputs on the -Preprocess Spectra tab) or the Processed -Spectra (manipulated by the inputs in the Preprocess Spectra -Tab).

-
-
-

Region To Match

-

-

The region to match input specifies if the “Full Spectrum” will match -the entire range of the spectra (including non peak regions) in the -reference database. This is the most intuitive match. Or should the -Peaks Only match just the peak regions in the reference -database. This is an advanced feature proposed in Renner et al. (2017). -This can be a less intuitive approach but in cases where there are few -peaks and high baseline interference, it could be the best option. In -cases where non-peak regions are important for the interpretation of the -match, this is not the best approach.

-
-
-

Match Table

-

-

The selectable table shows the top material matches returned by the -tool, their Pearson’s r value, and the organization they were provided -by. When rows are selected their spectra are added to the match plot. -The spectrum being matched and reference library are determined by the -previously mentioned parameters. During the matching process, one final -cleaning step happens using a simple minimum subtraction algorithm -(Equation 2) which in many cases will allow unprocessed spectra to -remove subtle baseline, but will not harm the spectra which has no -baseline. Then, these aligned data are tested for correlation using the -Pearson’s r. The Pearson’s r is used as a match quality indicator and -the spectra from the top 1000 best matches are returned from the -library. You can restrict the libraries which are displayed in the table -by clicking the box that says All under the -Organization column.

-

Similarly you can restrict the range of Pearson's r values or search -for specific material types.

-\[\mathrm{for~each}~peak~group^{1,n}: x - -\mathrm{min}(x)\] -

-Equation 2: Minimum Subtraction +

+

Range Selection

+

+

Sometimes an instrument operates with high noise at the ends of the +spectrum and, a baseline fit produces distortions, or there are regions +of interest for analysis. Range selection accomplishes those goals. You +should look into the signal to noise ratio of your specific instrument +by wavelength to determine what wavelength ranges to use. Distortions +due to baseline fit can be assessed from looking at the preprocess +spectra. Additionally, you can restrict the range to examine a single +peak or a subset of peaks of interests. The maximum and minimum +wavenumbers will specify the range to keep in the app. In the R +function, multiple ranges can be specified simultaneously.

+
data("raman_hdpe")
+
+none <- adj_intens(raman_hdpe)
+r1 <- restrict_range(raman_hdpe, min = 1000, max = 2000)
+r2 <- restrict_range(raman_hdpe, min = c(1000, 1800), max = c(1200, 2000))
+
+compare_ranges <- c_spec(list(none, r1, r2), range = "common")
+# Common argument crops the ranges to the most common range between the spectra
+# when joining. 
+
+plot(compare_ranges)
+
+Sample `raman_hdpe` spectrum with different degrees of background subtraction (Cowger et al., 2020). +

+Sample raman_hdpe spectrum with different degrees of +background subtraction (Cowger et al., 2020). +

+
+
+
+

Flattening Ranges

+

+

Sometimes there are peaks that really shouldn’t be in your spectra +and can distort your interpretation of the spectra but you don’t +necessarily want to remove the regions from the analysis because you +believe those regions should be flat instead of having a peak. One way +to deal with this is to replace the peak values with the mean of the +values around the peak. This is the purpose of the +flatten_range function. By default it is set to flatten the +CO2 region for FTIR spectra because that region often needs to be +flattened when atmouspheric artefacts occur in spectra. Like +restrict_range, the R function can accept multiple +ranges.

+
single <- filter_spec(spectral_map, 120) # Function to filter spectra by index
+                                         # number or name or a logical vector. 
+none <- adj_intens(single)
+f1 <- flatten_range(single)
+f2 <- flatten_range(single, min = c(1000, 2500), max = c(1200, 3000))
+
+compare_flats <- c_spec(list(none, f1, f2))
+
+plot(compare_flats)
+
+Sample `raman_hdpe` spectrum with different degrees of background subtraction (Cowger et al., 2020). +

+Sample raman_hdpe spectrum with different degrees of +background subtraction (Cowger et al., 2020).

-

The same table can be returned using the Open Specy library commands +

+
+
+

Min-Max Normalization

+

Min-Max normalization is used throughout the app but is not one of +the options the user can specify. Often we regard spectral intensities +as arbitrary and min-max normalization allows us to view spectra on the +same scale without drastically distorting their shapes or relative peak +intensities. In the app, it is primarily used for plotting and as a +processing step before correlation analysis. In the package, most of the +processing functions will min-max transform your spectra relative by +default if you do not specify otherwise. This plot shows the two spectra +on such different scales that one looks like a straight line.

+
none <- adj_intens(raman_hdpe, make_rel = F)
+relative <- adj_intens(raman_hdpe, make_rel = T)
+
+compare_rel <- c_spec(list(none, relative))
+
+plot(compare_rel)
+
+Sample `raman_hdpe` spectrum with different degrees of background subtraction (Cowger et al., 2020). +

+Sample raman_hdpe spectrum with different degrees of +background subtraction (Cowger et al., 2020). +

+
+
+
+
+

Identifying Spectra

+

+

After uploading data and preprocessing it (if desired) you can now +identify the spectrum. To identify the spectrum go to the +Identification box. Pro tip: if you select +*Identification** without uploading data to the app, you’ll be able to +explore the library by itself.

+
+

Reading Libraries

+

These options define the strategy for identification. The ID Library +will inform which library is used. Both (default) will search both FTIR +and Raman libraries. Deriv will search against a derivative transformed +library. No Baseline will search against a baseline corrected library. +This should be in line with how you choose to process your spectra. Cor +options use a simple Pearson correlation search algorithm. AI is +currently experimental and uses either a multinomial model or +correlation on mediod spectra from the library. Correlation thresholding +will set the minimum value from matching to use as a ‘positive +identification’

+

In the R package, you’ll download and load the libraries into your +working environment.

+
get_lib() #will load all libraries unless specified otherwise. 
+lib <- load_lib(type = "derivative")
+
+
+

Matches

+

+

Top matches in the app can be assessed by clicking the cog in the +right hand corner of the Spectra box. This will open a side window with +the matches sorted from most to least similar. Clicking on rows in the +table will add the selected match to the spectra viewer. Using the +table’s filter options, you can restrict the range of Pearson's r values +or search for specific material types.

+

The same table can be returned using the OpenSpecy package commands in the R console.

-
# Fetch current spectral library from https://osf.io/x7dpz/
-get_lib()
-# Load library into global environment
-spec_lib <- load_lib()
-# Match spectrum with library and retrieve meta data
-match_spec(raman_bgc, library = spec_lib, which = "raman")
-
-
-

Selection Metadata

+
data("test_lib")
+
+processed <- process_spec(ps_example, 
+                        conform_spec_args = list(range = test_lib$wavenumber, 
+                                                 res = NULL))
+
+test <- match_spec(processed, test_lib, add_object_metadata = "col_id",
+                   add_library_metadata = "sample_name")
+
+
+

Selection Metadata

Whatever match is selected from the match table may have additional metadata about it. That metadata will be displayed below the plot. Some @@ -772,10 +2924,10 @@

Selection Metadata

match.

The R command for manual metadata selection using sample_name == 5381 as example is:

-
find_spec(sample_name == 5381, library = spec_lib, which = "raman")
+
find_spec(sample_name == 5381, library = spec_lib, which = "raman")
-
-

Match Plot

+
+

Match Plot

This plot is dynamically updated by selecting matches from the match table. The red spectrum is the spectrum that you selected from the @@ -784,152 +2936,48 @@

Match Plot

data table in this tab will be updated. These plots can be saved as a .png by clicking the camera button at the top of the plot.

-
-

How to interpret the reported matches

-

There are several important things to consider when interpreting a -spectral match including the library source, the Pearson’s r, and other -metrics.

-
-

The library source

-

When you click on a spectrum, all of the metadata that we have in -Open Specy about that source will be displayed in a metadata window -below to the matches table. Each library has different methodologies -used to develop it. It is useful to read up on the library sources from -the literature that they came from. E.g. Chabuka et al. 2020 focuses on -weathered plastics, so matching to it may suggest that your spectrum is -of a weathered polymer. Primpke et al. 2018 only has a spectral range up -to 2000, so some polymers may be difficult to differentiate with it. -Make sure to cite the libraries that you use during your search when you -publish your results. The authors were kind enough to make their data -open access so that it could be used in Open Specy and we should return -the favor by citing them.

-
-
-

Pearson’s r

-

Correlation values are used to identify the closest matches available -in the current Open Specy spectral libraries to improve material -identification and reduce sample processing times. Pearson’s r values -range from 0 - 1 with 0 being a completely different spectrum and 1 -being an exact match. Some general guidelines that we have observed from -using Open Specy. If no matches are > ~0.3 the material may require -additional processing or may not exist in the Open Specy library. -Correlation values are not the only metric you should use to assess your -spectra’s match to a material in the library, matches need to make -sense.

-
-
-

Things to consider beyond correlation

-

Peak position and height similarities are more important than -correlation and need to be assessed manually. Peak position correlates -with specific bond types. Peak height correlates to the concentration of -a compound. Therefore, peak height and peak position should match as -closely as possible to the matched spectrum. When there are peaks that -exist in the spectra you are trying to interpret that do not exist in -the match, there may be additional materials to identify. In this case, -restrict the preprocessing range to just the unidentified peak and try -to identify it as an additional component (see also https://www.compoundchem.com/2015/02/05/irspectroscopy/).

-

Also, check the match metadata to see if the match makes sense. -Example: A single fiber cannot be a “cotton blend” since there would be -no other fibers to make up the rest of the blend. Example: Cellophane -does not degrade into fibers, so a match for a fiber to cellophane -wouldn’t make sense. Example: You are analyzing a particle at room -temperature, but the matched material is liquid at room temperature. The -material may be a component of the particle but it cannot be the whole -particle.

-
-
-

How specific do you need to be in the material type of the -match?

-

You can choose to be specific about how you classify a substance -(e.g. polyester, cellophane) or more general (e.g. synthetic, -semi-synthetic, natural, etc.). The choice depends on your research -question. Using more general groups can speed up analysis time but will -decrease the information you have for interpretation. To identify -materials more generally, you can often clump the identities provided by -Open Specy to suit your needs. For example, matches to “polyester” and -“polypropylene” could be clumped to the category “plastic”.

-
-
-

How to differentiate between similar spectra?

-

One common challenge is differentiating between LDPE and HDPE. But, -even with a low resolution instrument (MacroRAM, 2 cm-1 -pixel-1), you can still see some differences. From a wide -view, these low, medium, and high density PE samples all look relatively -similar (figures courtesy of Bridget O'Donnell, Horiba Scientific):

-

-

But, a closer look at the 1450 cm-1 band reveals clear -differences:

-

-

When you overlay them, you start to see differences in other spectral -regions too:

-

-

So, the question is, how do we deal with samples that are very -similar with only subtle differences? Usually, researchers will use MVA -techniques after they’ve collected multiple reference spectra of known -samples (LDPE and HDPE in this case). They can then develop models and -apply them to distinguish between different types of PE. With a -reference database like Open Specy, this is complicated by the fact that -researchers are measuring samples on different instruments with -correspondingly different spectral responses and spectral resolutions. -That makes it even more difficult to accurately match definitively to -LDPE and HDPE as opposed to generic ‘PE’.

-

One possibility is to place more emphasis (from a computational -perspective) on the bands that show the most difference (the triplet at -1450 cm-1) by restricting the range used to match in Open -Specy.

-

The other, much simpler option is to just match any PE hit to generic -‘PE’ and not specifically HDPE or LDPE.

-

Another challenge is in differentiating between types of nylons. But, -Raman has a pretty easy time distinguishing nylons. These spectra were -recorded of a series of nylons and the differences are much more -distinguishable compared to the PE results above (nylon 6, 6-6, 6-9, -6-10, and 6-12 top to bottom):

-

-

The differences are even more pronounced when you overlay the -spectra:

-

-
-
-

What to do when matches aren’t making sense

-
    -
  1. Double check that the baseline correction and smoothing parameters -result in the best preprocessing of the data.
  2. -
  3. Try reprocessing your spectrum, but limit it to specific peak -regions with a higher signal to noise ratio.
  4. -
  5. Restrict the spectral range to include or exclude questionable peaks -or peaks that were not present in the previous matches.
  6. -
  7. Restrict the spectral range to exclude things like CO2 -(2200 cm-1) or H2O (~1600 cm-1) in -spikes in the IR spectrum.
  8. -
  9. If nothing above works to determine a quality match, you may need to -measure the spectrum of your material again or use another spectral -analysis tool.
  10. -
-
-
-
-
-

Mini Tutorials

-
-

File Conversion in SpectraGryph to Open Specy Accepted Format

-
    -
  1. Download Spectragryph from https://www.effemm2.de/spectragryph/down.html

  2. -
  3. Open Spectragryph and upload your file by dragging and dropping -it into the console.

  4. -
-

-
    -
  1. Click File, Save/export data, save data as, and save it as an spc -file. ¸
  2. -
-

-
    -
  1. Then upload that .spc file to Open Specy.
  2. -
-

-
-
-

Conceptual diagram of data flow through Open Specy

+
+

Sharing Reference Data

+

If you have reference data or AI models that you think would be +useful for sharing with the spectroscopy community through OpenSpecy +please contact the website administrator to discuss options for +collaborating.

+
+
+
+

Characterizing Particles

+
+

Thresholding

+
+
+

Collapse Spectra

+
+
+
+

Additional App Features

+

Accessibility is extremely important to us and we are making strives +to improve the accessibility of Open Specy for all spectroscopists. +Please reach out if you have ideas for improvement.

+

We added a Google translate plugin to all pages in the app so that +you can easily translate the app. We know that not all languages will be +fully supported but we will continue to try and improve the +translations.

+
+

Download Data

+

+

After you have the preprocessing parameters set, we recommend that +you download the preprocessed data for your records. The download data +button will append the uploaded data to three columns created by the +preprocessing parameters. “Wavelength” and “Absorbance” are columns from +the data uploaded by the user. “NormalizedIntensity” is the max-min +normalized value (Equation 1) of the “Absorbance”. “Smoothed” is the +Savitzky-Golay filter specified by the slider explained above. +“BaselineRemoved” is the smoothed and baseline corrected value that is +visible on the center plot.

+
+
+
+

Conceptual diagram of Open Specy

diff --git a/vignettes/sop/baselinecorrectionpoly.jpg b/vignettes/sop/baselinecorrectionpoly.jpg new file mode 100644 index 00000000..1f01c895 Binary files /dev/null and b/vignettes/sop/baselinecorrectionpoly.jpg differ diff --git a/vignettes/images/deselection.jpg b/vignettes/sop/deselection.jpg similarity index 100% rename from vignettes/images/deselection.jpg rename to vignettes/sop/deselection.jpg diff --git a/vignettes/images/download.jpg b/vignettes/sop/download.jpg similarity index 100% rename from vignettes/images/download.jpg rename to vignettes/sop/download.jpg diff --git a/vignettes/sop/flattten_example.jpg b/vignettes/sop/flattten_example.jpg new file mode 100644 index 00000000..8864d63c Binary files /dev/null and b/vignettes/sop/flattten_example.jpg differ diff --git a/vignettes/images/flowchart.png b/vignettes/sop/flowchart.png similarity index 100% rename from vignettes/images/flowchart.png rename to vignettes/sop/flowchart.png diff --git a/vignettes/images/hoverexample.jpg b/vignettes/sop/hoverexample.jpg similarity index 100% rename from vignettes/images/hoverexample.jpg rename to vignettes/sop/hoverexample.jpg diff --git a/vignettes/sop/identification.jpg b/vignettes/sop/identification.jpg new file mode 100644 index 00000000..7faff78a Binary files /dev/null and b/vignettes/sop/identification.jpg differ diff --git a/vignettes/sop/intensityadjustment.jpg b/vignettes/sop/intensityadjustment.jpg new file mode 100644 index 00000000..3c0dc754 Binary files /dev/null and b/vignettes/sop/intensityadjustment.jpg differ diff --git a/vignettes/sop/mainpage.jpg b/vignettes/sop/mainpage.jpg new file mode 100644 index 00000000..efbae6ab Binary files /dev/null and b/vignettes/sop/mainpage.jpg differ diff --git a/vignettes/sop/map_vis.jpg b/vignettes/sop/map_vis.jpg new file mode 100644 index 00000000..342b9991 Binary files /dev/null and b/vignettes/sop/map_vis.jpg differ diff --git a/vignettes/sop/matches.jpg b/vignettes/sop/matches.jpg new file mode 100644 index 00000000..f41c11f3 Binary files /dev/null and b/vignettes/sop/matches.jpg differ diff --git a/vignettes/images/matchplot.png b/vignettes/sop/matchplot.png similarity index 100% rename from vignettes/images/matchplot.png rename to vignettes/sop/matchplot.png diff --git a/vignettes/sop/processing.jpg b/vignettes/sop/processing.jpg new file mode 100644 index 00000000..665537c5 Binary files /dev/null and b/vignettes/sop/processing.jpg differ diff --git a/vignettes/sop/rangeselection.jpg b/vignettes/sop/rangeselection.jpg new file mode 100644 index 00000000..e4ecd419 Binary files /dev/null and b/vignettes/sop/rangeselection.jpg differ diff --git a/vignettes/sop/samplefile.jpg b/vignettes/sop/samplefile.jpg new file mode 100644 index 00000000..6f0dcbe8 Binary files /dev/null and b/vignettes/sop/samplefile.jpg differ diff --git a/vignettes/images/selectionmetadata.jpg b/vignettes/sop/selectionmetadata.jpg similarity index 100% rename from vignettes/images/selectionmetadata.jpg rename to vignettes/sop/selectionmetadata.jpg diff --git a/vignettes/sop/sig_over_noise.jpg b/vignettes/sop/sig_over_noise.jpg new file mode 100644 index 00000000..927d8b0a Binary files /dev/null and b/vignettes/sop/sig_over_noise.jpg differ diff --git a/vignettes/sop/signal_settings.jpg b/vignettes/sop/signal_settings.jpg new file mode 100644 index 00000000..1b6e94da Binary files /dev/null and b/vignettes/sop/signal_settings.jpg differ diff --git a/vignettes/sop/smoothingpoly.jpg b/vignettes/sop/smoothingpoly.jpg new file mode 100644 index 00000000..71bd2be8 Binary files /dev/null and b/vignettes/sop/smoothingpoly.jpg differ diff --git a/vignettes/sop/snr_threshold.jpg b/vignettes/sop/snr_threshold.jpg new file mode 100644 index 00000000..312b1f30 Binary files /dev/null and b/vignettes/sop/snr_threshold.jpg differ diff --git a/vignettes/sop/spectra_vis.jpg b/vignettes/sop/spectra_vis.jpg new file mode 100644 index 00000000..1ed0199e Binary files /dev/null and b/vignettes/sop/spectra_vis.jpg differ diff --git a/vignettes/sop/uploadfile.jpg b/vignettes/sop/uploadfile.jpg new file mode 100644 index 00000000..cf0a4d96 Binary files /dev/null and b/vignettes/sop/uploadfile.jpg differ diff --git a/vignettes/spectragryph.Rmd b/vignettes/spectragryph.Rmd new file mode 100644 index 00000000..83c50d6d --- /dev/null +++ b/vignettes/spectragryph.Rmd @@ -0,0 +1,44 @@ +--- +title: "File conversion in SpectraGryph" +author: > + Jessica Meyers, Jeremy Conkle, Win Cowger, Zacharias Steinmetz, + Andrew Gray, Chelsea Rochman, Sebastian Primpke, Jennifer Lynch, + Hannah Hapich, Hannah De Frond, Keenan Munno, Bridget O’Donnell +date: "`r Sys.Date()`" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{File conversion in SpectraGryph} + %\VignetteEncoding{UTF-8} + %\VignetteEngine{knitr::rmarkdown} +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + warning = FALSE +) +``` + +1. Download Spectragryph from +[https://www.effemm2.de/spectragryph/down.html](https://www.effemm2.de/spectragryph/down.html) + +2. Open Spectragryph and upload your file by dragging and dropping it into the +console. + +```{r, fig.align="center", out.width="98%", echo=FALSE} +knitr::include_graphics("spectragryph/spectragryph-1.png") +``` + +3. Click File, Save/export data, save data as, and save it as an spc +file. ¸ + +```{r, fig.align="center", out.width="98%", echo=FALSE} +knitr::include_graphics("spectragryph/spectragryph-2.png") +``` + +4. Then upload that .spc file to Open Specy. + +```{r, fig.align="center", echo=FALSE} +knitr::include_graphics("sop/uploadfile.jpg") +``` diff --git a/vignettes/images/spectragryph-1.png b/vignettes/spectragryph/spectragryph-1.png similarity index 100% rename from vignettes/images/spectragryph-1.png rename to vignettes/spectragryph/spectragryph-1.png diff --git a/vignettes/images/spectragryph-2.png b/vignettes/spectragryph/spectragryph-2.png similarity index 100% rename from vignettes/images/spectragryph-2.png rename to vignettes/spectragryph/spectragryph-2.png