Skip to content

Commit 8d291bf

Browse files
More judicious tests of missing confound columns when confound calculate/regress enabled
1 parent 301ee70 commit 8d291bf

2 files changed

Lines changed: 159 additions & 9 deletions

File tree

R/postprocess_confounds.R

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ postprocess_confounds <- function(proc_files, cfg, processing_sequence,
8787
stop("Cannot handle overlaps in noproc_columns and columns for confounds")
8888
}
8989

90+
confounds_to_filt <- NULL
9091
if (isTRUE(cfg$scrubbing$enable)) {
9192
to_log(lg, "info", "Computing spike regressors using expression: {paste(cfg$scrubbing$expression, collapse=', ')}")
9293
spike_mat <- compute_spike_regressors(confounds, cfg$scrubbing$expression, lg = lg)
@@ -108,6 +109,17 @@ postprocess_confounds <- function(proc_files, cfg, processing_sequence,
108109
has_confounds <- !is.null(confound_cols) && length(confound_cols) > 0L
109110
has_noproc <- !is.null(noproc_cols) && length(noproc_cols) > 0L
110111

112+
# verify that requested confound columns exist
113+
if (has_confounds) {
114+
confounds_to_filt <- subset(confounds, select = confound_cols)
115+
116+
if (ncol(confounds_to_filt) == 0L || nrow(confounds_to_filt) == 0L) {
117+
to_log(lg, "warn", "Confound columns were requested but no usable data were found in {proc_files$confounds}; skipping confound filtering.")
118+
has_confounds <- FALSE
119+
confound_cols <- character()
120+
}
121+
}
122+
111123
if (!has_confounds && !has_noproc) {
112124
if (isTRUE(cfg$confound_calculate$enable) || isTRUE(cfg$confound_regression$enable)) {
113125
to_log(lg, "info", "Confound postprocessing skipped; no confound columns matched the request and no noproc columns were supplied.")
@@ -118,8 +130,6 @@ postprocess_confounds <- function(proc_files, cfg, processing_sequence,
118130
}
119131

120132
if (has_confounds) {
121-
confounds_to_filt <- subset(confounds, select = confound_cols)
122-
123133
# generate NIfTI with confound timeseries
124134
confounds_bids <- extract_bids_info(proc_files$confounds)
125135
tmp_out <- construct_bids_filename(modifyList(confounds_bids, list(description = cfg$bids_desc, directory=tempdir(), ext=NA)), full.names=TRUE)
@@ -202,10 +212,19 @@ postprocess_confounds <- function(proc_files, cfg, processing_sequence,
202212
)
203213

204214
calc_cols <- cfg$confound_calculate$columns
215+
df <- filtered_confounds[, integer(0), drop = FALSE]
216+
205217
if (!is.null(calc_cols) && length(calc_cols) > 0L) {
206-
df <- subset(filtered_confounds, select = calc_cols)
207-
} else {
208-
df <- filtered_confounds[, integer(0), drop = FALSE]
218+
present_cols <- intersect(calc_cols, names(filtered_confounds))
219+
missing_cols <- setdiff(calc_cols, names(filtered_confounds))
220+
221+
if (length(missing_cols) > 0L) {
222+
to_log(lg, "warn", "Requested confound_calculate columns were not available after filtering and will be skipped: {paste(missing_cols, collapse = ', ')}")
223+
}
224+
225+
if (length(present_cols) > 0L) {
226+
df <- cbind(df, filtered_confounds[, present_cols, drop = FALSE])
227+
}
209228
}
210229

211230
calc_noproc <- cfg$confound_calculate$noproc_columns
@@ -254,13 +273,19 @@ postprocess_confounds <- function(proc_files, cfg, processing_sequence,
254273

255274
if (isTRUE(cfg$confound_regression$enable)) {
256275
reg_cols <- cfg$confound_regression$columns
276+
df <- filtered_confounds[, integer(0), drop = FALSE]
257277
if (!is.null(reg_cols) && length(reg_cols) > 0L) {
258-
df <- subset(filtered_confounds, select = reg_cols)
259-
if (ncol(df) > 0L) {
278+
present_cols <- intersect(reg_cols, names(filtered_confounds))
279+
missing_cols <- setdiff(reg_cols, names(filtered_confounds))
280+
281+
if (length(missing_cols) > 0L) {
282+
to_log(lg, "warn", "Requested confound_regression columns were not available after filtering and will be skipped: {paste(missing_cols, collapse = ', ')}")
283+
}
284+
285+
if (length(present_cols) > 0L) {
286+
df <- filtered_confounds[, present_cols, drop = FALSE]
260287
df <- as.data.frame(lapply(df, function(cc) cc - mean(cc, na.rm = TRUE)))
261288
}
262-
} else {
263-
df <- filtered_confounds[, integer(0), drop = FALSE]
264289
}
265290

266291
reg_noproc <- cfg$confound_regression$noproc_columns

tests/testthat/test-postprocess_confounds.R

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,3 +247,128 @@ test_that("postprocess_confounds recomputes framewise displacement when only nop
247247
replace(conf_df$csf, is.na(conf_df$csf), 0)
248248
)
249249
})
250+
251+
test_that("postprocess_confounds skips processing when confound data are empty", {
252+
skip_if_not_installed("RNifti")
253+
254+
tmpdir <- tempfile("postproc_confounds_empty_")
255+
dir.create(tmpdir)
256+
on.exit(unlink(tmpdir, recursive = TRUE), add = TRUE)
257+
258+
conf_df <- data.frame(rot_x = numeric(0))
259+
conf_path <- file.path(tmpdir, "sub-01_task-rest_desc-confounds_timeseries.tsv")
260+
data.table::fwrite(conf_df, conf_path, sep = "\t")
261+
262+
cfg <- list(
263+
motion_filter = list(enable = FALSE),
264+
scrubbing = list(enable = FALSE, apply = FALSE, add_to_confounds = FALSE),
265+
confound_regression = list(enable = TRUE, columns = "rot_x", noproc_columns = NULL, prefix = "r"),
266+
confound_calculate = list(enable = FALSE, columns = NULL, noproc_columns = NULL, demean = TRUE),
267+
apply_aroma = list(enable = FALSE, nonaggressive = TRUE),
268+
temporal_filter = list(low_pass_hz = NULL, high_pass_hz = NULL, method = "butterworth"),
269+
bids_desc = "pp",
270+
tr = 1,
271+
overwrite = TRUE
272+
)
273+
274+
output_bids_info <- list(
275+
subject = "01",
276+
task = "rest",
277+
description = "denoise",
278+
suffix = "bold",
279+
ext = ".nii.gz",
280+
directory = tmpdir
281+
)
282+
283+
proc_files <- list(
284+
confounds = conf_path,
285+
melodic_mix = NULL,
286+
noise_ics = integer(0)
287+
)
288+
289+
expect_warning(
290+
regress_path <- postprocess_confounds(
291+
proc_files = proc_files,
292+
cfg = cfg,
293+
processing_sequence = character(0),
294+
output_bids_info = output_bids_info,
295+
fsl_img = NULL,
296+
lg = NULL
297+
),
298+
"Confound columns were requested but no usable data were found",
299+
fixed = TRUE
300+
)
301+
302+
expect_null(regress_path)
303+
304+
reg_file <- construct_bids_filename(
305+
modifyList(output_bids_info, list(suffix = "regressors", ext = ".tsv")),
306+
full.names = TRUE
307+
)
308+
expect_false(file.exists(reg_file))
309+
})
310+
311+
test_that("postprocess_confounds handles empty confound data with calc/reg enabled", {
312+
skip_if_not_installed("RNifti")
313+
314+
tmpdir <- tempfile("postproc_confounds_empty_calc_")
315+
dir.create(tmpdir)
316+
on.exit(unlink(tmpdir, recursive = TRUE), add = TRUE)
317+
318+
conf_df <- data.frame(rot_x = numeric(0), framewise_displacement = numeric(0))
319+
conf_path <- file.path(tmpdir, "sub-01_task-rest_desc-confounds_timeseries.tsv")
320+
data.table::fwrite(conf_df, conf_path, sep = "\t")
321+
322+
cfg <- list(
323+
motion_filter = list(enable = FALSE),
324+
scrubbing = list(enable = FALSE, apply = FALSE, add_to_confounds = FALSE),
325+
confound_regression = list(enable = TRUE, columns = "rot_x", noproc_columns = NULL, prefix = "r"),
326+
confound_calculate = list(enable = TRUE, columns = "framewise_displacement", noproc_columns = NULL, demean = TRUE),
327+
apply_aroma = list(enable = FALSE, nonaggressive = TRUE),
328+
temporal_filter = list(low_pass_hz = NULL, high_pass_hz = NULL, method = "butterworth"),
329+
bids_desc = "pp",
330+
tr = 1,
331+
overwrite = TRUE
332+
)
333+
334+
output_bids_info <- list(
335+
subject = "01",
336+
task = "rest",
337+
description = "denoise",
338+
suffix = "bold",
339+
ext = ".nii.gz",
340+
directory = tmpdir
341+
)
342+
343+
proc_files <- list(
344+
confounds = conf_path,
345+
melodic_mix = NULL,
346+
noise_ics = integer(0)
347+
)
348+
349+
expect_warning(
350+
regress_path <- postprocess_confounds(
351+
proc_files = proc_files,
352+
cfg = cfg,
353+
processing_sequence = character(0),
354+
output_bids_info = output_bids_info,
355+
fsl_img = NULL,
356+
lg = NULL
357+
),
358+
"Confound columns were requested but no usable data were found",
359+
fixed = TRUE
360+
)
361+
362+
expect_null(regress_path)
363+
364+
reg_file <- construct_bids_filename(
365+
modifyList(output_bids_info, list(suffix = "regressors", ext = ".tsv")),
366+
full.names = TRUE
367+
)
368+
confound_file <- construct_bids_filename(
369+
modifyList(output_bids_info, list(suffix = "confounds", ext = ".tsv")),
370+
full.names = TRUE
371+
)
372+
expect_false(file.exists(reg_file))
373+
expect_false(file.exists(confound_file))
374+
})

0 commit comments

Comments
 (0)