Skip to content

Commit 2f2c925

Browse files
committed
Return per-file NeuroVecs for preproc scans
1 parent c208afa commit 2f2c925

3 files changed

Lines changed: 95 additions & 3 deletions

File tree

NEWS.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# bidser 0.4.0
22

3+
* Change `read_preproc_scans()` to return a file-ordered list of `NeuroVec`
4+
objects, one per matched preprocessed scan, instead of collapsing multiple
5+
files into a single container.
6+
* Fix derivative discovery and legacy fMRIPrep helpers for datasets that place
7+
derivative subject folders directly under `derivatives/` with
8+
`prep_dir = "derivatives"`.
9+
* Make `read_confounds()` raise informative, selection-aware errors when no
10+
matching participants, confound files, or usable requested confounds are
11+
found.
12+
* Fix `create_preproc_mask()` to ignore matching JSON sidecars and only read
13+
actual NIfTI mask images.
314
* Add `query_files()` as the recommended explicit query API for new workflows,
415
with `match_mode`, `require_entity`, and raw/derivatives `scope` controls.
516
* Add `get_metadata()` for inheritance-aware BIDS metadata resolution with

R/bidsio.R

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ read_func_scans.bids_project <- function(x, mask, mode = c("normal", "bigvec"),
5555
#'
5656
#' This function reads preprocessed functional MRI scans from a BIDS project's fMRIPrep
5757
#' derivatives directory. It uses the \code{preproc_scans} function to locate the files
58-
#' and then reads them into a \code{NeuroVec} object using the neuroim2 package. If a
58+
#' and then reads each one into its own \code{NeuroVec} object using the
59+
#' neuroim2 package. If a
5960
#' mask is not provided, one will be automatically created from available brainmask files.
6061
#'
6162
#' @param x A \code{bids_project} object with fMRIPrep derivatives
@@ -67,7 +68,8 @@ read_func_scans.bids_project <- function(x, mask, mode = c("normal", "bigvec"),
6768
#' @param modality Image modality to match (default: "bold" for functional MRI)
6869
#' @param ... Extra arguments passed to \code{neuroim2::read_vec}
6970
#'
70-
#' @return An instance of type \code{NeuroVec} containing the preprocessed functional data.
71+
#' @return A named list of \code{NeuroVec} objects, one per matched preprocessed
72+
#' scan, in file order.
7173
#'
7274
#' @details
7375
#' This function requires the \code{neuroim2} package to be installed. It will throw an
@@ -100,6 +102,7 @@ read_func_scans.bids_project <- function(x, mask, mode = c("normal", "bigvec"),
100102
#' # Provide a custom mask
101103
#' mask <- create_preproc_mask(proj, thresh=0.95)
102104
#' masked_scans <- read_preproc_scans(proj, mask=mask)
105+
#' first_run <- masked_scans[[1]]
103106
#'
104107
#' # Clean up
105108
#' unlink(ds_path, recursive=TRUE)
@@ -138,7 +141,11 @@ read_preproc_scans.bids_project <- function(x, mask=NULL, mode = c("normal", "bi
138141
stop("Package 'neuroim2' is required for this function. Install with: remotes::install_github('bbuchsbaum/neuroim2')", call. = FALSE)
139142
}
140143

141-
neuroim2::read_vec(fnames, mask=mask, mode=mode, ...)
144+
scans <- lapply(fnames, function(fname) {
145+
neuroim2::read_vec(fname, mask=mask, mode=mode, ...)
146+
})
147+
names(scans) <- fnames
148+
scans
142149
}
143150

144151

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
context("read_preproc_scans.bids_project")
2+
library(testthat)
3+
library(bidser)
4+
5+
create_read_preproc_fixture <- function() {
6+
tmp <- tempfile("bidser_read_preproc_")
7+
dir.create(tmp, recursive = TRUE)
8+
9+
readr::write_tsv(
10+
tibble::tibble(participant_id = "sub-01"),
11+
file.path(tmp, "participants.tsv")
12+
)
13+
jsonlite::write_json(
14+
list(Name = "ReadPreproc", BIDSVersion = "1.8.0"),
15+
file.path(tmp, "dataset_description.json"),
16+
auto_unbox = TRUE
17+
)
18+
19+
dir.create(file.path(tmp, "sub-01", "func"), recursive = TRUE)
20+
file.create(file.path(tmp, "sub-01", "func", "sub-01_task-rest_run-01_bold.nii.gz"))
21+
file.create(file.path(tmp, "sub-01", "func", "sub-01_task-rest_run-02_bold.nii.gz"))
22+
23+
deriv_root <- file.path(tmp, "derivatives", "fmriprep", "sub-01", "func")
24+
dir.create(deriv_root, recursive = TRUE)
25+
jsonlite::write_json(
26+
list(
27+
Name = "fmriprep",
28+
BIDSVersion = "1.8.0",
29+
DatasetType = "derivative",
30+
GeneratedBy = list(list(Name = "fmriprep", Version = "test"))
31+
),
32+
file.path(tmp, "derivatives", "fmriprep", "dataset_description.json"),
33+
auto_unbox = TRUE
34+
)
35+
36+
bold_arr <- array(seq_len(2 * 2 * 2 * 3), dim = c(2, 2, 2, 3))
37+
mask_arr <- array(1, dim = c(2, 2, 2))
38+
39+
for (run in c("01", "02")) {
40+
RNifti::writeNifti(
41+
bold_arr,
42+
file.path(
43+
deriv_root,
44+
paste0("sub-01_task-rest_run-", run, "_space-MNI152NLin2009cAsym_desc-preproc_bold.nii.gz")
45+
)
46+
)
47+
RNifti::writeNifti(
48+
mask_arr,
49+
file.path(
50+
deriv_root,
51+
paste0("sub-01_task-rest_run-", run, "_space-MNI152NLin2009cAsym_desc-brain_mask.nii.gz")
52+
)
53+
)
54+
}
55+
56+
tmp
57+
}
58+
59+
test_that("read_preproc_scans returns one NeuroVec per matched file", {
60+
skip_if_not_installed("RNifti")
61+
skip_if_not_installed("neuroim2")
62+
63+
tmp <- create_read_preproc_fixture()
64+
on.exit(unlink(tmp, recursive = TRUE, force = TRUE), add = TRUE)
65+
66+
proj <- bids_project(tmp, fmriprep = TRUE, index = "none")
67+
fnames <- preproc_scans(proj, subid = "01", task = "rest", full_path = TRUE)
68+
scans <- bidser:::read_preproc_scans.bids_project(proj, subid = "01", task = "rest")
69+
70+
expect_type(scans, "list")
71+
expect_length(scans, 2)
72+
expect_equal(names(scans), fnames)
73+
expect_true(all(vapply(scans, inherits, logical(1), "NeuroVec")))
74+
})

0 commit comments

Comments
 (0)