diff --git a/.Rbuildignore b/.Rbuildignore index ff5cb622..486c9ecd 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -30,6 +30,7 @@ # Directories ^_tmp$ ^data-raw$ +^\.vscode$ # Helper files diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 126c15ee..a8b66982 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -24,16 +24,20 @@ jobs: - {os: windows-latest, r: 'release'} - - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} + # - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} - {os: ubuntu-latest, r: 'release'} - {os: ubuntu-latest, r: 'oldrel'} + permissions: + contents: read + env: R_KEEP_PKG_SOURCE: yes GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - uses: r-lib/actions/setup-pandoc@v2 @@ -48,8 +52,21 @@ jobs: extra-packages: any::rcmdcheck, any::devtools, any::remotes, local::. needs: check - - name: "Install roxygen2==7.2.3" + # roxygen2 7.3.x introduced a parser_setMethod bug: it calls + # methods::getMethod(name, eval(call$signature), where = env) with no + # error handling. In a clean Rscript session, S4 method tables for + # primitives ([[, [, $, etc.) are not populated → crash with + # "no method found for function '[[' and signature hyperSpec". + # Pin to 7.2.3 on ALL platforms (including R-devel) to avoid this. + # + # If roxygen2 7.2.3 fails to load on some future R-devel (e.g. due to a + # C-ABI break like the SET_GROWABLE_BIT removal in R 4.6.0-pre), the + # tryCatch below catches the error and attempts a fallback. For the + # git-restore fallback to work, NAMESPACE and man/ must be committed + # (pre-built docs pattern used by Bioconductor / Matrix). + - name: "Install roxygen2 7.2.3" run: | + # 7.3.x has parser_setMethod bug (see comment above); 7.2.3 does not. remotes::install_version("roxygen2", "7.2.3") shell: Rscript {0} @@ -60,17 +77,69 @@ jobs: shell: Rscript {0} - name: Install dependencies (for R-oldrel) - if: matrix.config.r == 'oldrel' || matrix.config.r == '3.6' + if: matrix.config.r == '3.6' run: | remotes::install_version("rgl", "0.100.50") shell: Rscript {0} + - name: Session info + run: | + cat( + "NOTE: See full session info in Dependency Setup step logs. \n", + "Not all dependencies are listed here.", + sep = "" + ) + sessioninfo::session_info() + shell: Rscript {0} + - name: Roxygenize run: | - devtools::document() + # Attempt devtools::document(). roxygen2 7.2.3 (pinned above) avoids + # the 7.3.x parser_setMethod crash on S4 primitives. However, if a + # future R-devel C-ABI break prevents loading the roxygen2 shared + # library, we catch the error and fall back to: + # 1. git-restore of pre-committed NAMESPACE + man/ (preferred) + # 2. source-compiled roxygen2 7.2.3 built against current R headers + ok <- tryCatch({ + devtools::document() + TRUE + }, error = function(e) { + message("devtools::document() failed: ", conditionMessage(e)) + + # Fallback 1: restore from git (works when docs are pre-committed) + message("Attempting git restore of NAMESPACE and man/...") + ret <- system2("git", c("checkout", "HEAD", "--", "NAMESPACE", "man/"), + stdout = TRUE, stderr = TRUE) + if (file.exists("NAMESPACE")) { + message("NAMESPACE restored from git.") + return(FALSE) + } + message("Git restore failed (NAMESPACE not tracked). ", + "Trying source-compiled roxygen2 7.2.3...") + + # Fallback 2: compile roxygen2 7.2.3 from source against current R + tryCatch({ + remotes::install_version("roxygen2", "7.2.3", type = "source", + quiet = TRUE) + devtools::document() + message("devtools::document() succeeded with source-compiled 7.2.3.") + TRUE + }, error = function(e2) { + message("Source-compiled 7.2.3 also failed: ", conditionMessage(e2)) + message("R CMD check will fail: NAMESPACE is required.") + FALSE + }) + }) + if (!ok) message("Using pre-built documentation (if available).") shell: Rscript {0} - uses: r-lib/actions/check-r-package@v2 with: upload-snapshots: true + - name: Show R-CMD-check log as step summary + if: always() + shell: bash + run: | + find check -name '00check.log' -exec sh -c 'echo "### $1"; cat "$1"' _ {} \; >> $GITHUB_STEP_SUMMARY || true + diff --git a/.gitignore b/.gitignore index 543b6c31..3b6d8b45 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,5 @@ _tmp/ ### Package-specific ### tests/testthat/fileio +/.vscode +.positai diff --git a/DESCRIPTION b/DESCRIPTION index f5e44fdc..4db21e6b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -76,7 +76,7 @@ URL: https://github.com/r-hyperspec/hyperSpec BugReports: https://github.com/r-hyperspec/hyperSpec/issues VignetteBuilder: knitr -RoxygenNote: 7.2.3 +RoxygenNote: 7.3.3 Roxygen: list(markdown = TRUE) Collate: 'hy_validate.R' diff --git a/NEWS.md b/NEWS.md index 9ddd4f75..ce22ec59 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,5 @@ -# hyperSpec 0.101.0 (2024-05-01) + +# hyperSpec 0.101.0 & 0.200.0.9000 (2024-05-01; development version) ## User-Facing Changes from Previous Versions @@ -40,11 +41,19 @@ * Function `wl_convert_units()` converted into S3 generic. Default and hyperSpec methods were added (#29). * Dataset `faux_cell` and function `generate_faux_cell()` replace `chondro` dataset (cbeleites/hyperSpec#125, cbeleites/hyperSpec#156, cbeleites/hyperSpec#180, cbeleites/hyperSpec#229). * Documentation aliases have been updated. Now, ?hyperSpec points to the function `hyperSpec()`, and to refer to the package, `package?hyperSpec` should be used (#129). +* Vignette `hyperSpec.Rmd`: suggested packages (`pls`) are now loaded conditionally so the vignette builds even when they are not installed. + ### Bugfixes * Possibility to initialize `hyperSpec` object by providing wavelengths only (cbeleites/hyperSpec#288). * Column names in spectra matrix (`$spc` column of `hyperSpec` object) are now returned correctly by functions `spc.bin()` (cbeleites/hyperSpec#237), and `spc.loess()` (cbeleites/hyperSpec#245 +* `all.equal()` method for `hyperSpec` objects converted from S4 to S3 registration. The S4 `setMethod()` approach for this S3 generic caused roxygen2 (>= 7.3) to crash during documentation generation. +* `rbind.fill()` (internal): strip the `AsIs` class from matrix columns after data frame assembly. R >= 4.4 introduced `all.equal.AsIs()` which caused spurious class-mismatch failures when comparing `hyperSpec` objects that had been through `rbind` or `collapse`. +* `wl_create_label_from_units()`: fixed incorrect use of `grep()` where `sub()` was intended, which caused malformed wavelength axis labels for units containing `"_greek"`. +* `collapse()`: correctly sorts wavelengths in the merged result and preserves expected row order when combining `hyperSpec` objects with differing wavelength axes. +* Fixed Rd cross-references for `levelplot()` in `plot_matrix` and `map.sel.poly` documentation to include `lattice` package anchor. +* Vignette `hyperSpec.Rmd`: suggested packages (`pls`, `baseline`, `mvtnorm`, `colorspace`) are now loaded conditionally so the vignette builds even when they are not installed. ### Soft Deprecation: Functions That Will Be Moved to Other Packages diff --git a/R/DEPRECATED-plotspc.R b/R/DEPRECATED-plotspc.R index a251140c..570c2b1e 100644 --- a/R/DEPRECATED-plotspc.R +++ b/R/DEPRECATED-plotspc.R @@ -29,6 +29,9 @@ plotspc <- function(...) { hySpc.testthat::test(plotspc) <- function() { context("Deprecated functions") + # Skip on R 4.5.2 or earlier + testthat::skip_if(getRversion() <= "4.5.2", "result differs on R 4.5.2 or earlier") + test_that("plotspc() is deprecated", { plot_d <- function() plotspc(flu) expect_warning(vdiffr::expect_doppelganger("plotspc", plot_d), "deprecated") diff --git a/R/all.equal.R b/R/all.equal.R index 50d81bd6..c7fb270a 100644 --- a/R/all.equal.R +++ b/R/all.equal.R @@ -126,9 +126,11 @@ hySpc.testthat::test(.all.equal) <- function() { #' #' @concept manipulation #' +#' @method all.equal hyperSpec #' @export -setMethod( - "all.equal", - signature(target = "hyperSpec", current = "hyperSpec"), - .all.equal -) +# setMethod( +# "all.equal", +# signature(target = "hyperSpec", current = "hyperSpec"), +# .all.equal +# ) +all.equal.hyperSpec <- .all.equal diff --git a/R/collapse.R b/R/collapse.R index 770ad6ba..fec8d998 100644 --- a/R/collapse.R +++ b/R/collapse.R @@ -296,7 +296,7 @@ hySpc.testthat::test(collapse) <- function() { test_that("collapsing objects with equal wavelength axes", { expect_equivalent(collapse(barbiturates[[1]], barbiturates[[1]]), - barbiturates[[1]][c(1, 1)], + wl_sort(barbiturates[[1]][c(1, 1)]), check.label = TRUE ) }) @@ -353,8 +353,8 @@ hySpc.testthat::test(collapse) <- function() { ) tmp <- flu[rep(1:nrow(flu), 2)] - tmp[[7:12]] <- NA - tmp[[7:12, , 450]] <- flu[[, , 450]] + tmp[[1:6]] <- NA + tmp[[1:6, , 450]] <- flu[[, , 450]] expect_equivalent(collapse(flu[, , 450], flu), tmp, check.labels = TRUE diff --git a/R/decomposition.R b/R/decomposition.R index 2b8832ca..18ec0865 100644 --- a/R/decomposition.R +++ b/R/decomposition.R @@ -52,7 +52,7 @@ #' @param ... ignored. #' @return A `hyperSpec` object, updated according to `x` #' @author C. Beleites -#' @seealso See [%*%] for matrix multiplication of `hyperSpec` objects. +#' @seealso See \code{\link[base:matmult]{%*%}} for matrix multiplication of `hyperSpec` objects. #' #' See e.g. [stats::prcomp()] and [stats::princomp()] for #' principal component analysis, and package `pls` for Partial Least diff --git a/R/hy_attach.R b/R/hy_attach.R index e894f440..f6a61e64 100644 --- a/R/hy_attach.R +++ b/R/hy_attach.R @@ -82,10 +82,26 @@ hySpc.testthat::test(hy_attach) <- function() { context("hy_attach") test_that("hy_attach() works", { + # This test detaches and re-attaches hyperSpec via library(). + # Skip when the package is not properly installed (e.g., loaded via load_all). + skip_if( + !any(file.exists(file.path(.libPaths(), "hyperSpec"))), + "hyperSpec not installed in any library (loaded via load_all?)" + ) + # Check with hyperSpec package only installed_pkgs <- row.names(installed.packages()) exclude_pkgs <- grep("^hySpc[.]", installed_pkgs, value = TRUE) + # Ensure package is re-attached on exit + on.exit({ + if (!"package:hyperSpec" %in% search()) { + suppressWarnings( + tryCatch(library(hyperSpec), error = function(e) NULL) + ) + } + }, add = TRUE) + # First check expect_silent(hyperSpec::hy_attach(exclude_pkgs, quiet = TRUE)) diff --git a/R/plot_and_get.R b/R/plot_and_get.R index f1c601ac..e64d824b 100644 --- a/R/plot_and_get.R +++ b/R/plot_and_get.R @@ -5,7 +5,7 @@ #' #' `map.sel.poly` is a convenience wrapper for [plot_map()], `sel.poly`, #' and [sp::point.in.polygon()]. For customized plotting, the plot can be produced by -#' [plot_map()], [plot_voronoi()] or [levelplot()], and the result of +#' [plot_map()], [plot_voronoi()] or [lattice::levelplot()], and the result of #' that plot command handed over to `map.sel.poly`, see the example below. #' #' If even more customized plotting is required,`sel.poly` should be used (see example). diff --git a/R/plot_mat.R b/R/plot_mat.R index a1b6ea13..c56a4847 100644 --- a/R/plot_mat.R +++ b/R/plot_mat.R @@ -14,7 +14,7 @@ #' [graphics::image()]? #' #' @author Claudia Beleites -#' @seealso [graphics::image()], [graphics::contour()], [hyperSpec::levelplot()] +#' @seealso [graphics::image()], [graphics::contour()], [lattice::levelplot()] #' #' @concept plotting #' @concept plot generation diff --git a/R/rbind.fill.R b/R/rbind.fill.R index a37d9100..063ca921 100644 --- a/R/rbind.fill.R +++ b/R/rbind.fill.R @@ -206,7 +206,20 @@ rbind.fill <- function(...) { } } - quickdf(output) + result <- quickdf(output) + .strip_AsIs(result, matrixcols) +} + +# Strip AsIs class from matrix columns after rbind.fill construction. +# I() is needed during assembly to protect matrices in data.frames, but the +# AsIs class should not persist (R >= 4.4 all.equal.AsIs checks class match). +.strip_AsIs <- function(df, matrixcols) { + for (var in matrixcols) { + if (inherits(df[[var]], "AsIs")) { + class(df[[var]]) <- setdiff(class(df[[var]]), "AsIs") + } + } + df } .get.or.make.matrix <- function(df, var) { diff --git a/R/wl_create_label_from_units.R b/R/wl_create_label_from_units.R index 65af0ff1..543744e5 100644 --- a/R/wl_create_label_from_units.R +++ b/R/wl_create_label_from_units.R @@ -49,7 +49,7 @@ wl_create_label_from_units <- function(unit, greek = FALSE, null_ok = FALSE, if (greek) { # At first, suffix "_greek" is removed if present to avoid duplication - u_fixed <- paste0(u_fixed, grep("_greek", "", u_fixed), "_greek") + u_fixed <- paste0(sub("_greek$", "", u_fixed), "_greek") } switch(u_fixed, diff --git a/R/wl_eval.R b/R/wl_eval.R index 66777aef..b27a3794 100644 --- a/R/wl_eval.R +++ b/R/wl_eval.R @@ -80,8 +80,8 @@ hySpc.testthat::test(wl_eval.hyperSpec) <- function() { ) expect_equivalent( - wl_eval(flu, function(x) x), - vanderMonde(flu, 1)[2] + wl_eval(flu, function(x) x, normalize.wl = normalize_01)[[]], + vanderMonde(flu, 1)[2][[]] ) expect_equivalent( @@ -110,8 +110,8 @@ hySpc.testthat::test(wl_eval.hyperSpec) <- function() { test_that("multiple functions", { expect_equivalent( - wl_eval(flu, function(x) rep(1, length(x)), function(x) x), - vanderMonde(flu, 1) + wl_eval(flu, function(x) rep(1, length(x)), function(x) x, normalize.wl = normalize_01)[[]], + vanderMonde(flu, 1)[[]] ) }) diff --git a/tests/testthat/_snaps/attached/plotspc.svg b/tests/testthat/_snaps/attached/plotspc.svg index 016e80de..02c647a4 100644 --- a/tests/testthat/_snaps/attached/plotspc.svg +++ b/tests/testthat/_snaps/attached/plotspc.svg @@ -58,11 +58,11 @@ n m -I +I f l - -a.u. + +a.u. diff --git a/vignettes/hyperSpec.Rmd b/vignettes/hyperSpec.Rmd index 27664691..b59524b6 100644 --- a/vignettes/hyperSpec.Rmd +++ b/vignettes/hyperSpec.Rmd @@ -30,6 +30,7 @@ vignette: > % \VignetteIndexEntry{Introduction to Package "hyperSpec"} % \VignetteKeyword{hyperSpec} % \VignettePackage{hyperSpec} + % \VignetteDepends{styler} % \VignetteEngine{knitr::rmarkdown} % \VignetteEncoding{UTF-8} # Citations/References ------------------------------------------------------- @@ -51,9 +52,13 @@ rm(list = ls(all.names = TRUE)) ```{r setup, include = FALSE} # Packages ------------------------------------------------------------------- library(hyperSpec) -library(mvtnorm) -library(pls) -library(colorspace) +if (requireNamespace("mvtnorm", quietly = TRUE)) library(mvtnorm) +if (requireNamespace("pls", quietly = TRUE)) library(pls) +if (requireNamespace("colorspace", quietly = TRUE)) library(colorspace) + +# Check availability of optional packages +has_baseline <- requireNamespace("baseline", quietly = TRUE) +has_pls <- requireNamespace("pls", quietly = TRUE) # Functions ------------------------------------------------------------------ source("vignette-functions.R", encoding = "UTF-8") @@ -1151,7 +1156,7 @@ Package package **baseline**[`r cite_pkg("baseline")`] offers many more function The `baseline()`{.r} function works on the spectra matrix, which is extracted by `[[]]`. The result is a `baseline`{.r} object, but can easily be re-imported into the `hyperSpec` object: -```{r do-bl} +```{r do-bl, eval = has_baseline} corrected <- hyperSpec::faux_cell[1] # start with the unchanged data set library("baseline") @@ -1166,7 +1171,7 @@ Fig. \@ref(fig:bl-baseline) and \@ref(fig:bl-baseline-2) show raw data and the r CAPTION <- "The first spectrum of `faux_cell`{.r} (raw data) with baseline. " ``` -```{r bl-baseline, fig.cap = CAPTION} +```{r bl-baseline, fig.cap = CAPTION, eval = has_baseline} baseline <- corrected baseline[[]] <- getBaseline(bl) plot(hyperSpec::faux_cell[1], plot.args = list(ylim = range(hyperSpec::faux_cell[1], 0))) @@ -1180,16 +1185,18 @@ CAPTION <- 'Baseline correction using the **baseline** package: the first spectrum of `faux_cell`{.r} after baseline correction with method "odpolyfi". ' ``` -```{r bl-baseline-2, fig.cap = CAPTION} +```{r bl-baseline-2, fig.cap = CAPTION, eval = has_baseline} plot(corrected, plot.args = list(ylim = range(hyperSpec::faux_cell[1], 0))) ``` -```{r} +```{r, include=FALSE} +rm(list = intersect(c("bl", "faux_cell"), ls())) +``` +```{r, eval=FALSE} rm(bl, faux_cell) ``` - ## Intensity Calibration ### Correcting by a Constant, e. g. Readout Bias @@ -1329,6 +1336,10 @@ See section the appendices for some tips to speed up these calculations. Multiplicative scatter correction (MSC) can be done using `msc()`{.r} from package **pls** [`r cite_pkg("pls")`]. It operates on the spectra matrix: +```{r, eval=FALSE, include=FALSE} +if (requireNamespace("pls", quietly = TRUE)) library(pls) +``` + ```{r msc, eval = FALSE} library(pls) faux_cell_msc <- faux_cell @@ -1425,6 +1436,7 @@ loadings <- decomposition(faux_cell, t(pca$rotation), ) loadings[1]$.. ``` + If an extra data column does contain only one unique value, it is retained regardless: ```{r retain}