Run a Custom Analysis Function in a Searchlight
run_custom_searchlight.RdApplies a user-defined function to the data within each searchlight sphere and returns the results, typically as `NeuroVol` or `NeuroSurface` objects within a `searchlight_result` structure.
Usage
run_custom_searchlight(
  dataset,
  custom_func,
  radius,
  method = c("standard", "randomized"),
  niter = 100,
  ...,
  .cores = 1,
  .verbose = FALSE
)Arguments
- dataset
 An `mvpa_dataset` or `mvpa_surface_dataset` object.
- custom_func
 A function to apply within each searchlight sphere. It should accept two arguments:
`sl_data`: A matrix or tibble containing the data (samples x features_in_sphere) for the current sphere.
`sl_info`: A list containing information about the sphere, including `center_index` (the index of the center voxel/vertex), `indices` (the indices of all features within the sphere), and potentially `coords` (coordinates of the center).
The function *must* return a named list or a single-row data frame (or tibble) containing scalar metric values. All spheres must return the same named metrics.
- radius
 The radius of the searchlight sphere (in mm for volumes, or vertex connections for surfaces - see `neuroim2::spherical_roi`).
- method
 The type of searchlight: "standard" (systematically covers all center voxels) or "randomized" (samples spheres randomly, useful for large datasets). Defaults to "standard".
- niter
 The number of iterations for a "randomized" searchlight. Ignored if `method = "standard"`. Defaults to 100.
- ...
 Optional arguments passed to `mvpa_iterate` (e.g., `batch_size`).
- .cores
 Number of cores to use for parallel processing via the `future` framework. Defaults to 1 (sequential). Set using `future::plan()` beforehand for more control.
- .verbose
 Logical. If `TRUE`, prints progress messages during iteration. Defaults to `FALSE`.
Value
A `searchlight_result` object (see `rMVPA::wrap_out`). This is a list containing:
`results`: A named list where each element corresponds to a metric returned by `custom_func`. Each element is itself a `searchlight_performance` object containing a `NeuroVol` or `NeuroSurface` (`$data`) with the metric values mapped back to the brain space, along with summary statistics (`$summary_stats`).
`metrics`: A character vector of the metric names.
`n_voxels`, `active_voxels`: Information about the dataset mask.
If `method = "randomized"`, the values in the output maps represent the average metric value for each voxel across all spheres it participated in.
Details
This function provides a flexible way to perform custom analyses across the brain using a searchlight approach, without defining a full `mvpa_model`. It handles iterating over searchlight spheres, extracting data, running the custom function (potentially in parallel), handling errors, and combining the results back into brain-space maps.
The `custom_func` performs the core calculation for each sphere. The framework manages the iteration, data handling, parallelization, error catching, and result aggregation.
For `method = "standard"`, the function iterates through every active voxel/vertex in the dataset mask as a potential sphere center. For `method = "randomized"`, it randomly selects sphere centers for `niter` iterations. The final map represents an average of the results from spheres covering each voxel. This requires the custom function's results to be meaningfully averageable.
**Important**: The `custom_func` must consistently return the same set of named scalar metrics for every sphere it successfully processes.
Examples
# Generate sample dataset
dset_info <- gen_sample_dataset(D = c(10, 10, 10), nobs = 30, nlevels = 2)
dataset_obj <- dset_info$dataset
# Define a custom function: calculate mean and sd within the sphere
my_sl_stats <- function(sl_data, sl_info) {
  # sl_data is samples x features_in_sphere matrix
  # sl_info contains center_index, indices, etc.
  mean_signal <- mean(sl_data, na.rm = TRUE)
  sd_signal <- sd(sl_data, na.rm = TRUE)
  n_features <- ncol(sl_data)
  list(
    mean_signal = mean_signal,
    sd_signal = sd_signal,
    n_vox_in_sphere = n_features
  )
}
# Run the custom searchlight (standard method)
# \donttest{
custom_sl_results <- run_custom_searchlight(dataset_obj, my_sl_stats,
                                            radius = 7, method = "standard",
                                            .cores = 2, .verbose = TRUE)
#> Error in run_custom_searchlight(dataset_obj, my_sl_stats, radius = 7,     method = "standard", .cores = 2, .verbose = TRUE): Specified radius (7) is too large for dataset dimensions (10x10x10). Maximum supported radius is 5.
print(custom_sl_results)
#> Error: object 'custom_sl_results' not found
# Access the NeuroVol for a specific metric
mean_signal_map <- custom_sl_results$results$mean_signal$data
#> Error: object 'custom_sl_results' not found
# plot(mean_signal_map) # Requires neuroim2 plotting capabilities
# Example with an error in some spheres (e.g., if too few voxels)
my_error_sl_func <- function(sl_data, sl_info) {
  if (ncol(sl_data) < 5) {
    stop("Too few voxels in this sphere!")
  }
  list(mean_signal = mean(sl_data))
}
error_sl_results <- run_custom_searchlight(dataset_obj, my_error_sl_func,
                                           radius = 4, method = "standard")
#> INFO [2025-09-28 22:01:00] Starting custom searchlight analysis (method: standard, radius: 4 mm)...
#> INFO [2025-09-28 22:01:00] Preparing 512 standard searchlight spheres...
#> INFO [2025-09-28 22:01:17] 
#> MVPA Iteration Complete
#> - Total ROIs: 512
#> - Processed: 512
#> - Skipped: 0
#> INFO [2025-09-28 22:01:18] Combining results from standard searchlight...
#> INFO [2025-09-28 22:01:18] Finished custom searchlight analysis.
print(error_sl_results) # Errors will be caught, corresponding voxels may be NA
#> 
#>  Searchlight Analysis Results 
#> 
#> - Coverage 
#>   - Voxels/Vertices in Mask:  1,000 
#>   - Voxels/Vertices with Results:  512 
#> - Output Maps (Metrics) 
#>   -  mean_signal  (Type:  searchlight_performance ) 
#> 
# Run randomized searchlight (faster for large datasets/radii)
custom_sl_rand_results <- run_custom_searchlight(dataset_obj, my_sl_stats,
                                                 radius = 7, method = "randomized",
                                                 niter = 50, # Fewer iterations for example
                                                 .cores = 2, .verbose = TRUE)
#> Error in run_custom_searchlight(dataset_obj, my_sl_stats, radius = 7,     method = "randomized", niter = 50, .cores = 2, .verbose = TRUE): Specified radius (7) is too large for dataset dimensions (10x10x10). Maximum supported radius is 5.
print(custom_sl_rand_results)
#> Error: object 'custom_sl_rand_results' not found
# Clean up parallel plan
future::plan(future::sequential)
# }